107

Is it possible to get the object property name as a string

person = {};
person.first_name = 'Jack';
person.last_name = 'Trades';
person.address = {};
person.address.street = 'Factory 1';
person.address.country = 'USA';

I'd like to use it like this:

var pn = propName( person.address.country ); // should return 'country' or 'person.address.country'
var pn = propName( person.first_name );      // should return 'first_name' or 'person.first_name'

NOTE: this code is exactly what I'm looking for. I understand it sounds even stupid, but it's not.

This is what I want to do with it.

HTML

person = {};
person.id_first_name = 'Jack';
person.id_last_name = 'Trades';
person.address = {};
person.address.id_address = 'Factory 1';
person.address.id_country = 'USA';


extPort.postMessage
(
  {
    message : MSG_ACTION,
    propName( person.first_name ): person.first_name
  }
};

----------------------ANSWER-----------------------

Got it thanks to ibu. He pointed the right way and I used a recursive function

var res = '';

function propName(prop, value) {
    for (var i in prop) {
        if (typeof prop[i] == 'object') {
            if (propName(prop[i], value)) {
                return res;
            }
        } else {
            if (prop[i] == value) {
                res = i;
                return res;
            }
        }
    }
    return undefined;
}

var pn = propName(person, person.first_name); // returns 'first_name'
var pn = propName(person, person.address.country); // returns 'country'

DEMO: http://jsbin.com/iyabal/1/edit

Cœur
  • 37,241
  • 25
  • 195
  • 267
