Get object property name as a string
Asked Answered
C

17

109

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

Curvilinear answered 28/11, 2012 at 18:31 Comment(22)
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 indicesChengteh
You need to also pass a reference to the object into the function.Mint
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.Chiffchaff
I understand this can be confusing, but that is exactly what I need. I need the property name as a string toughCurvilinear
so you actually mean var pn = propName("USA");Underwriter
I'd love to help, but jsFiddle is currently unresponsive -.-Mint
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.Curvilinear
You need to explicitly pass them as separate values.Chiffchaff
You want to use the ID to get the prop, or the prop to get the ID?Loehr
@Šime CodePen.io is better than jsFiddle anyway ;)Loehr
You really need to clarify your question, maybe add the html sample. Look at the answers, 4 people have 4 different interpretations!!!Underwriter
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.Beauty
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?Heinie
And jsbin.com/iyabal/2/edit shows the flaw with this approach.Enneahedron
-1 Sorry, your code is really a bad practice. See this example: jsbin.com/iyabal/4/edit ... and en.wikipedia.org/wiki/Nestor_Burma as a reference ;-)Underwriter
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.Curvilinear
possible duplicate of JavaScript: Getting the object's property namePestle
This would be useful instead of passing strings around representing a object property name. You could then have static checking, refactoring or mangling.Norward
automatic name property in a created objectEsquiline
automatic name property for a created object you could have a look at that example.. ;)Esquiline
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.Tantalum
Tell me you want to use typescript without telling me you want to use typescript.Jeramey
E
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.

Enjoyment answered 28/11, 2012 at 18:38 Comment(2)
This assumes that no two properties will have the same value in an object.Macrobiotics
your function returns 'first_name' if you pass person.first_name but if you use person.address.street returns false. maybe using it recursively?Curvilinear
C
42

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.

Cristophercristy answered 9/6, 2016 at 8:33 Comment(2)
this does not answer the question, as it will return an array of the keys. the answer should return one string valueManos
smh... everything triet said but add .join()Capita
V
35

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'
Vauntcourier answered 8/12, 2017 at 4:27 Comment(3)
It is the best solution!Hangnail
If you see warning about "no return value"; here is the modified version: Object.keys(obj).map(k => { res[k] = () => k; return k;});Spaghetti
FYI I've added a TypeScript version below if anyone's looking for one.Compendium
C
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.

Compendium answered 27/3, 2021 at 23:5 Comment(4)
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.Rimarimas
I get an error: TypeError: expression(...) is not a functionHomeland
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 :)Cohlette
@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!);Vauntcourier
R
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.

Repetitious answered 12/9, 2015 at 18:38 Comment(0)
F
10

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);
Farfetched answered 5/6, 2019 at 13:38 Comment(4)
Why does even this work console.log(propName({}).first_name);?Buckles
This fails when properties have same value.Buckles
It's working in JS V8 runtime, eg. substituting same values: person.last_name = person.first_name; and person.address.country = person.first_name; or that substitution using = 'Jack' instead of = person.first_name;Trident
This answer is the best. It works. It's brief and clear. It's performant in speed/operations, in memory, in complexity (no iteration in the source code, no extra generations filtered by yet more operations, no risky exec()s), no extra objects. It uses Proxy for exactly the reflection Proxy is designed to do.Trident
B
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
Bash answered 27/12, 2019 at 8:57 Comment(1)
Do you have a simple example how to use this method?Littleton
E
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.

Enjoyment answered 28/11, 2012 at 18:38 Comment(2)
This assumes that no two properties will have the same value in an object.Macrobiotics
your function returns 'first_name' if you pass person.first_name but if you use person.address.street returns false. maybe using it recursively?Curvilinear
P
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)
Phial answered 1/9, 2019 at 15:3 Comment(1)
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"Manos
P
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
Phial answered 13/1, 2019 at 10:11 Comment(0)
A
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
}
Alkene answered 5/11, 2018 at 15:52 Comment(4)
And how do you get a specific key?Doublespace
Why shoult Object.keys return a specific key? The point from that question was, to get the key of an object as a string!? Or not? I‘m confused now...Alkene
Yes, but you iterate over ALL keys. There is no chance getting a specific property name with your solution.Doublespace
Ah, I got it! No, my solution is only for all keys, you‘re right. I misunderstood the question.Alkene
W
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);
Worley answered 28/11, 2012 at 19:17 Comment(3)
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: jsfiddle.net/y4Y8p/17Adynamia
@JasonSperske nice, but it’s not recursive so it only works 3 levels deep: jsfiddle.net/y4Y8p/18Worley
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 withAdynamia
B
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 )
Boll answered 7/7, 2017 at 11:20 Comment(0)
G
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'
Greenlee answered 5/1, 2019 at 20:26 Comment(3)
@Please_Dont_Bully_Me_SO_Lords, Hence the sentence: "my properties are functions that when called return the value of the property"Greenlee
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,Greenlee
@Please_Dont_Bully_Me_SO_Lords, if you see anyway this answer can be improved, please be my guest. Thank you.Greenlee
S
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
Similar answered 16/12, 2022 at 16:6 Comment(0)
B
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());
Boehmenism answered 24/7, 2023 at 6:25 Comment(0)
A
0

I think you need to solve for each depth of the object.

Here's a solution using the string from Object.keys()

const PersonKeyStrings = Object.keys(Person).reduce((mem, keySt) => 
  (mem[keySt] = keySt, mem), {})

let first_name_str = PersonKeyStrings.first_name
let first_name_value = Person[first_name_str]
Anachronism answered 28/12, 2023 at 17:40 Comment(0)
E
-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.

Enneahedron answered 28/11, 2012 at 18:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.