CLiFoS
  • 1,163
  • 2
  • 9
  • 11
  • 8
    I'm confused... why do you want the property name to return the same thing you fed it? You already know the property name then... If you're looking for a way to iterate through properties, you can use the bracket notation and loop through the keys, as properties are also hash indices – RonaldBarzell Nov 28 '12 at 18:34
  • You need to also pass a reference to the object into the function. – Šime Vidas Nov 28 '12 at 18:35
  • Not automatically. The string referenced by `country` property doesn't know anything about the `address` object, and the object referenced by the `address` property doesn't know anything about the `person` object. – I Hate Lazy Nov 28 '12 at 18:37
  • I understand this can be confusing, but that is exactly what I need. I need the property name as a string tough – CLiFoS Nov 28 '12 at 18:38
  • so you actually mean var pn = propName("USA"); – Christophe Nov 28 '12 at 18:38
  • I'd love to help, but jsFiddle is currently unresponsive `-.-` – Šime Vidas Nov 28 '12 at 18:41
  • no. the code I posted is what I need. basically, the prop name is the ID of the page element and I want to get it to use it. – CLiFoS Nov 28 '12 at 18:41
  • You need to explicitly pass them as separate values. – I Hate Lazy Nov 28 '12 at 18:43
  • You want to use the ID to get the prop, or the prop to get the ID? – Jason Nov 28 '12 at 18:46
  • @Šime CodePen.io is better than jsFiddle anyway ;) – Jason Nov 28 '12 at 18:48
  • You really need to clarify your question, maybe add the html sample. Look at the answers, 4 people have 4 different interpretations!!! – Christophe Nov 28 '12 at 18:55
  • Your code sample won't work as you're actually just passing the value of that property, not some reference to the property. Your first example would pass 'USA' to the function which is not useful for your desired result. – Jason L. Nov 28 '12 at 19:04
  • Do you want to do this just because you like the coding style, or are you working wtih some existing unchangeable code that you need to use this way? What are your constraints? – Aerik Nov 28 '12 at 19:45
  • And http://jsbin.com/iyabal/2/edit shows the flaw with this approach. – Scott Sauyet Nov 28 '12 at 20:10
  • 8
    -1 Sorry, your code is really a bad practice. See this example: http://jsbin.com/iyabal/4/edit ... and http://en.wikipedia.org/wiki/Nestor_Burma as a reference ;-) – Christophe Nov 28 '12 at 20:23
  • yes, you are both right and I have doped this line of thinking. I'm sending an array as a parameter now, and it's working fine. thanks anyway. – CLiFoS Nov 29 '12 at 00:52
  • possible duplicate of [JavaScript: Getting the object's property name](http://stackoverflow.com/questions/4260308/javascript-getting-the-objects-property-name) – Nic Mar 20 '15 at 01:20
  • 1
    This would be useful instead of passing strings around representing a object property name. You could then have static checking, refactoring or mangling. – RationalDev likes GoFundMonica May 28 '16 at 09:01
  • [automatic name property in a created object](http://stackoverflow.com/a/43127618/6452210) – Alain DENIS Mar 30 '17 at 20:45
  • [automatic name property for a created object](http://stackoverflow.com/a/43127618/6452210) you could have a look at that example.. ;) – Alain DENIS Mar 30 '17 at 20:47
  • Can you delete your answer out of the question please. If you want to answer your own question post an actual answer below so others can vote on it. – Caltor Nov 07 '19 at 13:53
  • Tell me you want to use typescript without telling me you want to use typescript. – Chris Sep 30 '21 at 22:09

16 Answers16

43

I know a best practice that using Object.keys(your_object). It will parse to array property name for you. Example:

var person = { firstName: 'John', lastName: 'Cena', age: '30' };
var listPropertyNames = Object.keys(person); //["firstName", "lastName", "age"]

I hope this example is useful for you.

Triet Nguyen
  • 763
  • 9
  • 20
34

You can accomplish this by creating a mirror object that stores all the property names and call a property with an expression

Javascript version

var person = {};
person.firstname = 'Jack';
person.address = "123 Street";
person.child = { name: 'Example' };

function getPropertyName(obj, expression) {
    var res = {};
    Object.keys(obj).map(k => { res[k] = k; });
    return expression(res);
}

console.log(getPropertyName(person, o => o.address)); // 'address'
console.log(getPropertyName(person.child, o => o.name)); // 'name'

Typescript version (based on @MadSkunk answer)

function getPropertyName<T extends object>(o: T, expression: (x: { [Property in keyof T]: string }) => string) {
  const res = {} as { [Property in keyof T]: string };
  Object.keys(o).map(k => res[k as keyof T] = k);
  return expression(res);
}

console.log(getPropertyName(obj, a => a.firstname)); // 'firstname'
console.log(getPropertyName(obj.child, a => a.name)); // 'name'
artemnih
  • 4,136
  • 2
  • 18
  • 28
28

If anyone's looking for a TypeScript version of MarsRobot's answer, try this:

function nameof<T>(obj: T, expression: (x: { [Property in keyof T]: () => string }) => () => string): string
{
    const res: { [Property in keyof T]: () => string } = {} as { [Property in keyof T]: () => string };

    Object.keys(obj).map(k => res[k as keyof T] = () => k);

    return expression(res)();
}

Usage:

const obj = { 
    property1: 'Jim',
    property2: 'Bloggs',
    property3: 'Bloggs',
    method: () => 'a string',
    child: { property4: 'child1' }
};

const test1 = nameof(obj, x => x.property1);
const test2 = nameof(obj, x => x.property2);
const test3 = nameof(obj, x => x.method);
const test4 = nameof(obj.child, x => x.property4);

console.log(test1);    // -> 'property1'
console.log(test2);    // -> 'property2'
console.log(test3);    // -> 'method'
console.log(test4);    // -> 'property4'

This version works even when objects have multiple properties with the same value (unlike some of the other answers above), and with editors like Visual Studio will provide intellisense for the property names when you get here: nameof(obj, x => x.

Ben Orgera
  • 38
  • 8
MadSkunk
  • 3,309
  • 2
  • 32
  • 49
  • To get rid of the "No index signature with a parameter of type ...." error, you need to add `as keyof T` to `res[k]`. It would be `res[k as keyof T]`. Suggested edit queue is full else I'd add this to the answer myself. – Luminous May 03 '22 at 14:15
  • I get an error: `TypeError: expression(...) is not a function` – Olivier.B Jul 08 '22 at 12:01
  • It doesn't work if the property is nullable/optional (prop?) (gives the error said by @Olivier.B) but I don't know how to fix it :) – Carlo G Aug 29 '22 at 20:03
  • @CarloG it makes sense since that property may not be defined, therefore `Object.keys` wouldn't be able to retrieve it. Otherwise you may use `nameof(obj, a => a.optional!);` – artemnih May 12 '23 at 21:51
14

You can wrap your property in a function and then convert the function to a string and get the property out of it.

For example:

function getPropertyName(propertyFunction) {
    return /\.([^\.;]+);?\s*\}$/.exec(propertyFunction.toString())[1];
}

Then to use it:

var myObj = {
    myProperty: "testing"
};

getPropertyName(function() { myObj.myProperty; }); // myProperty

Beware that minifiers could break this.

Edit: I have created a compiler transform that works with babel and the typescript compiler (see ts-nameof). This is a much more reliable than doing something at runtime.

David Sherret
  • 101,669
  • 28
  • 188
  • 178
9

Using Proxy:

var propName = ( obj ) => new Proxy(obj, {
    get(_, key) {
        return key;
    } 
});


var person = {};
person.first_name = 'Jack';
person.last_name = 'Trades';
person.address = {};
person.address.street = 'Factory 1';
person.address.country = 'USA';

console.log(propName(person).first_name);
console.log(propName(person.address).country);
Isk1n
  • 371
  • 2
  • 6
9

I use the following in TypeScript. This way retains type-information and disallows selecting non-existing property keys.

export function getPropertyName<T extends object>(obj: T, selector: (x: Record<keyof T, keyof T>) => keyof T): keyof T {
  const keyRecord = Object.keys(obj).reduce((res, key) => {
    const typedKey = key as keyof T
    res[typedKey] = typedKey
    return res
  }, {} as Record<keyof T, keyof T>)
  return selector(keyRecord)
}

const obj = {
  name: 'test',
  address: {
    street: 'test',
  }
}

console.log(getPropertyName(obj, (x) => x.name)) // name
console.log(getPropertyName(obj.address, (x) => x.street)) // street
artemnih
  • 4,136
  • 2
  • 18
  • 28
IACM-KF
  • 91
  • 1
  • 1
8

Yes you can, with a little change.

function propName(prop, value){
   for(var i in prop) {
       if (prop[i] == value){
            return i;
       }
   }
   return false;
}

Now you can get the value like so:

 var pn = propName(person,person.first_name);
 // pn = "first_name";

Note I am not sure what it can be used for.

Other Note wont work very well with nested objects. but then again, see the first note.

Ibu
  • 42,752
  • 13
  • 76
  • 103
  • 39
    This assumes that no two properties will have the same value in an object. – Sampson Nov 28 '12 at 18:38
  • your function returns 'first_name' if you pass person.first_name but if you use person.address.street returns false. maybe using it recursively? – CLiFoS Nov 28 '12 at 18:54
6

I like one liners, here's a generic solution:

const propName = (obj,type) => Object.keys(obj).find(key => obj[key] === type)

propName(person, person.age)
Nimrod
  • 392
  • 2
  • 9
  • 2
    this will give a wrong result in case the value of 2 keys are the same. ex: `person = {old: 25, name:'John', age: 25}; propName(person, person.age); // return "old"` – Digital Alpha Jan 23 '22 at 17:03
3

Following up on @David Sherret's answer with ES6 it can be made super simple:

propName = f => /\.([^\.;]+);?\s*\}$/.exec(f.toString())[1]
let prop = propName(() => {obj.name}); // myProperty
Nimrod
  • 392
  • 2
  • 9
1

I prefer it clean and simple like this:

var obj = {
  sessionId: 123,
  branchId: 456,
  seasonId: 789
};

var keys = Object.keys(obj);

for (var i in keys) {
  console.log(keys[i]); //output of keys as string
}
Maximilian Fixl
  • 670
  • 6
  • 23
0

You could create a namespacing method for the object. The method will need to mutate the object so that the strings becomes an object instead to hold two properties, a value and a _namespace.

DEMO: http://jsfiddle.net/y4Y8p/1/

var namespace = function(root, name) {
    root._namespace = name;
    function ns(obj) {
        for( var i in obj ) {
            var a = obj._namespace.split('.')
            if ( a.length ) {
                a.push(i);
            }
            if( typeof obj[i] == 'object' ) {
                obj[i]._namespace = a.join('.');
                ns(obj[i]);
                return;
            }
            if( typeof obj[i] == 'string' ) {
                var str = obj[i].toString();
                obj[i] = {
                    _namespace: a.join('.'),
                    value: str
                };
            }
        }
    }
    ns(root);
};

namespace(person, 'person');

console.log(person.address.street._namespace) // person.address.street
console.log(person.address.street.value) // 'Factory 1'

So now you can do:

var o = { message: MSG_ACTION };
o[ person.first_name._namespace ] = person.first_name.value;

extPort.postMessage(o);
David Hellsing
  • 106,495
  • 44
  • 176
  • 212
  • I was fascinated by this answer so I started hacking away at it to try and find a cleaner approach. here is what I've come up with so far, what do you think: http://jsfiddle.net/y4Y8p/17/ – Jason Sperske Nov 28 '12 at 20:38
  • @JasonSperske nice, but it’s not recursive so it only works 3 levels deep: http://jsfiddle.net/y4Y8p/18/ – David Hellsing Nov 28 '12 at 20:42
  • http://jsfiddle.net/y4Y8p/22/ try it now :) There are still larger problems, like it takes all values and casts them to strings, but this is a fun bit of code to play with – Jason Sperske Nov 28 '12 at 20:48
0

I am in same situation.

Here is thy way to get it done using Lodash or UnderScore library, with one limitation of value to be unique:

var myObject = {
'a': 1,
'b': 2,
'c': 3
}
_.findKey(myObject, function( curValue ) { return myObject.a === curValue });

Plain JavaScript

function getPropAsString( source, value ){
    var keys = Object.keys( source );

    var curIndex,
        total,
        foundKey;

    for(curIndex = 0, total = keys.length; curIndex < total; curIndex++){
        var curKey = keys[ curIndex ];
        if ( source[ curKey ] === value ){
            foundKey = curKey;
            break;
        }
    }

    return foundKey;
}

var myObject = {
'a': 1,
'b': 2,
'c': 3
}
getPropAsString( myObject, myObject.a )

But, I would prefer to fix the code as solution. An example:

var myObject = {
'a': {key:'a', value:1},
'b': {key:'b', value:2},
'c': {key:'c', value:3}
}

console.log( myObject.a.key )
Amit Bhagat
  • 4,182
  • 3
  • 23
  • 24
0

I am late to the party but I took a completely different approach, so I will throw in my approach and see what the community thinks.

I used Function.prototype.name to do what I want. my properties are functions that when called return the value of the property, and I can get the name of the property (which is a function) using .name

Here is an example:

person = {
    firstName(){
        return 'John';
    },
    address(){
        return '123 street'
    }
}

person.firstName.name // 'firstName'
person.address.name // 'address'

Note:
you can't easily change the value of a property (e.g firstname) at run time in this case. you would need to create a function (.name would be anonymous in this case) and this function would return a new named function which return the new value:

// note the () at the end     
person.firstName = new Function('', 'return function firstName(){return "johny"}')(); 
person.firstName.name ; // 'firstName' 
person.firstName(); // 'johny'
Moha the almighty camel
  • 4,327
  • 4
  • 30
  • 53
  • @Please_Dont_Bully_Me_SO_Lords, Hence the sentence: "my properties are functions that when called return the value of the property" – Moha the almighty camel Feb 14 '19 at 13:36
  • and that's why I made it clear in my answer: 1- I am taking a different approach, 2- my properties are functions, 3- I have a note about how you should update the value of a property. By no means I am suggesting that this is the best approach, I am just proposing a way to do what OP wants to do. In his example it is a string because he is using plain attributes, in my approach I am wrapping them in functions. OP can use my answer to add wrapper around his attributes. My attributes are functions syntactically, yes. but, semantically, my attributes are the result of the function call, – Moha the almighty camel Feb 14 '19 at 13:56
  • @Please_Dont_Bully_Me_SO_Lords, if you see anyway this answer can be improved, please be my guest. Thank you. – Moha the almighty camel Feb 14 '19 at 13:57
0

Improved Solution for TypeScript using PROXY from Isk1n solution:

//get property name
export function getPropertyName<T>(obj: any): T {
  return new Proxy(obj, {
    get(_, key) {
      return key;
    }
  });
}

and usage:

sampleItem: TestClass = new TestClass();
getPropertyName<TestClass>(this.sampleItem).LayoutVersion
0

Here is a solution that uses Proxy.

selectorToPropertyPath converts any property selector callback into an array of properties.

const target = {};

const handler = (parents = []) => ({
    get: (target, property) => {
        if (property in target) {
            return target[property];
        }
        
        target[property] = new Proxy({
            __path: [...parents, property],
        }, handler([...parents, property]));
        
        return target[property];
    },
});

const proxy = new Proxy(target, handler());

const selectorToPropertyPath = (selector) =>
    selector(proxy).__path;


// Usage:
const accessId = (data) => data.id;
const accessGroupId = (data) => data.group.id;
const accessRandomProperties = (data) => data['one'].two['three'].four['five'];

console.log(selectorToPropertyPath(accessId));
console.log(selectorToPropertyPath(accessGroupId));
console.log(selectorToPropertyPath(accessRandomProperties));


// NOTE: Property `__path` is reserved to store path info. This will return the correct path:
console.log(selectorToPropertyPath(data => data.__path));
// ...however, this will return invalid path:
console.log(selectorToPropertyPath(data => data.__path.__path));

TypeScript implementation:

type Target = {
    [key: string | symbol]: Target;
} & { __path?: (string | symbol)[] };

const target: Target = {};

const handler: (parens?: (string | symbol)[]) => ProxyHandler<typeof target> = (parents = []) => ({
    get: (target, property) => {
        if (property in target) {
            return target[property];
        }

        target[property] = new Proxy({
            __path: [...parents, property],
        } as Target, handler([...parents, property]));

        return target[property];
    },
});

const proxy = new Proxy(target, handler());
hophiphip
  • 1
  • 1
-2

No, it's not possible.

Imagine this:

person.age = 42;
person.favoriteNumber = 42;

var pn = propName(person.age)
// == propName(42)
// == propName(person.favoriteNumber);

The reference to the property name is simply lost in that process.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103