Related
Given a JavaScript object,
var obj = { a: { b: '1', c: '2' } }
and a string
"a.b"
how can I convert the string to dot notation so I can go
var val = obj.a.b
If the string was just 'a', I could use obj[a]. But this is more complex. I imagine there is some straightforward method, but it escapes me at present.
recent note: While I'm flattered that this answer has gotten many upvotes, I am also somewhat horrified. If one needs to convert dot-notation strings like "x.a.b.c" into references, it could (maybe) be a sign that there is something very wrong going on (unless maybe you're performing some strange deserialization).
That is to say, novices who find their way to this answer must ask themselves the question "why am I doing this?"
It is of course generally fine to do this if your use case is small and you will not run into performance issues, AND you won't need to build upon your abstraction to make it more complicated later. In fact, if this will reduce code complexity and keep things simple, you should probably go ahead and do what OP is asking for. However, if that's not the case, consider if any of these apply:
case 1: As the primary method of working with your data (e.g. as your app's default form of passing objects around and dereferencing them). Like asking "how can I look up a function or variable name from a string".
This is bad programming practice (unnecessary metaprogramming specifically, and kind of violates function side-effect-free coding style, and will have performance hits). Novices who find themselves in this case, should instead consider working with array representations, e.g. ['x','a','b','c'], or even something more direct/simple/straightforward if possible: like not losing track of the references themselves in the first place (most ideal if it's only client-side or only server-side), etc. (A pre-existing unique id would be inelegant to add, but could be used if the spec otherwise requires its existence regardless.)
case 2: Working with serialized data, or data that will be displayed to the user. Like using a date as a string "1999-12-30" rather than a Date object (which can cause timezone bugs or added serialization complexity if not careful). Or you know what you're doing.
This is maybe fine. Be careful that there are no dot strings "." in your sanitized input fragments.
If you find yourself using this answer all the time and converting back and forth between string and array, you may be in the bad case, and should consider an alternative.
Here's an elegant one-liner that's 10x shorter than the other solutions:
function index(obj,i) {return obj[i]}
'a.b.etc'.split('.').reduce(index, obj)
[edit] Or in ECMAScript 6:
'a.b.etc'.split('.').reduce((o,i)=> o[i], obj)
(Not that I think eval always bad like others suggest it is (though it usually is), nevertheless those people will be pleased that this method doesn't use eval. The above will find obj.a.b.etc given obj and the string "a.b.etc".)
In response to those who still are afraid of using reduce despite it being in the ECMA-262 standard (5th edition), here is a two-line recursive implementation:
function multiIndex(obj,is) { // obj,['1','2','3'] -> ((obj['1'])['2'])['3']
return is.length ? multiIndex(obj[is[0]],is.slice(1)) : obj
}
function pathIndex(obj,is) { // obj,'1.2.3' -> multiIndex(obj,['1','2','3'])
return multiIndex(obj,is.split('.'))
}
pathIndex('a.b.etc')
Depending on the optimizations the JS compiler is doing, you may want to make sure any nested functions are not re-defined on every call via the usual methods (placing them in a closure, object, or global namespace).
edit:
To answer an interesting question in the comments:
how would you turn this into a setter as well? Not only returning the values by path, but also setting them if a new value is sent into the function? – Swader Jun 28 at 21:42
(sidenote: sadly can't return an object with a Setter, as that would violate the calling convention; commenter seems to instead be referring to a general setter-style function with side-effects like index(obj,"a.b.etc", value) doing obj.a.b.etc = value.)
The reduce style is not really suitable to that, but we can modify the recursive implementation:
function index(obj,is, value) {
if (typeof is == 'string')
return index(obj,is.split('.'), value);
else if (is.length==1 && value!==undefined)
return obj[is[0]] = value;
else if (is.length==0)
return obj;
else
return index(obj[is[0]],is.slice(1), value);
}
Demo:
> obj = {a:{b:{etc:5}}}
> index(obj,'a.b.etc')
5
> index(obj,['a','b','etc']) #works with both strings and lists
5
> index(obj,'a.b.etc', 123) #setter-mode - third argument (possibly poor form)
123
> index(obj,'a.b.etc')
123
...though personally I'd recommend making a separate function setIndex(...). I would like to end on a side-note that the original poser of the question could (should?) be working with arrays of indices (which they can get from .split), rather than strings; though there's usually nothing wrong with a convenience function.
A commenter asked:
what about arrays? something like "a.b[4].c.d[1][2][3]" ? –AlexS
Javascript is a very weird language; in general objects can only have strings as their property keys, so for example if x was a generic object like x={}, then x[1] would become x["1"]... you read that right... yup...
Javascript Arrays (which are themselves instances of Object) specifically encourage integer keys, even though you could do something like x=[]; x["puppy"]=5;.
But in general (and there are exceptions), x["somestring"]===x.somestring (when it's allowed; you can't do x.123).
(Keep in mind that whatever JS compiler you're using might choose, maybe, to compile these down to saner representations if it can prove it would not violate the spec.)
So the answer to your question would depend on whether you're assuming those objects only accept integers (due to a restriction in your problem domain), or not. Let's assume not. Then a valid expression is a concatenation of a base identifier plus some .identifiers plus some ["stringindex"]s.
Let us ignore for a moment that we can of course do other things legitimately in the grammar like identifier[0xFA7C25DD].asdf[f(4)?.[5]+k][false][null][undefined][NaN]; integers are not (that) 'special'.
Commenter's statement would then be equivalent to a["b"][4]["c"]["d"][1][2][3], though we should probably also support a.b["c\"validjsstringliteral"][3]. You'd have to check the ecmascript grammar section on string literals to see how to parse a valid string literal. Technically you'd also want to check (unlike in my first answer) that a is a valid javascript identifier.
A simple answer to your question though, if your strings don't contain commas or brackets, would be just be to match length 1+ sequences of characters not in the set , or [ or ]:
> "abc[4].c.def[1][2][\"gh\"]".match(/[^\]\[.]+/g)
// ^^^ ^ ^ ^^^ ^ ^ ^^^^^
["abc", "4", "c", "def", "1", "2", ""gh""]
If your strings don't contain escape characters or " characters, and because IdentifierNames are a sublanguage of StringLiterals (I think???) you could first convert your dots to []:
> var R=[], demoString="abc[4].c.def[1][2][\"gh\"]";
> for(var match,matcher=/^([^\.\[]+)|\.([^\.\[]+)|\["([^"]+)"\]|\[(\d+)\]/g;
match=matcher.exec(demoString); ) {
R.push(Array.from(match).slice(1).filter(x=> x!==undefined)[0]);
// extremely bad code because js regexes are weird, don't use this
}
> R
["abc", "4", "c", "def", "1", "2", "gh"]
Of course, always be careful and never trust your data. Some bad ways to do this that might work for some use cases also include:
// hackish/wrongish; preprocess your string into "a.b.4.c.d.1.2.3", e.g.:
> yourstring.replace(/]/g,"").replace(/\[/g,".").split(".")
"a.b.4.c.d.1.2.3" //use code from before
Special 2018 edit:
Let's go full-circle and do the most inefficient, horribly-overmetaprogrammed solution we can come up with... in the interest of syntactical purityhamfistery. With ES6 Proxy objects!... Let's also define some properties which (imho are fine and wonderful but) may break improperly-written libraries. You should perhaps be wary of using this if you care about performance, sanity (yours or others'), your job, etc.
// [1,2,3][-1]==3 (or just use .slice(-1)[0])
if (![1][-1])
Object.defineProperty(Array.prototype, -1, {get() {return this[this.length-1]}}); //credit to caub
// WARNING: THIS XTREME™ RADICAL METHOD IS VERY INEFFICIENT,
// ESPECIALLY IF INDEXING INTO MULTIPLE OBJECTS,
// because you are constantly creating wrapper objects on-the-fly and,
// even worse, going through Proxy i.e. runtime ~reflection, which prevents
// compiler optimization
// Proxy handler to override obj[*]/obj.* and obj[*]=...
var hyperIndexProxyHandler = {
get: function(obj,key, proxy) {
return key.split('.').reduce((o,i)=> o[i], obj);
},
set: function(obj,key,value, proxy) {
var keys = key.split('.');
var beforeLast = keys.slice(0,-1).reduce((o,i)=> o[i], obj);
beforeLast[keys[-1]] = value;
},
has: function(obj,key) {
//etc
}
};
function hyperIndexOf(target) {
return new Proxy(target, hyperIndexProxyHandler);
}
Demo:
var obj = {a:{b:{c:1, d:2}}};
console.log("obj is:", JSON.stringify(obj));
var objHyper = hyperIndexOf(obj);
console.log("(proxy override get) objHyper['a.b.c'] is:", objHyper['a.b.c']);
objHyper['a.b.c'] = 3;
console.log("(proxy override set) objHyper['a.b.c']=3, now obj is:", JSON.stringify(obj));
console.log("(behind the scenes) objHyper is:", objHyper);
if (!({}).H)
Object.defineProperties(Object.prototype, {
H: {
get: function() {
return hyperIndexOf(this); // TODO:cache as a non-enumerable property for efficiency?
}
}
});
console.log("(shortcut) obj.H['a.b.c']=4");
obj.H['a.b.c'] = 4;
console.log("(shortcut) obj.H['a.b.c'] is obj['a']['b']['c'] is", obj.H['a.b.c']);
Output:
obj is: {"a":{"b":{"c":1,"d":2}}}
(proxy override get) objHyper['a.b.c'] is: 1
(proxy override set) objHyper['a.b.c']=3, now obj is: {"a":{"b":{"c":3,"d":2}}}
(behind the scenes) objHyper is: Proxy {a: {…}}
(shortcut) obj.H['a.b.c']=4
(shortcut) obj.H['a.b.c'] is obj['a']['b']['c'] is: 4
inefficient idea: You can modify the above to dispatch based on the input argument; either use the .match(/[^\]\[.]+/g) method to support obj['keys'].like[3]['this'], or if instanceof Array, then just accept an Array as input like keys = ['a','b','c']; obj.H[keys].
Per suggestion that maybe you want to handle undefined indices in a 'softer' NaN-style manner (e.g. index({a:{b:{c:...}}}, 'a.x.c') return undefined rather than uncaught TypeError)...:
This makes sense from the perspective of "we should return undefined rather than throw an error" in the 1-dimensional index situation ({})['e.g.']==undefined, so "we should return undefined rather than throw an error" in the N-dimensional situation.
This does not make sense from the perspective that we are doing x['a']['x']['c'], which would fail with a TypeError in the above example.
That said, you'd make this work by replacing your reducing function with either:
(o,i)=> o===undefined?undefined:o[i], or
(o,i)=> (o||{})[i].
(You can make this more efficient by using a for loop and breaking/returning whenever the subresult you'd next index into is undefined, or using a try-catch if you expect such failures to be sufficiently rare.)
If you can use Lodash, there is a function, which does exactly that:
_.get(object, path, [defaultValue])
var val = _.get(obj, "a.b");
You could use lodash.get
After installing (npm i lodash.get), use it like this:
const get = require('lodash.get');
const myObj = {
user: {
firstName: 'Stacky',
lastName: 'Overflowy',
list: ['zero', 'one', 'two']
},
id: 123
};
console.log(get(myObj, 'user.firstName')); // outputs Stacky
console.log(get(myObj, 'id')); // outputs 123
console.log(get(myObj, 'user.list[1]')); // outputs one
// You can also update values
get(myObj, 'user').firstName = 'John';
A little more involved example with recursion.
function recompose(obj, string) {
var parts = string.split('.');
var newObj = obj[parts[0]];
if (parts[1]) {
parts.splice(0, 1);
var newString = parts.join('.');
return recompose(newObj, newString);
}
return newObj;
}
var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}};
console.log(recompose(obj, 'a.d.a.b')); //blah
2021
You don't need to pull in another dependency every time you wish for new capabilities in your program. Modern JS is very capable and the optional-chaining operator ?. is now widely supported and makes this kind of task easy as heck.
With a single line of code we can write get that takes an input object, t and string path. It works for object and arrays of any nesting level -
const get = (t, path) =>
path.split(".").reduce((r, k) => r?.[k], t)
const mydata =
{ a: { b: [ 0, { c: { d: [ "hello", "world" ] } } ] } }
console.log(get(mydata, "a.b.1.c.d.0"))
console.log(get(mydata, "a.b.1.c.d.1"))
console.log(get(mydata, "a.b.x.y.z"))
"hello"
"world"
undefined
I suggest to split the path and iterate it and reduce the object you have. This proposal works with a default value for missing properties.
const getValue = (object, keys) => keys.split('.').reduce((o, k) => (o || {})[k], object);
console.log(getValue({ a: { b: '1', c: '2' } }, 'a.b'));
console.log(getValue({ a: { b: '1', c: '2' } }, 'foo.bar.baz'));
Many years since the original post.
Now there is a great library called 'object-path'.
https://github.com/mariocasciaro/object-path
Available on NPM and BOWER
https://www.npmjs.com/package/object-path
It's as easy as:
objectPath.get(obj, "a.c.1"); //returns "f"
objectPath.set(obj, "a.j.0.f", "m");
And works for deeply nested properties and arrays.
If you expect to dereference the same path many times, building a function for each dot notation path actually has the best performance by far (expanding on the perf tests James Wilkins linked to in comments above).
var path = 'a.b.x';
var getter = new Function("obj", "return obj." + path + ";");
getter(obj);
Using the Function constructor has some of the same drawbacks as eval() in terms of security and worst-case performance, but IMO it's a badly underused tool for cases where you need a combination of extreme dynamism and high performance. I use this methodology to build array filter functions and call them inside an AngularJS digest loop. My profiles consistently show the array.filter() step taking less than 1ms to dereference and filter about 2000 complex objects, using dynamically-defined paths 3-4 levels deep.
A similar methodology could be used to create setter functions, of course:
var setter = new Function("obj", "newval", "obj." + path + " = newval;");
setter(obj, "some new val");
Other proposals are a little cryptic, so I thought I'd contribute:
Object.prop = function(obj, prop, val){
var props = prop.split('.')
, final = props.pop(), p
while(p = props.shift()){
if (typeof obj[p] === 'undefined')
return undefined;
obj = obj[p]
}
return val ? (obj[final] = val) : obj[final]
}
var obj = { a: { b: '1', c: '2' } }
// get
console.log(Object.prop(obj, 'a.c')) // -> 2
// set
Object.prop(obj, 'a.c', function(){})
console.log(obj) // -> { a: { b: '1', c: [Function] } }
var a = { b: { c: 9 } };
function value(layer, path, value) {
var i = 0,
path = path.split('.');
for (; i < path.length; i++)
if (value != null && i + 1 === path.length)
layer[path[i]] = value;
layer = layer[path[i]];
return layer;
};
value(a, 'b.c'); // 9
value(a, 'b.c', 4);
value(a, 'b.c'); // 4
This is a lot of code when compared to the much simpler eval way of doing it, but like Simon Willison says, you should never use eval.
Also, JSFiddle.
You can use the library available at npm, which simplifies this process. https://www.npmjs.com/package/dot-object
var dot = require('dot-object');
var obj = {
some: {
nested: {
value: 'Hi there!'
}
}
};
var val = dot.pick('some.nested.value', obj);
console.log(val);
// Result: Hi there!
Note if you're already using Lodash you can use the property or get functions:
var obj = { a: { b: '1', c: '2' } };
_.property('a.b')(obj); // => 1
_.get(obj, 'a.b'); // => 1
Underscore.js also has a property function, but it doesn't support dot notation.
I have extended the elegant answer by ninjagecko so that the function handles both dotted and/or array style references, and so that an empty string causes the parent object to be returned.
Here you go:
string_to_ref = function (object, reference) {
function arr_deref(o, ref, i) { return !ref ? o : (o[ref.slice(0, i ? -1 : ref.length)]) }
function dot_deref(o, ref) { return ref.split('[').reduce(arr_deref, o); }
return !reference ? object : reference.split('.').reduce(dot_deref, object);
};
See my working jsFiddle example here: http://jsfiddle.net/sc0ttyd/q7zyd/
You can obtain value of an object member by dot notation with a single line of code:
new Function('_', 'return _.' + path)(obj);
In you case:
var obj = { a: { b: '1', c: '2' } }
var val = new Function('_', 'return _.a.b')(obj);
To make it simple you may write a function like this:
function objGet(obj, path){
return new Function('_', 'return _.' + path)(obj);
}
Explanation:
The Function constructor creates a new Function object. In JavaScript every function is actually a Function object. Syntax to create a function explicitly with Function constructor is:
new Function ([arg1[, arg2[, ...argN]],] functionBody)
where arguments(arg1 to argN) must be a string that corresponds to a valid javaScript identifier and functionBody is a string containing the javaScript statements comprising the function definition.
In our case we take the advantage of string function body to retrieve object member with dot notation.
Hope it helps.
var find = function(root, path) {
var segments = path.split('.'),
cursor = root,
target;
for (var i = 0; i < segments.length; ++i) {
target = cursor[segments[i]];
if (typeof target == "undefined") return void 0;
cursor = target;
}
return cursor;
};
var obj = { a: { b: '1', c: '2' } }
find(obj, "a.b"); // 1
var set = function (root, path, value) {
var segments = path.split('.'),
cursor = root,
target;
for (var i = 0; i < segments.length - 1; ++i) {
cursor = cursor[segments[i]] || { };
}
cursor[segments[segments.length - 1]] = value;
};
set(obj, "a.k", function () { console.log("hello world"); });
find(obj, "a.k")(); // hello world
Use this function:
function dotToObject(data) {
function index(parent, key, value) {
const [mainKey, ...children] = key.split(".");
parent[mainKey] = parent[mainKey] || {};
if (children.length === 1) {
parent[mainKey][children[0]] = value;
} else {
index(parent[mainKey], children.join("."), value);
}
}
const result = Object.entries(data).reduce((acc, [key, value]) => {
if (key.includes(".")) {
index(acc, key, value);
} else {
acc[key] = value;
}
return acc;
}, {});
return result;
}
module.exports = { dotToObject };
Ex:
const user = {
id: 1,
name: 'My name',
'address.zipCode': '123',
'address.name': 'Some name',
'address.something.id': 1,
}
const mappedUser = dotToObject(user)
console.log(JSON.stringify(mappedUser, null, 2))
Output:
{
"id": 1,
"name": "My name",
"address": {
"zipCode": "123",
"name": "Some name",
"something": {
"id": 1
}
}
}
using Array Reduce function will get/set based on path provided.
I tested it with a.b.c and a.b.2.c {a:{b:[0,1,{c:7}]}} and its works for both getting key or mutating object to set value
function setOrGet(obj, path=[], newValue){
const l = typeof path === 'string' ? path.split('.') : path;
return l.reduce((carry,item, idx)=>{
const leaf = carry[item];
// is this last item in path ? cool lets set/get value
if( l.length-idx===1) {
// mutate object if newValue is set;
carry[item] = newValue===undefined ? leaf : newValue;
// return value if its a get/object if it was a set
return newValue===undefined ? leaf : obj ;
}
carry[item] = leaf || {}; // mutate if key not an object;
return carry[item]; // return object ref: to continue reduction;
}, obj)
}
console.log(
setOrGet({a: {b:1}},'a.b') === 1 ||
'Test Case: Direct read failed'
)
console.log(
setOrGet({a: {b:1}},'a.c',22).a.c===22 ||
'Test Case: Direct set failed'
)
console.log(
setOrGet({a: {b:[1,2]}},'a.b.1',22).a.b[1]===22 ||
'Test Case: Direct set on array failed'
)
console.log(
setOrGet({a: {b:{c: {e:1} }}},'a.b.c.e',22).a.b.c. e===22 ||
'Test Case: deep get failed'
)
// failed !. Thats your homework :)
console.log(
setOrGet({a: {b:{c: {e:[1,2,3,4,5]} }}},'a.b.c.e.3 ',22)
)
my personal recommendation.
do not use such a thing unless there is no other way!
i saw many examples people use it for translations for example from json; so you see function like locale('app.homepage.welcome') . this is just bad. if you already have data in an object/json; and you know path.. then just use it directly example locale().app.homepage.welcome by changing you function to return object you get typesafe, with autocomplete, less prone to typo's ..
I copied the following from Ricardo Tomasi's answer and modified to also create sub-objects that don't yet exist as necessary. It's a little less efficient (more ifs and creating of empty objects), but should be pretty good.
Also, it'll allow us to do Object.prop(obj, 'a.b', false) where we couldn't before. Unfortunately, it still won't let us assign undefined...Not sure how to go about that one yet.
/**
* Object.prop()
*
* Allows dot-notation access to object properties for both getting and setting.
*
* #param {Object} obj The object we're getting from or setting
* #param {string} prop The dot-notated string defining the property location
* #param {mixed} val For setting only; the value to set
*/
Object.prop = function(obj, prop, val){
var props = prop.split('.'),
final = props.pop(),
p;
for (var i = 0; i < props.length; i++) {
p = props[i];
if (typeof obj[p] === 'undefined') {
// If we're setting
if (typeof val !== 'undefined') {
// If we're not at the end of the props, keep adding new empty objects
if (i != props.length)
obj[p] = {};
}
else
return undefined;
}
obj = obj[p]
}
return typeof val !== "undefined" ? (obj[final] = val) : obj[final]
}
Few years later, I found this that handles scope and array. e.g. a['b']["c"].d.etc
function getScopedObj(scope, str) {
let obj=scope, arr;
try {
arr = str.split(/[\[\]\.]/) // split by [,],.
.filter(el => el) // filter out empty one
.map(el => el.replace(/^['"]+|['"]+$/g, '')); // remove string quotation
arr.forEach(el => obj = obj[el])
} catch(e) {
obj = undefined;
}
return obj;
}
window.a = {b: {c: {d: {etc: 'success'}}}}
getScopedObj(window, `a.b.c.d.etc`) // success
getScopedObj(window, `a['b']["c"].d.etc`) // success
getScopedObj(window, `a['INVALID']["c"].d.etc`) // undefined
If you wish to convert any object that contains dot notation keys into an arrayed version of those keys you can use this.
This will convert something like
{
name: 'Andy',
brothers.0: 'Bob'
brothers.1: 'Steve'
brothers.2: 'Jack'
sisters.0: 'Sally'
}
to
{
name: 'Andy',
brothers: ['Bob', 'Steve', 'Jack']
sisters: ['Sally']
}
convertDotNotationToArray(objectWithDotNotation) {
Object.entries(objectWithDotNotation).forEach(([key, val]) => {
// Is the key of dot notation
if (key.includes('.')) {
const [name, index] = key.split('.');
// If you have not created an array version, create one
if (!objectWithDotNotation[name]) {
objectWithDotNotation[name] = new Array();
}
// Save the value in the newly created array at the specific index
objectWithDotNotation[name][index] = val;
// Delete the current dot notation key val
delete objectWithDotNotation[key];
}
});
}
If you want to convert a string dot notation into an object, I've made a handy little helper than can turn a string like a.b.c.d with a value of e with dotPathToObject("a.b.c.d", "value") returning this:
{
"a": {
"b": {
"c": {
"d": "value"
}
}
}
}
https://gist.github.com/ahallora/9731d73efb15bd3d3db647efa3389c12
Solution:
function deepFind(key, data){
return key.split('.').reduce((ob,i)=> ob?.[i], data)
}
Usage:
const obj = {
company: "Pet Shop",
person: {
name: "John"
},
animal: {
name: "Lucky"
}
}
const company = deepFind("company", obj)
const personName = deepFind("person.name", obj)
const animalName = deepFind("animal.name", obj)
Here is my implementation
Implementation 1
Object.prototype.access = function() {
var ele = this[arguments[0]];
if(arguments.length === 1) return ele;
return ele.access.apply(ele, [].slice.call(arguments, 1));
}
Implementation 2 (using array reduce instead of slice)
Object.prototype.access = function() {
var self = this;
return [].reduce.call(arguments,function(prev,cur) {
return prev[cur];
}, self);
}
Examples:
var myobj = {'a':{'b':{'c':{'d':'abcd','e':[11,22,33]}}}};
myobj.access('a','b','c'); // returns: {'d':'abcd', e:[0,1,2,3]}
myobj.a.b.access('c','d'); // returns: 'abcd'
myobj.access('a','b','c','e',0); // returns: 11
it can also handle objects inside arrays as for
var myobj2 = {'a': {'b':[{'c':'ab0c'},{'d':'ab1d'}]}}
myobj2.access('a','b','1','d'); // returns: 'ab1d'
I used this code in my project
const getValue = (obj, arrPath) => (
arrPath.reduce((x, y) => {
if (y in x) return x[y]
return {}
}, obj)
)
Usage:
const obj = { id: { user: { local: 104 } } }
const path = [ 'id', 'user', 'local' ]
getValue(obj, path) // return 104
Using object-scan seems a bit overkill, but you can simply do
// const objectScan = require('object-scan');
const get = (obj, p) => objectScan([p], { abort: true, rtn: 'value' })(obj);
const obj = { a: { b: '1', c: '2' } };
console.log(get(obj, 'a.b'));
// => 1
console.log(get(obj, '*.c'));
// => 2
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.7.1"></script>
Disclaimer: I'm the author of object-scan
There are a lot more advanced examples in the readme.
This is one of those cases, where you ask 10 developers and you get 10 answers.
Below is my [simplified] solution for OP, using dynamic programming.
The idea is that you would pass an existing DTO object that you wish to UPDATE. This makes the method most useful in the case where you have a form with several input elements having name attributes set with dot (fluent) syntax.
Example use:
<input type="text" name="person.contact.firstName" />
Code snippet:
const setFluently = (obj, path, value) => {
if (typeof path === "string") {
return setFluently(obj, path.split("."), value);
}
if (path.length <= 1) {
obj[path[0]] = value;
return obj;
}
const key = path[0];
obj[key] = setFluently(obj[key] ? obj[key] : {}, path.slice(1), value);
return obj;
};
const origObj = {
a: {
b: "1",
c: "2"
}
};
setFluently(origObj, "a.b", "3");
setFluently(origObj, "a.c", "4");
console.log(JSON.stringify(origObj, null, 3));
function at(obj, path, val = undefined) {
// If path is an Array,
if (Array.isArray(path)) {
// it returns the mapped array for each result of the path
return path.map((path) => at(obj, path, val));
}
// Uniting several RegExps into one
const rx = new RegExp(
[
/(?:^(?:\.\s*)?([_a-zA-Z][_a-zA-Z0-9]*))/,
/(?:^\[\s*(\d+)\s*\])/,
/(?:^\[\s*'([^']*(?:\\'[^']*)*)'\s*\])/,
/(?:^\[\s*"([^"]*(?:\\"[^"]*)*)"\s*\])/,
/(?:^\[\s*`([^`]*(?:\\`[^`]*)*)`\s*\])/,
]
.map((r) => r.source)
.join("|")
);
let rm;
while (rm = rx.exec(path.trim())) {
// Matched resource
let [rf, rp] = rm.filter(Boolean);
// If no one matches found,
if (!rm[1] && !rm[2]) {
// it will replace escape-chars
rp = rp.replace(/\\(.)/g, "$1");
}
// If the new value is set,
if ("undefined" != typeof val && path.length == rf.length) {
// assign a value to the object property and return it
return (obj[rp] = val);
}
// Going one step deeper
obj = obj[rp];
// Removing a step from the path
path = path.substr(rf.length).trim();
}
if (path) {
throw new SyntaxError();
}
return obj;
}
// Test object schema
let o = { a: { b: [ [ { c: { d: { '"e"': { f: { g: "xxx" } } } } } ] ] } };
// Print source object
console.log(JSON.stringify(o));
// Set value
console.log(at(o, '.a["b"][0][0].c[`d`]["\\"e\\""][\'f\']["g"]', "zzz"));
// Get value
console.log(at(o, '.a["b"][0][0].c[`d`]["\\"e\\""][\'f\']["g"]'));
// Print result object
console.log(JSON.stringify(o));
Here is my code without using eval. It’s easy to understand too.
function value(obj, props) {
if (!props)
return obj;
var propsArr = props.split('.');
var prop = propsArr.splice(0, 1);
return value(obj[prop], propsArr.join('.'));
}
var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}};
console.log(value(obj, 'a.d.a.b')); // Returns blah
Yes, extending base prototypes is not usually good idea but, if you keep all extensions in one place, they might be useful.
So, here is my way to do this.
Object.defineProperty(Object.prototype, "getNestedProperty", {
value : function (propertyName) {
var result = this;
var arr = propertyName.split(".");
while (arr.length && result) {
result = result[arr.shift()];
}
return result;
},
enumerable: false
});
Now you will be able to get nested property everywhere without importing module with function or copy/pasting function.
Example:
{a:{b:11}}.getNestedProperty('a.b'); // Returns 11
The Next.js extension broke Mongoose in my project. Also I've read that it might break jQuery. So, never do it in the Next.js way:
Object.prototype.getNestedProperty = function (propertyName) {
var result = this;
var arr = propertyName.split(".");
while (arr.length && result) {
result = result[arr.shift()];
}
return result;
};
This is my extended solution proposed by ninjagecko.
For me, simple string notation was not enough, so the below version supports things like:
index(obj, 'data.accounts[0].address[0].postcode');
/**
* Get object by index
* #supported
* - arrays supported
* - array indexes supported
* #not-supported
* - multiple arrays
* #issues:
* index(myAccount, 'accounts[0].address[0].id') - works fine
* index(myAccount, 'accounts[].address[0].id') - doesnt work
* #Example:
* index(obj, 'data.accounts[].id') => returns array of id's
* index(obj, 'data.accounts[0].id') => returns id of 0 element from array
* index(obj, 'data.accounts[0].addresses.list[0].id') => error
* #param obj
* #param path
* #returns {any}
*/
var index = function(obj, path, isArray?, arrIndex?){
// is an array
if(typeof isArray === 'undefined') isArray = false;
// array index,
// if null, will take all indexes
if(typeof arrIndex === 'undefined') arrIndex = null;
var _arrIndex = null;
var reduceArrayTag = function(i, subArrIndex){
return i.replace(/(\[)([\d]{0,})(\])/, (i) => {
var tmp = i.match(/(\[)([\d]{0,})(\])/);
isArray = true;
if(subArrIndex){
_arrIndex = (tmp[2] !== '') ? tmp[2] : null;
}else{
arrIndex = (tmp[2] !== '') ? tmp[2] : null;
}
return '';
});
}
function byIndex(obj, i) {
// if is an array
if(isArray){
isArray = false;
i = reduceArrayTag(i, true);
// if array index is null,
// return an array of with values from every index
if(!arrIndex){
var arrValues = [];
_.forEach(obj, (el) => {
arrValues.push(index(el, i, isArray, arrIndex));
})
return arrValues;
}
// if array index is specified
var value = obj[arrIndex][i];
if(isArray){
arrIndex = _arrIndex;
}else{
arrIndex = null;
}
return value;
}else{
// remove [] from notation,
// if [] has been removed, check the index of array
i = reduceArrayTag(i, false);
return obj[i]
}
}
// reduce with the byIndex method
return path.split('.').reduce(byIndex, obj)
}
Given a JavaScript object,
var obj = { a: { b: '1', c: '2' } }
and a string
"a.b"
how can I convert the string to dot notation so I can go
var val = obj.a.b
If the string was just 'a', I could use obj[a]. But this is more complex. I imagine there is some straightforward method, but it escapes me at present.
recent note: While I'm flattered that this answer has gotten many upvotes, I am also somewhat horrified. If one needs to convert dot-notation strings like "x.a.b.c" into references, it could (maybe) be a sign that there is something very wrong going on (unless maybe you're performing some strange deserialization).
That is to say, novices who find their way to this answer must ask themselves the question "why am I doing this?"
It is of course generally fine to do this if your use case is small and you will not run into performance issues, AND you won't need to build upon your abstraction to make it more complicated later. In fact, if this will reduce code complexity and keep things simple, you should probably go ahead and do what OP is asking for. However, if that's not the case, consider if any of these apply:
case 1: As the primary method of working with your data (e.g. as your app's default form of passing objects around and dereferencing them). Like asking "how can I look up a function or variable name from a string".
This is bad programming practice (unnecessary metaprogramming specifically, and kind of violates function side-effect-free coding style, and will have performance hits). Novices who find themselves in this case, should instead consider working with array representations, e.g. ['x','a','b','c'], or even something more direct/simple/straightforward if possible: like not losing track of the references themselves in the first place (most ideal if it's only client-side or only server-side), etc. (A pre-existing unique id would be inelegant to add, but could be used if the spec otherwise requires its existence regardless.)
case 2: Working with serialized data, or data that will be displayed to the user. Like using a date as a string "1999-12-30" rather than a Date object (which can cause timezone bugs or added serialization complexity if not careful). Or you know what you're doing.
This is maybe fine. Be careful that there are no dot strings "." in your sanitized input fragments.
If you find yourself using this answer all the time and converting back and forth between string and array, you may be in the bad case, and should consider an alternative.
Here's an elegant one-liner that's 10x shorter than the other solutions:
function index(obj,i) {return obj[i]}
'a.b.etc'.split('.').reduce(index, obj)
[edit] Or in ECMAScript 6:
'a.b.etc'.split('.').reduce((o,i)=> o[i], obj)
(Not that I think eval always bad like others suggest it is (though it usually is), nevertheless those people will be pleased that this method doesn't use eval. The above will find obj.a.b.etc given obj and the string "a.b.etc".)
In response to those who still are afraid of using reduce despite it being in the ECMA-262 standard (5th edition), here is a two-line recursive implementation:
function multiIndex(obj,is) { // obj,['1','2','3'] -> ((obj['1'])['2'])['3']
return is.length ? multiIndex(obj[is[0]],is.slice(1)) : obj
}
function pathIndex(obj,is) { // obj,'1.2.3' -> multiIndex(obj,['1','2','3'])
return multiIndex(obj,is.split('.'))
}
pathIndex('a.b.etc')
Depending on the optimizations the JS compiler is doing, you may want to make sure any nested functions are not re-defined on every call via the usual methods (placing them in a closure, object, or global namespace).
edit:
To answer an interesting question in the comments:
how would you turn this into a setter as well? Not only returning the values by path, but also setting them if a new value is sent into the function? – Swader Jun 28 at 21:42
(sidenote: sadly can't return an object with a Setter, as that would violate the calling convention; commenter seems to instead be referring to a general setter-style function with side-effects like index(obj,"a.b.etc", value) doing obj.a.b.etc = value.)
The reduce style is not really suitable to that, but we can modify the recursive implementation:
function index(obj,is, value) {
if (typeof is == 'string')
return index(obj,is.split('.'), value);
else if (is.length==1 && value!==undefined)
return obj[is[0]] = value;
else if (is.length==0)
return obj;
else
return index(obj[is[0]],is.slice(1), value);
}
Demo:
> obj = {a:{b:{etc:5}}}
> index(obj,'a.b.etc')
5
> index(obj,['a','b','etc']) #works with both strings and lists
5
> index(obj,'a.b.etc', 123) #setter-mode - third argument (possibly poor form)
123
> index(obj,'a.b.etc')
123
...though personally I'd recommend making a separate function setIndex(...). I would like to end on a side-note that the original poser of the question could (should?) be working with arrays of indices (which they can get from .split), rather than strings; though there's usually nothing wrong with a convenience function.
A commenter asked:
what about arrays? something like "a.b[4].c.d[1][2][3]" ? –AlexS
Javascript is a very weird language; in general objects can only have strings as their property keys, so for example if x was a generic object like x={}, then x[1] would become x["1"]... you read that right... yup...
Javascript Arrays (which are themselves instances of Object) specifically encourage integer keys, even though you could do something like x=[]; x["puppy"]=5;.
But in general (and there are exceptions), x["somestring"]===x.somestring (when it's allowed; you can't do x.123).
(Keep in mind that whatever JS compiler you're using might choose, maybe, to compile these down to saner representations if it can prove it would not violate the spec.)
So the answer to your question would depend on whether you're assuming those objects only accept integers (due to a restriction in your problem domain), or not. Let's assume not. Then a valid expression is a concatenation of a base identifier plus some .identifiers plus some ["stringindex"]s.
Let us ignore for a moment that we can of course do other things legitimately in the grammar like identifier[0xFA7C25DD].asdf[f(4)?.[5]+k][false][null][undefined][NaN]; integers are not (that) 'special'.
Commenter's statement would then be equivalent to a["b"][4]["c"]["d"][1][2][3], though we should probably also support a.b["c\"validjsstringliteral"][3]. You'd have to check the ecmascript grammar section on string literals to see how to parse a valid string literal. Technically you'd also want to check (unlike in my first answer) that a is a valid javascript identifier.
A simple answer to your question though, if your strings don't contain commas or brackets, would be just be to match length 1+ sequences of characters not in the set , or [ or ]:
> "abc[4].c.def[1][2][\"gh\"]".match(/[^\]\[.]+/g)
// ^^^ ^ ^ ^^^ ^ ^ ^^^^^
["abc", "4", "c", "def", "1", "2", ""gh""]
If your strings don't contain escape characters or " characters, and because IdentifierNames are a sublanguage of StringLiterals (I think???) you could first convert your dots to []:
> var R=[], demoString="abc[4].c.def[1][2][\"gh\"]";
> for(var match,matcher=/^([^\.\[]+)|\.([^\.\[]+)|\["([^"]+)"\]|\[(\d+)\]/g;
match=matcher.exec(demoString); ) {
R.push(Array.from(match).slice(1).filter(x=> x!==undefined)[0]);
// extremely bad code because js regexes are weird, don't use this
}
> R
["abc", "4", "c", "def", "1", "2", "gh"]
Of course, always be careful and never trust your data. Some bad ways to do this that might work for some use cases also include:
// hackish/wrongish; preprocess your string into "a.b.4.c.d.1.2.3", e.g.:
> yourstring.replace(/]/g,"").replace(/\[/g,".").split(".")
"a.b.4.c.d.1.2.3" //use code from before
Special 2018 edit:
Let's go full-circle and do the most inefficient, horribly-overmetaprogrammed solution we can come up with... in the interest of syntactical purityhamfistery. With ES6 Proxy objects!... Let's also define some properties which (imho are fine and wonderful but) may break improperly-written libraries. You should perhaps be wary of using this if you care about performance, sanity (yours or others'), your job, etc.
// [1,2,3][-1]==3 (or just use .slice(-1)[0])
if (![1][-1])
Object.defineProperty(Array.prototype, -1, {get() {return this[this.length-1]}}); //credit to caub
// WARNING: THIS XTREME™ RADICAL METHOD IS VERY INEFFICIENT,
// ESPECIALLY IF INDEXING INTO MULTIPLE OBJECTS,
// because you are constantly creating wrapper objects on-the-fly and,
// even worse, going through Proxy i.e. runtime ~reflection, which prevents
// compiler optimization
// Proxy handler to override obj[*]/obj.* and obj[*]=...
var hyperIndexProxyHandler = {
get: function(obj,key, proxy) {
return key.split('.').reduce((o,i)=> o[i], obj);
},
set: function(obj,key,value, proxy) {
var keys = key.split('.');
var beforeLast = keys.slice(0,-1).reduce((o,i)=> o[i], obj);
beforeLast[keys[-1]] = value;
},
has: function(obj,key) {
//etc
}
};
function hyperIndexOf(target) {
return new Proxy(target, hyperIndexProxyHandler);
}
Demo:
var obj = {a:{b:{c:1, d:2}}};
console.log("obj is:", JSON.stringify(obj));
var objHyper = hyperIndexOf(obj);
console.log("(proxy override get) objHyper['a.b.c'] is:", objHyper['a.b.c']);
objHyper['a.b.c'] = 3;
console.log("(proxy override set) objHyper['a.b.c']=3, now obj is:", JSON.stringify(obj));
console.log("(behind the scenes) objHyper is:", objHyper);
if (!({}).H)
Object.defineProperties(Object.prototype, {
H: {
get: function() {
return hyperIndexOf(this); // TODO:cache as a non-enumerable property for efficiency?
}
}
});
console.log("(shortcut) obj.H['a.b.c']=4");
obj.H['a.b.c'] = 4;
console.log("(shortcut) obj.H['a.b.c'] is obj['a']['b']['c'] is", obj.H['a.b.c']);
Output:
obj is: {"a":{"b":{"c":1,"d":2}}}
(proxy override get) objHyper['a.b.c'] is: 1
(proxy override set) objHyper['a.b.c']=3, now obj is: {"a":{"b":{"c":3,"d":2}}}
(behind the scenes) objHyper is: Proxy {a: {…}}
(shortcut) obj.H['a.b.c']=4
(shortcut) obj.H['a.b.c'] is obj['a']['b']['c'] is: 4
inefficient idea: You can modify the above to dispatch based on the input argument; either use the .match(/[^\]\[.]+/g) method to support obj['keys'].like[3]['this'], or if instanceof Array, then just accept an Array as input like keys = ['a','b','c']; obj.H[keys].
Per suggestion that maybe you want to handle undefined indices in a 'softer' NaN-style manner (e.g. index({a:{b:{c:...}}}, 'a.x.c') return undefined rather than uncaught TypeError)...:
This makes sense from the perspective of "we should return undefined rather than throw an error" in the 1-dimensional index situation ({})['e.g.']==undefined, so "we should return undefined rather than throw an error" in the N-dimensional situation.
This does not make sense from the perspective that we are doing x['a']['x']['c'], which would fail with a TypeError in the above example.
That said, you'd make this work by replacing your reducing function with either:
(o,i)=> o===undefined?undefined:o[i], or
(o,i)=> (o||{})[i].
(You can make this more efficient by using a for loop and breaking/returning whenever the subresult you'd next index into is undefined, or using a try-catch if you expect such failures to be sufficiently rare.)
If you can use Lodash, there is a function, which does exactly that:
_.get(object, path, [defaultValue])
var val = _.get(obj, "a.b");
You could use lodash.get
After installing (npm i lodash.get), use it like this:
const get = require('lodash.get');
const myObj = {
user: {
firstName: 'Stacky',
lastName: 'Overflowy',
list: ['zero', 'one', 'two']
},
id: 123
};
console.log(get(myObj, 'user.firstName')); // outputs Stacky
console.log(get(myObj, 'id')); // outputs 123
console.log(get(myObj, 'user.list[1]')); // outputs one
// You can also update values
get(myObj, 'user').firstName = 'John';
A little more involved example with recursion.
function recompose(obj, string) {
var parts = string.split('.');
var newObj = obj[parts[0]];
if (parts[1]) {
parts.splice(0, 1);
var newString = parts.join('.');
return recompose(newObj, newString);
}
return newObj;
}
var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}};
console.log(recompose(obj, 'a.d.a.b')); //blah
2021
You don't need to pull in another dependency every time you wish for new capabilities in your program. Modern JS is very capable and the optional-chaining operator ?. is now widely supported and makes this kind of task easy as heck.
With a single line of code we can write get that takes an input object, t and string path. It works for object and arrays of any nesting level -
const get = (t, path) =>
path.split(".").reduce((r, k) => r?.[k], t)
const mydata =
{ a: { b: [ 0, { c: { d: [ "hello", "world" ] } } ] } }
console.log(get(mydata, "a.b.1.c.d.0"))
console.log(get(mydata, "a.b.1.c.d.1"))
console.log(get(mydata, "a.b.x.y.z"))
"hello"
"world"
undefined
I suggest to split the path and iterate it and reduce the object you have. This proposal works with a default value for missing properties.
const getValue = (object, keys) => keys.split('.').reduce((o, k) => (o || {})[k], object);
console.log(getValue({ a: { b: '1', c: '2' } }, 'a.b'));
console.log(getValue({ a: { b: '1', c: '2' } }, 'foo.bar.baz'));
Many years since the original post.
Now there is a great library called 'object-path'.
https://github.com/mariocasciaro/object-path
Available on NPM and BOWER
https://www.npmjs.com/package/object-path
It's as easy as:
objectPath.get(obj, "a.c.1"); //returns "f"
objectPath.set(obj, "a.j.0.f", "m");
And works for deeply nested properties and arrays.
If you expect to dereference the same path many times, building a function for each dot notation path actually has the best performance by far (expanding on the perf tests James Wilkins linked to in comments above).
var path = 'a.b.x';
var getter = new Function("obj", "return obj." + path + ";");
getter(obj);
Using the Function constructor has some of the same drawbacks as eval() in terms of security and worst-case performance, but IMO it's a badly underused tool for cases where you need a combination of extreme dynamism and high performance. I use this methodology to build array filter functions and call them inside an AngularJS digest loop. My profiles consistently show the array.filter() step taking less than 1ms to dereference and filter about 2000 complex objects, using dynamically-defined paths 3-4 levels deep.
A similar methodology could be used to create setter functions, of course:
var setter = new Function("obj", "newval", "obj." + path + " = newval;");
setter(obj, "some new val");
Other proposals are a little cryptic, so I thought I'd contribute:
Object.prop = function(obj, prop, val){
var props = prop.split('.')
, final = props.pop(), p
while(p = props.shift()){
if (typeof obj[p] === 'undefined')
return undefined;
obj = obj[p]
}
return val ? (obj[final] = val) : obj[final]
}
var obj = { a: { b: '1', c: '2' } }
// get
console.log(Object.prop(obj, 'a.c')) // -> 2
// set
Object.prop(obj, 'a.c', function(){})
console.log(obj) // -> { a: { b: '1', c: [Function] } }
var a = { b: { c: 9 } };
function value(layer, path, value) {
var i = 0,
path = path.split('.');
for (; i < path.length; i++)
if (value != null && i + 1 === path.length)
layer[path[i]] = value;
layer = layer[path[i]];
return layer;
};
value(a, 'b.c'); // 9
value(a, 'b.c', 4);
value(a, 'b.c'); // 4
This is a lot of code when compared to the much simpler eval way of doing it, but like Simon Willison says, you should never use eval.
Also, JSFiddle.
You can use the library available at npm, which simplifies this process. https://www.npmjs.com/package/dot-object
var dot = require('dot-object');
var obj = {
some: {
nested: {
value: 'Hi there!'
}
}
};
var val = dot.pick('some.nested.value', obj);
console.log(val);
// Result: Hi there!
Note if you're already using Lodash you can use the property or get functions:
var obj = { a: { b: '1', c: '2' } };
_.property('a.b')(obj); // => 1
_.get(obj, 'a.b'); // => 1
Underscore.js also has a property function, but it doesn't support dot notation.
I have extended the elegant answer by ninjagecko so that the function handles both dotted and/or array style references, and so that an empty string causes the parent object to be returned.
Here you go:
string_to_ref = function (object, reference) {
function arr_deref(o, ref, i) { return !ref ? o : (o[ref.slice(0, i ? -1 : ref.length)]) }
function dot_deref(o, ref) { return ref.split('[').reduce(arr_deref, o); }
return !reference ? object : reference.split('.').reduce(dot_deref, object);
};
See my working jsFiddle example here: http://jsfiddle.net/sc0ttyd/q7zyd/
You can obtain value of an object member by dot notation with a single line of code:
new Function('_', 'return _.' + path)(obj);
In you case:
var obj = { a: { b: '1', c: '2' } }
var val = new Function('_', 'return _.a.b')(obj);
To make it simple you may write a function like this:
function objGet(obj, path){
return new Function('_', 'return _.' + path)(obj);
}
Explanation:
The Function constructor creates a new Function object. In JavaScript every function is actually a Function object. Syntax to create a function explicitly with Function constructor is:
new Function ([arg1[, arg2[, ...argN]],] functionBody)
where arguments(arg1 to argN) must be a string that corresponds to a valid javaScript identifier and functionBody is a string containing the javaScript statements comprising the function definition.
In our case we take the advantage of string function body to retrieve object member with dot notation.
Hope it helps.
var find = function(root, path) {
var segments = path.split('.'),
cursor = root,
target;
for (var i = 0; i < segments.length; ++i) {
target = cursor[segments[i]];
if (typeof target == "undefined") return void 0;
cursor = target;
}
return cursor;
};
var obj = { a: { b: '1', c: '2' } }
find(obj, "a.b"); // 1
var set = function (root, path, value) {
var segments = path.split('.'),
cursor = root,
target;
for (var i = 0; i < segments.length - 1; ++i) {
cursor = cursor[segments[i]] || { };
}
cursor[segments[segments.length - 1]] = value;
};
set(obj, "a.k", function () { console.log("hello world"); });
find(obj, "a.k")(); // hello world
Use this function:
function dotToObject(data) {
function index(parent, key, value) {
const [mainKey, ...children] = key.split(".");
parent[mainKey] = parent[mainKey] || {};
if (children.length === 1) {
parent[mainKey][children[0]] = value;
} else {
index(parent[mainKey], children.join("."), value);
}
}
const result = Object.entries(data).reduce((acc, [key, value]) => {
if (key.includes(".")) {
index(acc, key, value);
} else {
acc[key] = value;
}
return acc;
}, {});
return result;
}
module.exports = { dotToObject };
Ex:
const user = {
id: 1,
name: 'My name',
'address.zipCode': '123',
'address.name': 'Some name',
'address.something.id': 1,
}
const mappedUser = dotToObject(user)
console.log(JSON.stringify(mappedUser, null, 2))
Output:
{
"id": 1,
"name": "My name",
"address": {
"zipCode": "123",
"name": "Some name",
"something": {
"id": 1
}
}
}
using Array Reduce function will get/set based on path provided.
I tested it with a.b.c and a.b.2.c {a:{b:[0,1,{c:7}]}} and its works for both getting key or mutating object to set value
function setOrGet(obj, path=[], newValue){
const l = typeof path === 'string' ? path.split('.') : path;
return l.reduce((carry,item, idx)=>{
const leaf = carry[item];
// is this last item in path ? cool lets set/get value
if( l.length-idx===1) {
// mutate object if newValue is set;
carry[item] = newValue===undefined ? leaf : newValue;
// return value if its a get/object if it was a set
return newValue===undefined ? leaf : obj ;
}
carry[item] = leaf || {}; // mutate if key not an object;
return carry[item]; // return object ref: to continue reduction;
}, obj)
}
console.log(
setOrGet({a: {b:1}},'a.b') === 1 ||
'Test Case: Direct read failed'
)
console.log(
setOrGet({a: {b:1}},'a.c',22).a.c===22 ||
'Test Case: Direct set failed'
)
console.log(
setOrGet({a: {b:[1,2]}},'a.b.1',22).a.b[1]===22 ||
'Test Case: Direct set on array failed'
)
console.log(
setOrGet({a: {b:{c: {e:1} }}},'a.b.c.e',22).a.b.c. e===22 ||
'Test Case: deep get failed'
)
// failed !. Thats your homework :)
console.log(
setOrGet({a: {b:{c: {e:[1,2,3,4,5]} }}},'a.b.c.e.3 ',22)
)
my personal recommendation.
do not use such a thing unless there is no other way!
i saw many examples people use it for translations for example from json; so you see function like locale('app.homepage.welcome') . this is just bad. if you already have data in an object/json; and you know path.. then just use it directly example locale().app.homepage.welcome by changing you function to return object you get typesafe, with autocomplete, less prone to typo's ..
I copied the following from Ricardo Tomasi's answer and modified to also create sub-objects that don't yet exist as necessary. It's a little less efficient (more ifs and creating of empty objects), but should be pretty good.
Also, it'll allow us to do Object.prop(obj, 'a.b', false) where we couldn't before. Unfortunately, it still won't let us assign undefined...Not sure how to go about that one yet.
/**
* Object.prop()
*
* Allows dot-notation access to object properties for both getting and setting.
*
* #param {Object} obj The object we're getting from or setting
* #param {string} prop The dot-notated string defining the property location
* #param {mixed} val For setting only; the value to set
*/
Object.prop = function(obj, prop, val){
var props = prop.split('.'),
final = props.pop(),
p;
for (var i = 0; i < props.length; i++) {
p = props[i];
if (typeof obj[p] === 'undefined') {
// If we're setting
if (typeof val !== 'undefined') {
// If we're not at the end of the props, keep adding new empty objects
if (i != props.length)
obj[p] = {};
}
else
return undefined;
}
obj = obj[p]
}
return typeof val !== "undefined" ? (obj[final] = val) : obj[final]
}
Few years later, I found this that handles scope and array. e.g. a['b']["c"].d.etc
function getScopedObj(scope, str) {
let obj=scope, arr;
try {
arr = str.split(/[\[\]\.]/) // split by [,],.
.filter(el => el) // filter out empty one
.map(el => el.replace(/^['"]+|['"]+$/g, '')); // remove string quotation
arr.forEach(el => obj = obj[el])
} catch(e) {
obj = undefined;
}
return obj;
}
window.a = {b: {c: {d: {etc: 'success'}}}}
getScopedObj(window, `a.b.c.d.etc`) // success
getScopedObj(window, `a['b']["c"].d.etc`) // success
getScopedObj(window, `a['INVALID']["c"].d.etc`) // undefined
If you wish to convert any object that contains dot notation keys into an arrayed version of those keys you can use this.
This will convert something like
{
name: 'Andy',
brothers.0: 'Bob'
brothers.1: 'Steve'
brothers.2: 'Jack'
sisters.0: 'Sally'
}
to
{
name: 'Andy',
brothers: ['Bob', 'Steve', 'Jack']
sisters: ['Sally']
}
convertDotNotationToArray(objectWithDotNotation) {
Object.entries(objectWithDotNotation).forEach(([key, val]) => {
// Is the key of dot notation
if (key.includes('.')) {
const [name, index] = key.split('.');
// If you have not created an array version, create one
if (!objectWithDotNotation[name]) {
objectWithDotNotation[name] = new Array();
}
// Save the value in the newly created array at the specific index
objectWithDotNotation[name][index] = val;
// Delete the current dot notation key val
delete objectWithDotNotation[key];
}
});
}
If you want to convert a string dot notation into an object, I've made a handy little helper than can turn a string like a.b.c.d with a value of e with dotPathToObject("a.b.c.d", "value") returning this:
{
"a": {
"b": {
"c": {
"d": "value"
}
}
}
}
https://gist.github.com/ahallora/9731d73efb15bd3d3db647efa3389c12
Solution:
function deepFind(key, data){
return key.split('.').reduce((ob,i)=> ob?.[i], data)
}
Usage:
const obj = {
company: "Pet Shop",
person: {
name: "John"
},
animal: {
name: "Lucky"
}
}
const company = deepFind("company", obj)
const personName = deepFind("person.name", obj)
const animalName = deepFind("animal.name", obj)
Here is my implementation
Implementation 1
Object.prototype.access = function() {
var ele = this[arguments[0]];
if(arguments.length === 1) return ele;
return ele.access.apply(ele, [].slice.call(arguments, 1));
}
Implementation 2 (using array reduce instead of slice)
Object.prototype.access = function() {
var self = this;
return [].reduce.call(arguments,function(prev,cur) {
return prev[cur];
}, self);
}
Examples:
var myobj = {'a':{'b':{'c':{'d':'abcd','e':[11,22,33]}}}};
myobj.access('a','b','c'); // returns: {'d':'abcd', e:[0,1,2,3]}
myobj.a.b.access('c','d'); // returns: 'abcd'
myobj.access('a','b','c','e',0); // returns: 11
it can also handle objects inside arrays as for
var myobj2 = {'a': {'b':[{'c':'ab0c'},{'d':'ab1d'}]}}
myobj2.access('a','b','1','d'); // returns: 'ab1d'
I used this code in my project
const getValue = (obj, arrPath) => (
arrPath.reduce((x, y) => {
if (y in x) return x[y]
return {}
}, obj)
)
Usage:
const obj = { id: { user: { local: 104 } } }
const path = [ 'id', 'user', 'local' ]
getValue(obj, path) // return 104
Using object-scan seems a bit overkill, but you can simply do
// const objectScan = require('object-scan');
const get = (obj, p) => objectScan([p], { abort: true, rtn: 'value' })(obj);
const obj = { a: { b: '1', c: '2' } };
console.log(get(obj, 'a.b'));
// => 1
console.log(get(obj, '*.c'));
// => 2
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.7.1"></script>
Disclaimer: I'm the author of object-scan
There are a lot more advanced examples in the readme.
This is one of those cases, where you ask 10 developers and you get 10 answers.
Below is my [simplified] solution for OP, using dynamic programming.
The idea is that you would pass an existing DTO object that you wish to UPDATE. This makes the method most useful in the case where you have a form with several input elements having name attributes set with dot (fluent) syntax.
Example use:
<input type="text" name="person.contact.firstName" />
Code snippet:
const setFluently = (obj, path, value) => {
if (typeof path === "string") {
return setFluently(obj, path.split("."), value);
}
if (path.length <= 1) {
obj[path[0]] = value;
return obj;
}
const key = path[0];
obj[key] = setFluently(obj[key] ? obj[key] : {}, path.slice(1), value);
return obj;
};
const origObj = {
a: {
b: "1",
c: "2"
}
};
setFluently(origObj, "a.b", "3");
setFluently(origObj, "a.c", "4");
console.log(JSON.stringify(origObj, null, 3));
function at(obj, path, val = undefined) {
// If path is an Array,
if (Array.isArray(path)) {
// it returns the mapped array for each result of the path
return path.map((path) => at(obj, path, val));
}
// Uniting several RegExps into one
const rx = new RegExp(
[
/(?:^(?:\.\s*)?([_a-zA-Z][_a-zA-Z0-9]*))/,
/(?:^\[\s*(\d+)\s*\])/,
/(?:^\[\s*'([^']*(?:\\'[^']*)*)'\s*\])/,
/(?:^\[\s*"([^"]*(?:\\"[^"]*)*)"\s*\])/,
/(?:^\[\s*`([^`]*(?:\\`[^`]*)*)`\s*\])/,
]
.map((r) => r.source)
.join("|")
);
let rm;
while (rm = rx.exec(path.trim())) {
// Matched resource
let [rf, rp] = rm.filter(Boolean);
// If no one matches found,
if (!rm[1] && !rm[2]) {
// it will replace escape-chars
rp = rp.replace(/\\(.)/g, "$1");
}
// If the new value is set,
if ("undefined" != typeof val && path.length == rf.length) {
// assign a value to the object property and return it
return (obj[rp] = val);
}
// Going one step deeper
obj = obj[rp];
// Removing a step from the path
path = path.substr(rf.length).trim();
}
if (path) {
throw new SyntaxError();
}
return obj;
}
// Test object schema
let o = { a: { b: [ [ { c: { d: { '"e"': { f: { g: "xxx" } } } } } ] ] } };
// Print source object
console.log(JSON.stringify(o));
// Set value
console.log(at(o, '.a["b"][0][0].c[`d`]["\\"e\\""][\'f\']["g"]', "zzz"));
// Get value
console.log(at(o, '.a["b"][0][0].c[`d`]["\\"e\\""][\'f\']["g"]'));
// Print result object
console.log(JSON.stringify(o));
Here is my code without using eval. It’s easy to understand too.
function value(obj, props) {
if (!props)
return obj;
var propsArr = props.split('.');
var prop = propsArr.splice(0, 1);
return value(obj[prop], propsArr.join('.'));
}
var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}};
console.log(value(obj, 'a.d.a.b')); // Returns blah
Yes, extending base prototypes is not usually good idea but, if you keep all extensions in one place, they might be useful.
So, here is my way to do this.
Object.defineProperty(Object.prototype, "getNestedProperty", {
value : function (propertyName) {
var result = this;
var arr = propertyName.split(".");
while (arr.length && result) {
result = result[arr.shift()];
}
return result;
},
enumerable: false
});
Now you will be able to get nested property everywhere without importing module with function or copy/pasting function.
Example:
{a:{b:11}}.getNestedProperty('a.b'); // Returns 11
The Next.js extension broke Mongoose in my project. Also I've read that it might break jQuery. So, never do it in the Next.js way:
Object.prototype.getNestedProperty = function (propertyName) {
var result = this;
var arr = propertyName.split(".");
while (arr.length && result) {
result = result[arr.shift()];
}
return result;
};
This is my extended solution proposed by ninjagecko.
For me, simple string notation was not enough, so the below version supports things like:
index(obj, 'data.accounts[0].address[0].postcode');
/**
* Get object by index
* #supported
* - arrays supported
* - array indexes supported
* #not-supported
* - multiple arrays
* #issues:
* index(myAccount, 'accounts[0].address[0].id') - works fine
* index(myAccount, 'accounts[].address[0].id') - doesnt work
* #Example:
* index(obj, 'data.accounts[].id') => returns array of id's
* index(obj, 'data.accounts[0].id') => returns id of 0 element from array
* index(obj, 'data.accounts[0].addresses.list[0].id') => error
* #param obj
* #param path
* #returns {any}
*/
var index = function(obj, path, isArray?, arrIndex?){
// is an array
if(typeof isArray === 'undefined') isArray = false;
// array index,
// if null, will take all indexes
if(typeof arrIndex === 'undefined') arrIndex = null;
var _arrIndex = null;
var reduceArrayTag = function(i, subArrIndex){
return i.replace(/(\[)([\d]{0,})(\])/, (i) => {
var tmp = i.match(/(\[)([\d]{0,})(\])/);
isArray = true;
if(subArrIndex){
_arrIndex = (tmp[2] !== '') ? tmp[2] : null;
}else{
arrIndex = (tmp[2] !== '') ? tmp[2] : null;
}
return '';
});
}
function byIndex(obj, i) {
// if is an array
if(isArray){
isArray = false;
i = reduceArrayTag(i, true);
// if array index is null,
// return an array of with values from every index
if(!arrIndex){
var arrValues = [];
_.forEach(obj, (el) => {
arrValues.push(index(el, i, isArray, arrIndex));
})
return arrValues;
}
// if array index is specified
var value = obj[arrIndex][i];
if(isArray){
arrIndex = _arrIndex;
}else{
arrIndex = null;
}
return value;
}else{
// remove [] from notation,
// if [] has been removed, check the index of array
i = reduceArrayTag(i, false);
return obj[i]
}
}
// reduce with the byIndex method
return path.split('.').reduce(byIndex, obj)
}
How do I display the content of a JavaScript object in a string format like when we alert a variable?
The same formatted way I want to display an object.
Use native JSON.stringify method.
Works with nested objects and all major browsers support this method.
str = JSON.stringify(obj);
str = JSON.stringify(obj, null, 4); // (Optional) beautiful indented output.
console.log(str); // Logs output to dev tools console.
alert(str); // Displays output using window.alert()
Link to Mozilla API Reference and other examples.
obj = JSON.parse(str); // Reverses above operation (Just in case if needed.)
Use a custom JSON.stringify replacer if you
encounter this Javascript error
"Uncaught TypeError: Converting circular structure to JSON"
If you want to print the object for debugging purposes, use the code:
var obj = {
prop1: 'prop1Value',
prop2: 'prop2Value',
child: {
childProp1: 'childProp1Value',
},
}
console.log(obj)
will display:
Note: you must only log the object. For example, this won't work:
console.log('My object : ' + obj)
Note ': You can also use a comma in the log method, then the first line of the output will be the string and after that, the object will be rendered:
console.log('My object: ', obj);
var output = '';
for (var property in object) {
output += property + ': ' + object[property]+'; ';
}
alert(output);
console.dir(object):
Displays an interactive listing of the properties of a specified JavaScript object. This listing lets you use disclosure triangles to examine the contents of child objects.
Note that the console.dir() feature is non-standard. See MDN Web Docs
Try this:
console.log(JSON.stringify(obj))
This will print the stringify version of object. So instead of [object] as an output you will get the content of object.
Well, Firefox (thanks to #Bojangles for detailed information) has Object.toSource() method which prints objects as JSON and function(){}.
That's enough for most debugging purposes, I guess.
If you want to use alert, to print your object, you can do this:
alert("myObject is " + myObject.toSource());
It should print each property and its corresponding value in string format.
If you would like to see data in tabular format you can use:
console.table(obj);
Table can be sorted if you click on the table column.
You can also select what columns to show:
console.table(obj, ['firstName', 'lastName']);
You can find more information about console.table here
Function:
var print = function(o){
var str='';
for(var p in o){
if(typeof o[p] == 'string'){
str+= p + ': ' + o[p]+'; </br>';
}else{
str+= p + ': { </br>' + print(o[p]) + '}';
}
}
return str;
}
Usage:
var myObject = {
name: 'Wilson Page',
contact: {
email: 'wilson#hotmail.com',
tel: '123456789'
}
}
$('body').append( print(myObject) );
Example:
http://jsfiddle.net/WilsonPage/6eqMn/
In NodeJS you can print an object by using util.inspect(obj). Be sure to state the depth or you'll only have a shallow print of the object.
Simply use
JSON.stringify(obj)
Example
var args_string = JSON.stringify(obj);
console.log(args_string);
Or
alert(args_string);
Also, note in javascript functions are considered as objects.
As an extra note :
Actually you can assign new property like this and access it console.log or display it in alert
foo.moo = "stackoverflow";
console.log(foo.moo);
alert(foo.moo);
To print the full object with Node.js with colors as a bonus:
console.dir(object, {depth: null, colors: true})
Colors are of course optional, 'depth: null' will print the full object.
The options don't seem to be supported in browsers.
References:
https://developer.mozilla.org/en-US/docs/Web/API/Console/dir
https://nodejs.org/api/console.html#console_console_dir_obj_options
NB:
In these examples, yourObj defines the object you want to examine.
First off my least favorite yet most utilized way of displaying an object:
This is the defacto way of showing the contents of an object
console.log(yourObj)
will produce something like :
I think the best solution is to look through the Objects Keys, and then through the Objects Values if you really want to see what the object holds...
console.log(Object.keys(yourObj));
console.log(Object.values(yourObj));
It will output something like :
(pictured above: the keys/values stored in the object)
There is also this new option if you're using ECMAScript 2016 or newer:
Object.keys(yourObj).forEach(e => console.log(`key=${e} value=${yourObj[e]}`));
This will produce neat output :
The solution mentioned in a previous answer: console.log(yourObj) displays too many parameters and is not the most user friendly way to display the data you want. That is why I recommend logging keys and then values separately.
Next up :
console.table(yourObj)
Someone in an earlier comment suggested this one, however it never worked for me. If it does work for someone else on a different browser or something, then kudos! Ill still put the code here for reference!
Will output something like this to the console :
Here's a way to do it:
console.log("%o", obj);
Use this:
console.log('print object: ' + JSON.stringify(session));
As it was said before best and most simply way i found was
var getPrintObject=function(object)
{
return JSON.stringify(object);
}
(This has been added to my library at GitHub)
Reinventing the wheel here! None of these solutions worked for my situation. So, I quickly doctored up wilsonpage's answer. This one is not for printing to screen (via console, or textfield or whatever). It does work fine in those situations and works just fine as the OP requested, for alert. Many answers here do not address using alert as the OP requested. Anyhow, It is, however, formatted for data transport. This version seems to return a very similar result as toSource(). I've not tested against JSON.stringify, but I assume this is about the same thing. This version is more like a poly-fil so that you can use it in any environment. The result of this function is a valid Javascript object declaration.
I wouldn't doubt if something like this was already on SO somewhere, but it was just shorter to make it than to spend a while searching past answers. And since this question was my top hit on google when I started searching about this; I figured putting it here might help others.
Anyhow, the result from this function will be a string representation of your object, even if your object has embedded objects and arrays, and even if those objects or arrays have even further embedded objects and arrays. (I heard you like to drink? So, I pimped your car with a cooler. And then, I pimped your cooler with a cooler. So, your cooler can drink, while your being cool.)
Arrays are stored with [] instead of {} and thus dont have key/value pairs, just values. Like regular arrays. Therefore, they get created like arrays do.
Also, all string (including key names) are quoted, this is not necessary unless those strings have special characters (like a space or a slash). But, I didn't feel like detecting this just to remove some quotes that would otherwise still work fine.
This resulting string can then be used with eval or just dumping it into a var thru string manipulation. Thus, re-creating your object again, from text.
function ObjToSource(o){
if (!o) return 'null';
var k="",na=typeof(o.length)=="undefined"?1:0,str="";
for(var p in o){
if (na) k = "'"+p+ "':";
if (typeof o[p] == "string") str += k + "'" + o[p]+"',";
else if (typeof o[p] == "object") str += k + ObjToSource(o[p])+",";
else str += k + o[p] + ",";
}
if (na) return "{"+str.slice(0,-1)+"}";
else return "["+str.slice(0,-1)+"]";
}
Let me know if I messed it all up, works fine in my testing. Also, the only way I could think of to detect type array was to check for the presence of length. Because Javascript really stores arrays as objects, I cant actually check for type array (there is no such type!). If anyone else knows a better way, I would love to hear it. Because, if your object also has a property named length then this function will mistakenly treat it as an array.
EDIT: Added check for null valued objects. Thanks Brock Adams
EDIT: Below is the fixed function to be able to print infinitely recursive objects. This does not print the same as toSource from FF because toSource will print the infinite recursion one time, where as, this function will kill it immediately. This function runs slower than the one above, so I'm adding it here instead of editing the above function, as its only needed if you plan to pass objects that link back to themselves, somewhere.
const ObjToSource=(o)=> {
if (!o) return null;
let str="",na=0,k,p;
if (typeof(o) == "object") {
if (!ObjToSource.check) ObjToSource.check = new Array();
for (k=ObjToSource.check.length;na<k;na++) if (ObjToSource.check[na]==o) return '{}';
ObjToSource.check.push(o);
}
k="",na=typeof(o.length)=="undefined"?1:0;
for(p in o){
if (na) k = "'"+p+"':";
if (typeof o[p] == "string") str += k+"'"+o[p]+"',";
else if (typeof o[p] == "object") str += k+ObjToSource(o[p])+",";
else str += k+o[p]+",";
}
if (typeof(o) == "object") ObjToSource.check.pop();
if (na) return "{"+str.slice(0,-1)+"}";
else return "["+str.slice(0,-1)+"]";
}
Test:
var test1 = new Object();
test1.foo = 1;
test1.bar = 2;
var testobject = new Object();
testobject.run = 1;
testobject.fast = null;
testobject.loop = testobject;
testobject.dup = test1;
console.log(ObjToSource(testobject));
console.log(testobject.toSource());
Result:
{'run':1,'fast':null,'loop':{},'dup':{'foo':1,'bar':2}}
({run:1, fast:null, loop:{run:1, fast:null, loop:{}, dup:{foo:1, bar:2}}, dup:{foo:1, bar:2}})
NOTE: Trying to print document.body is a terrible example. For one, FF just prints an empty object string when using toSource. And when using the function above, FF crashes on SecurityError: The operation is insecure.. And Chrome will crash on Uncaught RangeError: Maximum call stack size exceeded. Clearly, document.body was not meant to be converted to string. Because its either too large, or against security policy to access certain properties. Unless, I messed something up here, do tell!
If you would like to print the object of its full length, can use
console.log(require('util').inspect(obj, {showHidden: false, depth: null})
If you want to print the object by converting it to the string then
console.log(JSON.stringify(obj));
I needed a way to recursively print the object, which pagewil's answer provided (Thanks!). I updated it a little bit to include a way to print up to a certain level, and to add spacing so that it is properly indented based on the current level that we are in so that it is more readable.
// Recursive print of object
var print = function( o, maxLevel, level ) {
if ( typeof level == "undefined" ) {
level = 0;
}
if ( typeof level == "undefined" ) {
maxLevel = 0;
}
var str = '';
// Remove this if you don't want the pre tag, but make sure to remove
// the close pre tag on the bottom as well
if ( level == 0 ) {
str = '<pre>';
}
var levelStr = '';
for ( var x = 0; x < level; x++ ) {
levelStr += ' ';
}
if ( maxLevel != 0 && level >= maxLevel ) {
str += levelStr + '...</br>';
return str;
}
for ( var p in o ) {
if ( typeof o[p] == 'string' ) {
str += levelStr +
p + ': ' + o[p] + ' </br>';
} else {
str += levelStr +
p + ': { </br>' + print( o[p], maxLevel, level + 1 ) + levelStr + '}</br>';
}
}
// Remove this if you don't want the pre tag, but make sure to remove
// the open pre tag on the top as well
if ( level == 0 ) {
str += '</pre>';
}
return str;
};
Usage:
var pagewilsObject = {
name: 'Wilson Page',
contact: {
email: 'wilson#hotmail.com',
tel: '123456789'
}
}
// Recursive of whole object
$('body').append( print(pagewilsObject) );
// Recursive of myObject up to 1 level, will only show name
// and that there is a contact object
$('body').append( print(pagewilsObject, 1) );
You can also use ES6 template literal concept to display the content of a JavaScript object in a string format.
alert(`${JSON.stringify(obj)}`);
const obj = {
"name" : "John Doe",
"habbits": "Nothing",
};
alert(`${JSON.stringify(obj)}`);
I always use console.log("object will be: ", obj, obj1).
this way I don't need to do the workaround with stringify with JSON.
All the properties of the object will be expanded nicely.
Another way of displaying objects within the console is with JSON.stringify. Checkout the below example:
var gandalf = {
"real name": "Gandalf",
"age (est)": 11000,
"race": "Maia",
"haveRetirementPlan": true,
"aliases": [
"Greyhame",
"Stormcrow",
"Mithrandir",
"Gandalf the Grey",
"Gandalf the White"
]
};
//to console log object, we cannot use console.log("Object gandalf: " + gandalf);
console.log("Object gandalf: ");
//this will show object gandalf ONLY in Google Chrome NOT in IE
console.log(gandalf);
//this will show object gandalf IN ALL BROWSERS!
console.log(JSON.stringify(gandalf));
//this will show object gandalf IN ALL BROWSERS! with beautiful indent
console.log(JSON.stringify(gandalf, null, 4));
Javascript Function
<script type="text/javascript">
function print_r(theObj){
if(theObj.constructor == Array || theObj.constructor == Object){
document.write("<ul>")
for(var p in theObj){
if(theObj[p].constructor == Array || theObj[p].constructor == Object){
document.write("<li>["+p+"] => "+typeof(theObj)+"</li>");
document.write("<ul>")
print_r(theObj[p]);
document.write("</ul>")
} else {
document.write("<li>["+p+"] => "+theObj[p]+"</li>");
}
}
document.write("</ul>")
}
}
</script>
Printing Object
<script type="text/javascript">
print_r(JAVACRIPT_ARRAY_OR_OBJECT);
</script>
via print_r in Javascript
var list = function(object) {
for(var key in object) {
console.log(key);
}
}
where object is your object
or you can use this in chrome dev tools, "console" tab:
console.log(object);
Assume object obj = {0:'John', 1:'Foo', 2:'Bar'}
Print object's content
for (var i in obj){
console.log(obj[i], i);
}
Console output (Chrome DevTools) :
John 0
Foo 1
Bar 2
Hope that helps!
I prefer using console.table for getting clear object format, so imagine you have this object:
const obj = {name: 'Alireza', family: 'Dezfoolian', gender: 'male', netWorth: "$0"};
And you will you see a neat and readable table like this below:
Circular references solution
To make string without redundant information from object which contains duplicate references (references to same object in many places) including circular references, use JSON.stringify with replacer (presented in snippet) as follows
let s = JSON.stringify(obj, refReplacer(), 4);
function refReplacer() {
let m = new Map(), v= new Map(), init = null;
return function(field, value) {
let p= m.get(this) + (Array.isArray(this) ? `[${field}]` : '.' + field);
let isComplex= value===Object(value)
if (isComplex) m.set(value, p);
let pp = v.get(value)||'';
let path = p.replace(/undefined\.\.?/,'');
let val = pp ? `#REF:${pp[0]=='[' ? '$':'$.'}${pp}` : value;
!init ? (init=value) : (val===init ? val="#REF:$" : 0);
if(!pp && isComplex) v.set(value, path);
return val;
}
}
// ---------------
// TEST
// ---------------
// gen obj with duplicate references
let a = { a1: 1, a2: 2 };
let b = { b1: 3, b2: "4" };
let obj = { o1: { o2: a }, b, a }; // duplicate reference
a.a3 = [1,2,b]; // circular reference
b.b3 = a; // circular reference
let s = JSON.stringify(obj, refReplacer(), 4);
console.log(s);
alert(s);
This solution based on this (more info there) create JSONPath like path for each object value and if same object occurs twice (or more) it uses reference with this path to reference that object e.g. #REF:$.bar.arr[3].foo (where $ means main object) instead 'render' whole object (which is less redundant)
BONUS: inversion
function parseRefJSON(json) {
let objToPath = new Map();
let pathToObj = new Map();
let o = JSON.parse(json);
let traverse = (parent, field) => {
let obj = parent;
let path = '#REF:$';
if (field !== undefined) {
obj = parent[field];
path = objToPath.get(parent) + (Array.isArray(parent) ? `[${field}]` : `${field?'.'+field:''}`);
}
objToPath.set(obj, path);
pathToObj.set(path, obj);
let ref = pathToObj.get(obj);
if (ref) parent[field] = ref;
for (let f in obj) if (obj === Object(obj)) traverse(obj, f);
}
traverse(o);
return o;
}
// ------------
// TEST
// ------------
let s = `{
"o1": {
"o2": {
"a1": 1,
"a2": 2,
"a3": [
1,
2,
{
"b1": 3,
"b2": "4",
"b3": "#REF:$.o1.o2"
}
]
}
},
"b": "#REF:$.o1.o2.a3[2]",
"a": "#REF:$.o1.o2"
}`;
console.log('Open Chrome console to see nested fields');
let obj = parseRefJSON(s);
console.log(obj);
A little helper function I always use in my projects for simple, speedy debugging via the console.
Inspiration taken from Laravel.
/**
* #param variable mixed The var to log to the console
* #param varName string Optional, will appear as a label before the var
*/
function dd(variable, varName) {
var varNameOutput;
varName = varName || '';
varNameOutput = varName ? varName + ':' : '';
console.warn(varNameOutput, variable, ' (' + (typeof variable) + ')');
}
Usage
dd(123.55); outputs:
var obj = {field1: 'xyz', field2: 2016};
dd(obj, 'My Cool Obj');
The console.log() does a great job of debugging objects, but if you are looking to print the object to the page content, here's the simplest way that I've come up with to mimic the functionality of PHP's print_r(). A lot these other answers want to reinvent the wheel, but between JavaScript's JSON.stringify() and HTML's <pre> tag, you get exactly what you are looking for.
var obj = { name: 'The Name', contact: { email: 'thename#gmail.com', tel: '123456789' }};
$('body').append('<pre>'+JSON.stringify(obj, null, 4)+'</pre>');
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
i used pagewil's print method, and it worked very nicely.
here is my slightly extended version with (sloppy) indents and distinct prop/ob delimiters:
var print = function(obj, delp, delo, ind){
delp = delp!=null ? delp : "\t"; // property delimeter
delo = delo!=null ? delo : "\n"; // object delimeter
ind = ind!=null ? ind : " "; // indent; ind+ind geometric addition not great for deep objects
var str='';
for(var prop in obj){
if(typeof obj[prop] == 'string' || typeof obj[prop] == 'number'){
var q = typeof obj[prop] == 'string' ? "" : ""; // make this "'" to quote strings
str += ind + prop + ': ' + q + obj[prop] + q + '; ' + delp;
}else{
str += ind + prop + ': {'+ delp + print(obj[prop],delp,delo,ind+ind) + ind + '}' + delo;
}
}
return str;
};
Given a JavaScript object,
var obj = { a: { b: '1', c: '2' } }
and a string
"a.b"
how can I convert the string to dot notation so I can go
var val = obj.a.b
If the string was just 'a', I could use obj[a]. But this is more complex. I imagine there is some straightforward method, but it escapes me at present.
recent note: While I'm flattered that this answer has gotten many upvotes, I am also somewhat horrified. If one needs to convert dot-notation strings like "x.a.b.c" into references, it could (maybe) be a sign that there is something very wrong going on (unless maybe you're performing some strange deserialization).
That is to say, novices who find their way to this answer must ask themselves the question "why am I doing this?"
It is of course generally fine to do this if your use case is small and you will not run into performance issues, AND you won't need to build upon your abstraction to make it more complicated later. In fact, if this will reduce code complexity and keep things simple, you should probably go ahead and do what OP is asking for. However, if that's not the case, consider if any of these apply:
case 1: As the primary method of working with your data (e.g. as your app's default form of passing objects around and dereferencing them). Like asking "how can I look up a function or variable name from a string".
This is bad programming practice (unnecessary metaprogramming specifically, and kind of violates function side-effect-free coding style, and will have performance hits). Novices who find themselves in this case, should instead consider working with array representations, e.g. ['x','a','b','c'], or even something more direct/simple/straightforward if possible: like not losing track of the references themselves in the first place (most ideal if it's only client-side or only server-side), etc. (A pre-existing unique id would be inelegant to add, but could be used if the spec otherwise requires its existence regardless.)
case 2: Working with serialized data, or data that will be displayed to the user. Like using a date as a string "1999-12-30" rather than a Date object (which can cause timezone bugs or added serialization complexity if not careful). Or you know what you're doing.
This is maybe fine. Be careful that there are no dot strings "." in your sanitized input fragments.
If you find yourself using this answer all the time and converting back and forth between string and array, you may be in the bad case, and should consider an alternative.
Here's an elegant one-liner that's 10x shorter than the other solutions:
function index(obj,i) {return obj[i]}
'a.b.etc'.split('.').reduce(index, obj)
[edit] Or in ECMAScript 6:
'a.b.etc'.split('.').reduce((o,i)=> o[i], obj)
(Not that I think eval always bad like others suggest it is (though it usually is), nevertheless those people will be pleased that this method doesn't use eval. The above will find obj.a.b.etc given obj and the string "a.b.etc".)
In response to those who still are afraid of using reduce despite it being in the ECMA-262 standard (5th edition), here is a two-line recursive implementation:
function multiIndex(obj,is) { // obj,['1','2','3'] -> ((obj['1'])['2'])['3']
return is.length ? multiIndex(obj[is[0]],is.slice(1)) : obj
}
function pathIndex(obj,is) { // obj,'1.2.3' -> multiIndex(obj,['1','2','3'])
return multiIndex(obj,is.split('.'))
}
pathIndex('a.b.etc')
Depending on the optimizations the JS compiler is doing, you may want to make sure any nested functions are not re-defined on every call via the usual methods (placing them in a closure, object, or global namespace).
edit:
To answer an interesting question in the comments:
how would you turn this into a setter as well? Not only returning the values by path, but also setting them if a new value is sent into the function? – Swader Jun 28 at 21:42
(sidenote: sadly can't return an object with a Setter, as that would violate the calling convention; commenter seems to instead be referring to a general setter-style function with side-effects like index(obj,"a.b.etc", value) doing obj.a.b.etc = value.)
The reduce style is not really suitable to that, but we can modify the recursive implementation:
function index(obj,is, value) {
if (typeof is == 'string')
return index(obj,is.split('.'), value);
else if (is.length==1 && value!==undefined)
return obj[is[0]] = value;
else if (is.length==0)
return obj;
else
return index(obj[is[0]],is.slice(1), value);
}
Demo:
> obj = {a:{b:{etc:5}}}
> index(obj,'a.b.etc')
5
> index(obj,['a','b','etc']) #works with both strings and lists
5
> index(obj,'a.b.etc', 123) #setter-mode - third argument (possibly poor form)
123
> index(obj,'a.b.etc')
123
...though personally I'd recommend making a separate function setIndex(...). I would like to end on a side-note that the original poser of the question could (should?) be working with arrays of indices (which they can get from .split), rather than strings; though there's usually nothing wrong with a convenience function.
A commenter asked:
what about arrays? something like "a.b[4].c.d[1][2][3]" ? –AlexS
Javascript is a very weird language; in general objects can only have strings as their property keys, so for example if x was a generic object like x={}, then x[1] would become x["1"]... you read that right... yup...
Javascript Arrays (which are themselves instances of Object) specifically encourage integer keys, even though you could do something like x=[]; x["puppy"]=5;.
But in general (and there are exceptions), x["somestring"]===x.somestring (when it's allowed; you can't do x.123).
(Keep in mind that whatever JS compiler you're using might choose, maybe, to compile these down to saner representations if it can prove it would not violate the spec.)
So the answer to your question would depend on whether you're assuming those objects only accept integers (due to a restriction in your problem domain), or not. Let's assume not. Then a valid expression is a concatenation of a base identifier plus some .identifiers plus some ["stringindex"]s.
Let us ignore for a moment that we can of course do other things legitimately in the grammar like identifier[0xFA7C25DD].asdf[f(4)?.[5]+k][false][null][undefined][NaN]; integers are not (that) 'special'.
Commenter's statement would then be equivalent to a["b"][4]["c"]["d"][1][2][3], though we should probably also support a.b["c\"validjsstringliteral"][3]. You'd have to check the ecmascript grammar section on string literals to see how to parse a valid string literal. Technically you'd also want to check (unlike in my first answer) that a is a valid javascript identifier.
A simple answer to your question though, if your strings don't contain commas or brackets, would be just be to match length 1+ sequences of characters not in the set , or [ or ]:
> "abc[4].c.def[1][2][\"gh\"]".match(/[^\]\[.]+/g)
// ^^^ ^ ^ ^^^ ^ ^ ^^^^^
["abc", "4", "c", "def", "1", "2", ""gh""]
If your strings don't contain escape characters or " characters, and because IdentifierNames are a sublanguage of StringLiterals (I think???) you could first convert your dots to []:
> var R=[], demoString="abc[4].c.def[1][2][\"gh\"]";
> for(var match,matcher=/^([^\.\[]+)|\.([^\.\[]+)|\["([^"]+)"\]|\[(\d+)\]/g;
match=matcher.exec(demoString); ) {
R.push(Array.from(match).slice(1).filter(x=> x!==undefined)[0]);
// extremely bad code because js regexes are weird, don't use this
}
> R
["abc", "4", "c", "def", "1", "2", "gh"]
Of course, always be careful and never trust your data. Some bad ways to do this that might work for some use cases also include:
// hackish/wrongish; preprocess your string into "a.b.4.c.d.1.2.3", e.g.:
> yourstring.replace(/]/g,"").replace(/\[/g,".").split(".")
"a.b.4.c.d.1.2.3" //use code from before
Special 2018 edit:
Let's go full-circle and do the most inefficient, horribly-overmetaprogrammed solution we can come up with... in the interest of syntactical purityhamfistery. With ES6 Proxy objects!... Let's also define some properties which (imho are fine and wonderful but) may break improperly-written libraries. You should perhaps be wary of using this if you care about performance, sanity (yours or others'), your job, etc.
// [1,2,3][-1]==3 (or just use .slice(-1)[0])
if (![1][-1])
Object.defineProperty(Array.prototype, -1, {get() {return this[this.length-1]}}); //credit to caub
// WARNING: THIS XTREME™ RADICAL METHOD IS VERY INEFFICIENT,
// ESPECIALLY IF INDEXING INTO MULTIPLE OBJECTS,
// because you are constantly creating wrapper objects on-the-fly and,
// even worse, going through Proxy i.e. runtime ~reflection, which prevents
// compiler optimization
// Proxy handler to override obj[*]/obj.* and obj[*]=...
var hyperIndexProxyHandler = {
get: function(obj,key, proxy) {
return key.split('.').reduce((o,i)=> o[i], obj);
},
set: function(obj,key,value, proxy) {
var keys = key.split('.');
var beforeLast = keys.slice(0,-1).reduce((o,i)=> o[i], obj);
beforeLast[keys[-1]] = value;
},
has: function(obj,key) {
//etc
}
};
function hyperIndexOf(target) {
return new Proxy(target, hyperIndexProxyHandler);
}
Demo:
var obj = {a:{b:{c:1, d:2}}};
console.log("obj is:", JSON.stringify(obj));
var objHyper = hyperIndexOf(obj);
console.log("(proxy override get) objHyper['a.b.c'] is:", objHyper['a.b.c']);
objHyper['a.b.c'] = 3;
console.log("(proxy override set) objHyper['a.b.c']=3, now obj is:", JSON.stringify(obj));
console.log("(behind the scenes) objHyper is:", objHyper);
if (!({}).H)
Object.defineProperties(Object.prototype, {
H: {
get: function() {
return hyperIndexOf(this); // TODO:cache as a non-enumerable property for efficiency?
}
}
});
console.log("(shortcut) obj.H['a.b.c']=4");
obj.H['a.b.c'] = 4;
console.log("(shortcut) obj.H['a.b.c'] is obj['a']['b']['c'] is", obj.H['a.b.c']);
Output:
obj is: {"a":{"b":{"c":1,"d":2}}}
(proxy override get) objHyper['a.b.c'] is: 1
(proxy override set) objHyper['a.b.c']=3, now obj is: {"a":{"b":{"c":3,"d":2}}}
(behind the scenes) objHyper is: Proxy {a: {…}}
(shortcut) obj.H['a.b.c']=4
(shortcut) obj.H['a.b.c'] is obj['a']['b']['c'] is: 4
inefficient idea: You can modify the above to dispatch based on the input argument; either use the .match(/[^\]\[.]+/g) method to support obj['keys'].like[3]['this'], or if instanceof Array, then just accept an Array as input like keys = ['a','b','c']; obj.H[keys].
Per suggestion that maybe you want to handle undefined indices in a 'softer' NaN-style manner (e.g. index({a:{b:{c:...}}}, 'a.x.c') return undefined rather than uncaught TypeError)...:
This makes sense from the perspective of "we should return undefined rather than throw an error" in the 1-dimensional index situation ({})['e.g.']==undefined, so "we should return undefined rather than throw an error" in the N-dimensional situation.
This does not make sense from the perspective that we are doing x['a']['x']['c'], which would fail with a TypeError in the above example.
That said, you'd make this work by replacing your reducing function with either:
(o,i)=> o===undefined?undefined:o[i], or
(o,i)=> (o||{})[i].
(You can make this more efficient by using a for loop and breaking/returning whenever the subresult you'd next index into is undefined, or using a try-catch if you expect such failures to be sufficiently rare.)
If you can use Lodash, there is a function, which does exactly that:
_.get(object, path, [defaultValue])
var val = _.get(obj, "a.b");
You could use lodash.get
After installing (npm i lodash.get), use it like this:
const get = require('lodash.get');
const myObj = {
user: {
firstName: 'Stacky',
lastName: 'Overflowy',
list: ['zero', 'one', 'two']
},
id: 123
};
console.log(get(myObj, 'user.firstName')); // outputs Stacky
console.log(get(myObj, 'id')); // outputs 123
console.log(get(myObj, 'user.list[1]')); // outputs one
// You can also update values
get(myObj, 'user').firstName = 'John';
A little more involved example with recursion.
function recompose(obj, string) {
var parts = string.split('.');
var newObj = obj[parts[0]];
if (parts[1]) {
parts.splice(0, 1);
var newString = parts.join('.');
return recompose(newObj, newString);
}
return newObj;
}
var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}};
console.log(recompose(obj, 'a.d.a.b')); //blah
2021
You don't need to pull in another dependency every time you wish for new capabilities in your program. Modern JS is very capable and the optional-chaining operator ?. is now widely supported and makes this kind of task easy as heck.
With a single line of code we can write get that takes an input object, t and string path. It works for object and arrays of any nesting level -
const get = (t, path) =>
path.split(".").reduce((r, k) => r?.[k], t)
const mydata =
{ a: { b: [ 0, { c: { d: [ "hello", "world" ] } } ] } }
console.log(get(mydata, "a.b.1.c.d.0"))
console.log(get(mydata, "a.b.1.c.d.1"))
console.log(get(mydata, "a.b.x.y.z"))
"hello"
"world"
undefined
I suggest to split the path and iterate it and reduce the object you have. This proposal works with a default value for missing properties.
const getValue = (object, keys) => keys.split('.').reduce((o, k) => (o || {})[k], object);
console.log(getValue({ a: { b: '1', c: '2' } }, 'a.b'));
console.log(getValue({ a: { b: '1', c: '2' } }, 'foo.bar.baz'));
Many years since the original post.
Now there is a great library called 'object-path'.
https://github.com/mariocasciaro/object-path
Available on NPM and BOWER
https://www.npmjs.com/package/object-path
It's as easy as:
objectPath.get(obj, "a.c.1"); //returns "f"
objectPath.set(obj, "a.j.0.f", "m");
And works for deeply nested properties and arrays.
If you expect to dereference the same path many times, building a function for each dot notation path actually has the best performance by far (expanding on the perf tests James Wilkins linked to in comments above).
var path = 'a.b.x';
var getter = new Function("obj", "return obj." + path + ";");
getter(obj);
Using the Function constructor has some of the same drawbacks as eval() in terms of security and worst-case performance, but IMO it's a badly underused tool for cases where you need a combination of extreme dynamism and high performance. I use this methodology to build array filter functions and call them inside an AngularJS digest loop. My profiles consistently show the array.filter() step taking less than 1ms to dereference and filter about 2000 complex objects, using dynamically-defined paths 3-4 levels deep.
A similar methodology could be used to create setter functions, of course:
var setter = new Function("obj", "newval", "obj." + path + " = newval;");
setter(obj, "some new val");
Other proposals are a little cryptic, so I thought I'd contribute:
Object.prop = function(obj, prop, val){
var props = prop.split('.')
, final = props.pop(), p
while(p = props.shift()){
if (typeof obj[p] === 'undefined')
return undefined;
obj = obj[p]
}
return val ? (obj[final] = val) : obj[final]
}
var obj = { a: { b: '1', c: '2' } }
// get
console.log(Object.prop(obj, 'a.c')) // -> 2
// set
Object.prop(obj, 'a.c', function(){})
console.log(obj) // -> { a: { b: '1', c: [Function] } }
var a = { b: { c: 9 } };
function value(layer, path, value) {
var i = 0,
path = path.split('.');
for (; i < path.length; i++)
if (value != null && i + 1 === path.length)
layer[path[i]] = value;
layer = layer[path[i]];
return layer;
};
value(a, 'b.c'); // 9
value(a, 'b.c', 4);
value(a, 'b.c'); // 4
This is a lot of code when compared to the much simpler eval way of doing it, but like Simon Willison says, you should never use eval.
Also, JSFiddle.
You can use the library available at npm, which simplifies this process. https://www.npmjs.com/package/dot-object
var dot = require('dot-object');
var obj = {
some: {
nested: {
value: 'Hi there!'
}
}
};
var val = dot.pick('some.nested.value', obj);
console.log(val);
// Result: Hi there!
Note if you're already using Lodash you can use the property or get functions:
var obj = { a: { b: '1', c: '2' } };
_.property('a.b')(obj); // => 1
_.get(obj, 'a.b'); // => 1
Underscore.js also has a property function, but it doesn't support dot notation.
I have extended the elegant answer by ninjagecko so that the function handles both dotted and/or array style references, and so that an empty string causes the parent object to be returned.
Here you go:
string_to_ref = function (object, reference) {
function arr_deref(o, ref, i) { return !ref ? o : (o[ref.slice(0, i ? -1 : ref.length)]) }
function dot_deref(o, ref) { return ref.split('[').reduce(arr_deref, o); }
return !reference ? object : reference.split('.').reduce(dot_deref, object);
};
See my working jsFiddle example here: http://jsfiddle.net/sc0ttyd/q7zyd/
You can obtain value of an object member by dot notation with a single line of code:
new Function('_', 'return _.' + path)(obj);
In you case:
var obj = { a: { b: '1', c: '2' } }
var val = new Function('_', 'return _.a.b')(obj);
To make it simple you may write a function like this:
function objGet(obj, path){
return new Function('_', 'return _.' + path)(obj);
}
Explanation:
The Function constructor creates a new Function object. In JavaScript every function is actually a Function object. Syntax to create a function explicitly with Function constructor is:
new Function ([arg1[, arg2[, ...argN]],] functionBody)
where arguments(arg1 to argN) must be a string that corresponds to a valid javaScript identifier and functionBody is a string containing the javaScript statements comprising the function definition.
In our case we take the advantage of string function body to retrieve object member with dot notation.
Hope it helps.
var find = function(root, path) {
var segments = path.split('.'),
cursor = root,
target;
for (var i = 0; i < segments.length; ++i) {
target = cursor[segments[i]];
if (typeof target == "undefined") return void 0;
cursor = target;
}
return cursor;
};
var obj = { a: { b: '1', c: '2' } }
find(obj, "a.b"); // 1
var set = function (root, path, value) {
var segments = path.split('.'),
cursor = root,
target;
for (var i = 0; i < segments.length - 1; ++i) {
cursor = cursor[segments[i]] || { };
}
cursor[segments[segments.length - 1]] = value;
};
set(obj, "a.k", function () { console.log("hello world"); });
find(obj, "a.k")(); // hello world
Use this function:
function dotToObject(data) {
function index(parent, key, value) {
const [mainKey, ...children] = key.split(".");
parent[mainKey] = parent[mainKey] || {};
if (children.length === 1) {
parent[mainKey][children[0]] = value;
} else {
index(parent[mainKey], children.join("."), value);
}
}
const result = Object.entries(data).reduce((acc, [key, value]) => {
if (key.includes(".")) {
index(acc, key, value);
} else {
acc[key] = value;
}
return acc;
}, {});
return result;
}
module.exports = { dotToObject };
Ex:
const user = {
id: 1,
name: 'My name',
'address.zipCode': '123',
'address.name': 'Some name',
'address.something.id': 1,
}
const mappedUser = dotToObject(user)
console.log(JSON.stringify(mappedUser, null, 2))
Output:
{
"id": 1,
"name": "My name",
"address": {
"zipCode": "123",
"name": "Some name",
"something": {
"id": 1
}
}
}
using Array Reduce function will get/set based on path provided.
I tested it with a.b.c and a.b.2.c {a:{b:[0,1,{c:7}]}} and its works for both getting key or mutating object to set value
function setOrGet(obj, path=[], newValue){
const l = typeof path === 'string' ? path.split('.') : path;
return l.reduce((carry,item, idx)=>{
const leaf = carry[item];
// is this last item in path ? cool lets set/get value
if( l.length-idx===1) {
// mutate object if newValue is set;
carry[item] = newValue===undefined ? leaf : newValue;
// return value if its a get/object if it was a set
return newValue===undefined ? leaf : obj ;
}
carry[item] = leaf || {}; // mutate if key not an object;
return carry[item]; // return object ref: to continue reduction;
}, obj)
}
console.log(
setOrGet({a: {b:1}},'a.b') === 1 ||
'Test Case: Direct read failed'
)
console.log(
setOrGet({a: {b:1}},'a.c',22).a.c===22 ||
'Test Case: Direct set failed'
)
console.log(
setOrGet({a: {b:[1,2]}},'a.b.1',22).a.b[1]===22 ||
'Test Case: Direct set on array failed'
)
console.log(
setOrGet({a: {b:{c: {e:1} }}},'a.b.c.e',22).a.b.c. e===22 ||
'Test Case: deep get failed'
)
// failed !. Thats your homework :)
console.log(
setOrGet({a: {b:{c: {e:[1,2,3,4,5]} }}},'a.b.c.e.3 ',22)
)
my personal recommendation.
do not use such a thing unless there is no other way!
i saw many examples people use it for translations for example from json; so you see function like locale('app.homepage.welcome') . this is just bad. if you already have data in an object/json; and you know path.. then just use it directly example locale().app.homepage.welcome by changing you function to return object you get typesafe, with autocomplete, less prone to typo's ..
I copied the following from Ricardo Tomasi's answer and modified to also create sub-objects that don't yet exist as necessary. It's a little less efficient (more ifs and creating of empty objects), but should be pretty good.
Also, it'll allow us to do Object.prop(obj, 'a.b', false) where we couldn't before. Unfortunately, it still won't let us assign undefined...Not sure how to go about that one yet.
/**
* Object.prop()
*
* Allows dot-notation access to object properties for both getting and setting.
*
* #param {Object} obj The object we're getting from or setting
* #param {string} prop The dot-notated string defining the property location
* #param {mixed} val For setting only; the value to set
*/
Object.prop = function(obj, prop, val){
var props = prop.split('.'),
final = props.pop(),
p;
for (var i = 0; i < props.length; i++) {
p = props[i];
if (typeof obj[p] === 'undefined') {
// If we're setting
if (typeof val !== 'undefined') {
// If we're not at the end of the props, keep adding new empty objects
if (i != props.length)
obj[p] = {};
}
else
return undefined;
}
obj = obj[p]
}
return typeof val !== "undefined" ? (obj[final] = val) : obj[final]
}
Few years later, I found this that handles scope and array. e.g. a['b']["c"].d.etc
function getScopedObj(scope, str) {
let obj=scope, arr;
try {
arr = str.split(/[\[\]\.]/) // split by [,],.
.filter(el => el) // filter out empty one
.map(el => el.replace(/^['"]+|['"]+$/g, '')); // remove string quotation
arr.forEach(el => obj = obj[el])
} catch(e) {
obj = undefined;
}
return obj;
}
window.a = {b: {c: {d: {etc: 'success'}}}}
getScopedObj(window, `a.b.c.d.etc`) // success
getScopedObj(window, `a['b']["c"].d.etc`) // success
getScopedObj(window, `a['INVALID']["c"].d.etc`) // undefined
If you wish to convert any object that contains dot notation keys into an arrayed version of those keys you can use this.
This will convert something like
{
name: 'Andy',
brothers.0: 'Bob'
brothers.1: 'Steve'
brothers.2: 'Jack'
sisters.0: 'Sally'
}
to
{
name: 'Andy',
brothers: ['Bob', 'Steve', 'Jack']
sisters: ['Sally']
}
convertDotNotationToArray(objectWithDotNotation) {
Object.entries(objectWithDotNotation).forEach(([key, val]) => {
// Is the key of dot notation
if (key.includes('.')) {
const [name, index] = key.split('.');
// If you have not created an array version, create one
if (!objectWithDotNotation[name]) {
objectWithDotNotation[name] = new Array();
}
// Save the value in the newly created array at the specific index
objectWithDotNotation[name][index] = val;
// Delete the current dot notation key val
delete objectWithDotNotation[key];
}
});
}
If you want to convert a string dot notation into an object, I've made a handy little helper than can turn a string like a.b.c.d with a value of e with dotPathToObject("a.b.c.d", "value") returning this:
{
"a": {
"b": {
"c": {
"d": "value"
}
}
}
}
https://gist.github.com/ahallora/9731d73efb15bd3d3db647efa3389c12
Solution:
function deepFind(key, data){
return key.split('.').reduce((ob,i)=> ob?.[i], data)
}
Usage:
const obj = {
company: "Pet Shop",
person: {
name: "John"
},
animal: {
name: "Lucky"
}
}
const company = deepFind("company", obj)
const personName = deepFind("person.name", obj)
const animalName = deepFind("animal.name", obj)
Here is my implementation
Implementation 1
Object.prototype.access = function() {
var ele = this[arguments[0]];
if(arguments.length === 1) return ele;
return ele.access.apply(ele, [].slice.call(arguments, 1));
}
Implementation 2 (using array reduce instead of slice)
Object.prototype.access = function() {
var self = this;
return [].reduce.call(arguments,function(prev,cur) {
return prev[cur];
}, self);
}
Examples:
var myobj = {'a':{'b':{'c':{'d':'abcd','e':[11,22,33]}}}};
myobj.access('a','b','c'); // returns: {'d':'abcd', e:[0,1,2,3]}
myobj.a.b.access('c','d'); // returns: 'abcd'
myobj.access('a','b','c','e',0); // returns: 11
it can also handle objects inside arrays as for
var myobj2 = {'a': {'b':[{'c':'ab0c'},{'d':'ab1d'}]}}
myobj2.access('a','b','1','d'); // returns: 'ab1d'
I used this code in my project
const getValue = (obj, arrPath) => (
arrPath.reduce((x, y) => {
if (y in x) return x[y]
return {}
}, obj)
)
Usage:
const obj = { id: { user: { local: 104 } } }
const path = [ 'id', 'user', 'local' ]
getValue(obj, path) // return 104
Using object-scan seems a bit overkill, but you can simply do
// const objectScan = require('object-scan');
const get = (obj, p) => objectScan([p], { abort: true, rtn: 'value' })(obj);
const obj = { a: { b: '1', c: '2' } };
console.log(get(obj, 'a.b'));
// => 1
console.log(get(obj, '*.c'));
// => 2
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.7.1"></script>
Disclaimer: I'm the author of object-scan
There are a lot more advanced examples in the readme.
This is one of those cases, where you ask 10 developers and you get 10 answers.
Below is my [simplified] solution for OP, using dynamic programming.
The idea is that you would pass an existing DTO object that you wish to UPDATE. This makes the method most useful in the case where you have a form with several input elements having name attributes set with dot (fluent) syntax.
Example use:
<input type="text" name="person.contact.firstName" />
Code snippet:
const setFluently = (obj, path, value) => {
if (typeof path === "string") {
return setFluently(obj, path.split("."), value);
}
if (path.length <= 1) {
obj[path[0]] = value;
return obj;
}
const key = path[0];
obj[key] = setFluently(obj[key] ? obj[key] : {}, path.slice(1), value);
return obj;
};
const origObj = {
a: {
b: "1",
c: "2"
}
};
setFluently(origObj, "a.b", "3");
setFluently(origObj, "a.c", "4");
console.log(JSON.stringify(origObj, null, 3));
function at(obj, path, val = undefined) {
// If path is an Array,
if (Array.isArray(path)) {
// it returns the mapped array for each result of the path
return path.map((path) => at(obj, path, val));
}
// Uniting several RegExps into one
const rx = new RegExp(
[
/(?:^(?:\.\s*)?([_a-zA-Z][_a-zA-Z0-9]*))/,
/(?:^\[\s*(\d+)\s*\])/,
/(?:^\[\s*'([^']*(?:\\'[^']*)*)'\s*\])/,
/(?:^\[\s*"([^"]*(?:\\"[^"]*)*)"\s*\])/,
/(?:^\[\s*`([^`]*(?:\\`[^`]*)*)`\s*\])/,
]
.map((r) => r.source)
.join("|")
);
let rm;
while (rm = rx.exec(path.trim())) {
// Matched resource
let [rf, rp] = rm.filter(Boolean);
// If no one matches found,
if (!rm[1] && !rm[2]) {
// it will replace escape-chars
rp = rp.replace(/\\(.)/g, "$1");
}
// If the new value is set,
if ("undefined" != typeof val && path.length == rf.length) {
// assign a value to the object property and return it
return (obj[rp] = val);
}
// Going one step deeper
obj = obj[rp];
// Removing a step from the path
path = path.substr(rf.length).trim();
}
if (path) {
throw new SyntaxError();
}
return obj;
}
// Test object schema
let o = { a: { b: [ [ { c: { d: { '"e"': { f: { g: "xxx" } } } } } ] ] } };
// Print source object
console.log(JSON.stringify(o));
// Set value
console.log(at(o, '.a["b"][0][0].c[`d`]["\\"e\\""][\'f\']["g"]', "zzz"));
// Get value
console.log(at(o, '.a["b"][0][0].c[`d`]["\\"e\\""][\'f\']["g"]'));
// Print result object
console.log(JSON.stringify(o));
Here is my code without using eval. It’s easy to understand too.
function value(obj, props) {
if (!props)
return obj;
var propsArr = props.split('.');
var prop = propsArr.splice(0, 1);
return value(obj[prop], propsArr.join('.'));
}
var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}};
console.log(value(obj, 'a.d.a.b')); // Returns blah
Yes, extending base prototypes is not usually good idea but, if you keep all extensions in one place, they might be useful.
So, here is my way to do this.
Object.defineProperty(Object.prototype, "getNestedProperty", {
value : function (propertyName) {
var result = this;
var arr = propertyName.split(".");
while (arr.length && result) {
result = result[arr.shift()];
}
return result;
},
enumerable: false
});
Now you will be able to get nested property everywhere without importing module with function or copy/pasting function.
Example:
{a:{b:11}}.getNestedProperty('a.b'); // Returns 11
The Next.js extension broke Mongoose in my project. Also I've read that it might break jQuery. So, never do it in the Next.js way:
Object.prototype.getNestedProperty = function (propertyName) {
var result = this;
var arr = propertyName.split(".");
while (arr.length && result) {
result = result[arr.shift()];
}
return result;
};
This is my extended solution proposed by ninjagecko.
For me, simple string notation was not enough, so the below version supports things like:
index(obj, 'data.accounts[0].address[0].postcode');
/**
* Get object by index
* #supported
* - arrays supported
* - array indexes supported
* #not-supported
* - multiple arrays
* #issues:
* index(myAccount, 'accounts[0].address[0].id') - works fine
* index(myAccount, 'accounts[].address[0].id') - doesnt work
* #Example:
* index(obj, 'data.accounts[].id') => returns array of id's
* index(obj, 'data.accounts[0].id') => returns id of 0 element from array
* index(obj, 'data.accounts[0].addresses.list[0].id') => error
* #param obj
* #param path
* #returns {any}
*/
var index = function(obj, path, isArray?, arrIndex?){
// is an array
if(typeof isArray === 'undefined') isArray = false;
// array index,
// if null, will take all indexes
if(typeof arrIndex === 'undefined') arrIndex = null;
var _arrIndex = null;
var reduceArrayTag = function(i, subArrIndex){
return i.replace(/(\[)([\d]{0,})(\])/, (i) => {
var tmp = i.match(/(\[)([\d]{0,})(\])/);
isArray = true;
if(subArrIndex){
_arrIndex = (tmp[2] !== '') ? tmp[2] : null;
}else{
arrIndex = (tmp[2] !== '') ? tmp[2] : null;
}
return '';
});
}
function byIndex(obj, i) {
// if is an array
if(isArray){
isArray = false;
i = reduceArrayTag(i, true);
// if array index is null,
// return an array of with values from every index
if(!arrIndex){
var arrValues = [];
_.forEach(obj, (el) => {
arrValues.push(index(el, i, isArray, arrIndex));
})
return arrValues;
}
// if array index is specified
var value = obj[arrIndex][i];
if(isArray){
arrIndex = _arrIndex;
}else{
arrIndex = null;
}
return value;
}else{
// remove [] from notation,
// if [] has been removed, check the index of array
i = reduceArrayTag(i, false);
return obj[i]
}
}
// reduce with the byIndex method
return path.split('.').reduce(byIndex, obj)
}
Given a JavaScript object,
var obj = { a: { b: '1', c: '2' } }
and a string
"a.b"
how can I convert the string to dot notation so I can go
var val = obj.a.b
If the string was just 'a', I could use obj[a]. But this is more complex. I imagine there is some straightforward method, but it escapes me at present.
recent note: While I'm flattered that this answer has gotten many upvotes, I am also somewhat horrified. If one needs to convert dot-notation strings like "x.a.b.c" into references, it could (maybe) be a sign that there is something very wrong going on (unless maybe you're performing some strange deserialization).
That is to say, novices who find their way to this answer must ask themselves the question "why am I doing this?"
It is of course generally fine to do this if your use case is small and you will not run into performance issues, AND you won't need to build upon your abstraction to make it more complicated later. In fact, if this will reduce code complexity and keep things simple, you should probably go ahead and do what OP is asking for. However, if that's not the case, consider if any of these apply:
case 1: As the primary method of working with your data (e.g. as your app's default form of passing objects around and dereferencing them). Like asking "how can I look up a function or variable name from a string".
This is bad programming practice (unnecessary metaprogramming specifically, and kind of violates function side-effect-free coding style, and will have performance hits). Novices who find themselves in this case, should instead consider working with array representations, e.g. ['x','a','b','c'], or even something more direct/simple/straightforward if possible: like not losing track of the references themselves in the first place (most ideal if it's only client-side or only server-side), etc. (A pre-existing unique id would be inelegant to add, but could be used if the spec otherwise requires its existence regardless.)
case 2: Working with serialized data, or data that will be displayed to the user. Like using a date as a string "1999-12-30" rather than a Date object (which can cause timezone bugs or added serialization complexity if not careful). Or you know what you're doing.
This is maybe fine. Be careful that there are no dot strings "." in your sanitized input fragments.
If you find yourself using this answer all the time and converting back and forth between string and array, you may be in the bad case, and should consider an alternative.
Here's an elegant one-liner that's 10x shorter than the other solutions:
function index(obj,i) {return obj[i]}
'a.b.etc'.split('.').reduce(index, obj)
[edit] Or in ECMAScript 6:
'a.b.etc'.split('.').reduce((o,i)=> o[i], obj)
(Not that I think eval always bad like others suggest it is (though it usually is), nevertheless those people will be pleased that this method doesn't use eval. The above will find obj.a.b.etc given obj and the string "a.b.etc".)
In response to those who still are afraid of using reduce despite it being in the ECMA-262 standard (5th edition), here is a two-line recursive implementation:
function multiIndex(obj,is) { // obj,['1','2','3'] -> ((obj['1'])['2'])['3']
return is.length ? multiIndex(obj[is[0]],is.slice(1)) : obj
}
function pathIndex(obj,is) { // obj,'1.2.3' -> multiIndex(obj,['1','2','3'])
return multiIndex(obj,is.split('.'))
}
pathIndex('a.b.etc')
Depending on the optimizations the JS compiler is doing, you may want to make sure any nested functions are not re-defined on every call via the usual methods (placing them in a closure, object, or global namespace).
edit:
To answer an interesting question in the comments:
how would you turn this into a setter as well? Not only returning the values by path, but also setting them if a new value is sent into the function? – Swader Jun 28 at 21:42
(sidenote: sadly can't return an object with a Setter, as that would violate the calling convention; commenter seems to instead be referring to a general setter-style function with side-effects like index(obj,"a.b.etc", value) doing obj.a.b.etc = value.)
The reduce style is not really suitable to that, but we can modify the recursive implementation:
function index(obj,is, value) {
if (typeof is == 'string')
return index(obj,is.split('.'), value);
else if (is.length==1 && value!==undefined)
return obj[is[0]] = value;
else if (is.length==0)
return obj;
else
return index(obj[is[0]],is.slice(1), value);
}
Demo:
> obj = {a:{b:{etc:5}}}
> index(obj,'a.b.etc')
5
> index(obj,['a','b','etc']) #works with both strings and lists
5
> index(obj,'a.b.etc', 123) #setter-mode - third argument (possibly poor form)
123
> index(obj,'a.b.etc')
123
...though personally I'd recommend making a separate function setIndex(...). I would like to end on a side-note that the original poser of the question could (should?) be working with arrays of indices (which they can get from .split), rather than strings; though there's usually nothing wrong with a convenience function.
A commenter asked:
what about arrays? something like "a.b[4].c.d[1][2][3]" ? –AlexS
Javascript is a very weird language; in general objects can only have strings as their property keys, so for example if x was a generic object like x={}, then x[1] would become x["1"]... you read that right... yup...
Javascript Arrays (which are themselves instances of Object) specifically encourage integer keys, even though you could do something like x=[]; x["puppy"]=5;.
But in general (and there are exceptions), x["somestring"]===x.somestring (when it's allowed; you can't do x.123).
(Keep in mind that whatever JS compiler you're using might choose, maybe, to compile these down to saner representations if it can prove it would not violate the spec.)
So the answer to your question would depend on whether you're assuming those objects only accept integers (due to a restriction in your problem domain), or not. Let's assume not. Then a valid expression is a concatenation of a base identifier plus some .identifiers plus some ["stringindex"]s.
Let us ignore for a moment that we can of course do other things legitimately in the grammar like identifier[0xFA7C25DD].asdf[f(4)?.[5]+k][false][null][undefined][NaN]; integers are not (that) 'special'.
Commenter's statement would then be equivalent to a["b"][4]["c"]["d"][1][2][3], though we should probably also support a.b["c\"validjsstringliteral"][3]. You'd have to check the ecmascript grammar section on string literals to see how to parse a valid string literal. Technically you'd also want to check (unlike in my first answer) that a is a valid javascript identifier.
A simple answer to your question though, if your strings don't contain commas or brackets, would be just be to match length 1+ sequences of characters not in the set , or [ or ]:
> "abc[4].c.def[1][2][\"gh\"]".match(/[^\]\[.]+/g)
// ^^^ ^ ^ ^^^ ^ ^ ^^^^^
["abc", "4", "c", "def", "1", "2", ""gh""]
If your strings don't contain escape characters or " characters, and because IdentifierNames are a sublanguage of StringLiterals (I think???) you could first convert your dots to []:
> var R=[], demoString="abc[4].c.def[1][2][\"gh\"]";
> for(var match,matcher=/^([^\.\[]+)|\.([^\.\[]+)|\["([^"]+)"\]|\[(\d+)\]/g;
match=matcher.exec(demoString); ) {
R.push(Array.from(match).slice(1).filter(x=> x!==undefined)[0]);
// extremely bad code because js regexes are weird, don't use this
}
> R
["abc", "4", "c", "def", "1", "2", "gh"]
Of course, always be careful and never trust your data. Some bad ways to do this that might work for some use cases also include:
// hackish/wrongish; preprocess your string into "a.b.4.c.d.1.2.3", e.g.:
> yourstring.replace(/]/g,"").replace(/\[/g,".").split(".")
"a.b.4.c.d.1.2.3" //use code from before
Special 2018 edit:
Let's go full-circle and do the most inefficient, horribly-overmetaprogrammed solution we can come up with... in the interest of syntactical purityhamfistery. With ES6 Proxy objects!... Let's also define some properties which (imho are fine and wonderful but) may break improperly-written libraries. You should perhaps be wary of using this if you care about performance, sanity (yours or others'), your job, etc.
// [1,2,3][-1]==3 (or just use .slice(-1)[0])
if (![1][-1])
Object.defineProperty(Array.prototype, -1, {get() {return this[this.length-1]}}); //credit to caub
// WARNING: THIS XTREME™ RADICAL METHOD IS VERY INEFFICIENT,
// ESPECIALLY IF INDEXING INTO MULTIPLE OBJECTS,
// because you are constantly creating wrapper objects on-the-fly and,
// even worse, going through Proxy i.e. runtime ~reflection, which prevents
// compiler optimization
// Proxy handler to override obj[*]/obj.* and obj[*]=...
var hyperIndexProxyHandler = {
get: function(obj,key, proxy) {
return key.split('.').reduce((o,i)=> o[i], obj);
},
set: function(obj,key,value, proxy) {
var keys = key.split('.');
var beforeLast = keys.slice(0,-1).reduce((o,i)=> o[i], obj);
beforeLast[keys[-1]] = value;
},
has: function(obj,key) {
//etc
}
};
function hyperIndexOf(target) {
return new Proxy(target, hyperIndexProxyHandler);
}
Demo:
var obj = {a:{b:{c:1, d:2}}};
console.log("obj is:", JSON.stringify(obj));
var objHyper = hyperIndexOf(obj);
console.log("(proxy override get) objHyper['a.b.c'] is:", objHyper['a.b.c']);
objHyper['a.b.c'] = 3;
console.log("(proxy override set) objHyper['a.b.c']=3, now obj is:", JSON.stringify(obj));
console.log("(behind the scenes) objHyper is:", objHyper);
if (!({}).H)
Object.defineProperties(Object.prototype, {
H: {
get: function() {
return hyperIndexOf(this); // TODO:cache as a non-enumerable property for efficiency?
}
}
});
console.log("(shortcut) obj.H['a.b.c']=4");
obj.H['a.b.c'] = 4;
console.log("(shortcut) obj.H['a.b.c'] is obj['a']['b']['c'] is", obj.H['a.b.c']);
Output:
obj is: {"a":{"b":{"c":1,"d":2}}}
(proxy override get) objHyper['a.b.c'] is: 1
(proxy override set) objHyper['a.b.c']=3, now obj is: {"a":{"b":{"c":3,"d":2}}}
(behind the scenes) objHyper is: Proxy {a: {…}}
(shortcut) obj.H['a.b.c']=4
(shortcut) obj.H['a.b.c'] is obj['a']['b']['c'] is: 4
inefficient idea: You can modify the above to dispatch based on the input argument; either use the .match(/[^\]\[.]+/g) method to support obj['keys'].like[3]['this'], or if instanceof Array, then just accept an Array as input like keys = ['a','b','c']; obj.H[keys].
Per suggestion that maybe you want to handle undefined indices in a 'softer' NaN-style manner (e.g. index({a:{b:{c:...}}}, 'a.x.c') return undefined rather than uncaught TypeError)...:
This makes sense from the perspective of "we should return undefined rather than throw an error" in the 1-dimensional index situation ({})['e.g.']==undefined, so "we should return undefined rather than throw an error" in the N-dimensional situation.
This does not make sense from the perspective that we are doing x['a']['x']['c'], which would fail with a TypeError in the above example.
That said, you'd make this work by replacing your reducing function with either:
(o,i)=> o===undefined?undefined:o[i], or
(o,i)=> (o||{})[i].
(You can make this more efficient by using a for loop and breaking/returning whenever the subresult you'd next index into is undefined, or using a try-catch if you expect such failures to be sufficiently rare.)
If you can use Lodash, there is a function, which does exactly that:
_.get(object, path, [defaultValue])
var val = _.get(obj, "a.b");
You could use lodash.get
After installing (npm i lodash.get), use it like this:
const get = require('lodash.get');
const myObj = {
user: {
firstName: 'Stacky',
lastName: 'Overflowy',
list: ['zero', 'one', 'two']
},
id: 123
};
console.log(get(myObj, 'user.firstName')); // outputs Stacky
console.log(get(myObj, 'id')); // outputs 123
console.log(get(myObj, 'user.list[1]')); // outputs one
// You can also update values
get(myObj, 'user').firstName = 'John';
A little more involved example with recursion.
function recompose(obj, string) {
var parts = string.split('.');
var newObj = obj[parts[0]];
if (parts[1]) {
parts.splice(0, 1);
var newString = parts.join('.');
return recompose(newObj, newString);
}
return newObj;
}
var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}};
console.log(recompose(obj, 'a.d.a.b')); //blah
2021
You don't need to pull in another dependency every time you wish for new capabilities in your program. Modern JS is very capable and the optional-chaining operator ?. is now widely supported and makes this kind of task easy as heck.
With a single line of code we can write get that takes an input object, t and string path. It works for object and arrays of any nesting level -
const get = (t, path) =>
path.split(".").reduce((r, k) => r?.[k], t)
const mydata =
{ a: { b: [ 0, { c: { d: [ "hello", "world" ] } } ] } }
console.log(get(mydata, "a.b.1.c.d.0"))
console.log(get(mydata, "a.b.1.c.d.1"))
console.log(get(mydata, "a.b.x.y.z"))
"hello"
"world"
undefined
I suggest to split the path and iterate it and reduce the object you have. This proposal works with a default value for missing properties.
const getValue = (object, keys) => keys.split('.').reduce((o, k) => (o || {})[k], object);
console.log(getValue({ a: { b: '1', c: '2' } }, 'a.b'));
console.log(getValue({ a: { b: '1', c: '2' } }, 'foo.bar.baz'));
Many years since the original post.
Now there is a great library called 'object-path'.
https://github.com/mariocasciaro/object-path
Available on NPM and BOWER
https://www.npmjs.com/package/object-path
It's as easy as:
objectPath.get(obj, "a.c.1"); //returns "f"
objectPath.set(obj, "a.j.0.f", "m");
And works for deeply nested properties and arrays.
If you expect to dereference the same path many times, building a function for each dot notation path actually has the best performance by far (expanding on the perf tests James Wilkins linked to in comments above).
var path = 'a.b.x';
var getter = new Function("obj", "return obj." + path + ";");
getter(obj);
Using the Function constructor has some of the same drawbacks as eval() in terms of security and worst-case performance, but IMO it's a badly underused tool for cases where you need a combination of extreme dynamism and high performance. I use this methodology to build array filter functions and call them inside an AngularJS digest loop. My profiles consistently show the array.filter() step taking less than 1ms to dereference and filter about 2000 complex objects, using dynamically-defined paths 3-4 levels deep.
A similar methodology could be used to create setter functions, of course:
var setter = new Function("obj", "newval", "obj." + path + " = newval;");
setter(obj, "some new val");
Other proposals are a little cryptic, so I thought I'd contribute:
Object.prop = function(obj, prop, val){
var props = prop.split('.')
, final = props.pop(), p
while(p = props.shift()){
if (typeof obj[p] === 'undefined')
return undefined;
obj = obj[p]
}
return val ? (obj[final] = val) : obj[final]
}
var obj = { a: { b: '1', c: '2' } }
// get
console.log(Object.prop(obj, 'a.c')) // -> 2
// set
Object.prop(obj, 'a.c', function(){})
console.log(obj) // -> { a: { b: '1', c: [Function] } }
var a = { b: { c: 9 } };
function value(layer, path, value) {
var i = 0,
path = path.split('.');
for (; i < path.length; i++)
if (value != null && i + 1 === path.length)
layer[path[i]] = value;
layer = layer[path[i]];
return layer;
};
value(a, 'b.c'); // 9
value(a, 'b.c', 4);
value(a, 'b.c'); // 4
This is a lot of code when compared to the much simpler eval way of doing it, but like Simon Willison says, you should never use eval.
Also, JSFiddle.
You can use the library available at npm, which simplifies this process. https://www.npmjs.com/package/dot-object
var dot = require('dot-object');
var obj = {
some: {
nested: {
value: 'Hi there!'
}
}
};
var val = dot.pick('some.nested.value', obj);
console.log(val);
// Result: Hi there!
Note if you're already using Lodash you can use the property or get functions:
var obj = { a: { b: '1', c: '2' } };
_.property('a.b')(obj); // => 1
_.get(obj, 'a.b'); // => 1
Underscore.js also has a property function, but it doesn't support dot notation.
I have extended the elegant answer by ninjagecko so that the function handles both dotted and/or array style references, and so that an empty string causes the parent object to be returned.
Here you go:
string_to_ref = function (object, reference) {
function arr_deref(o, ref, i) { return !ref ? o : (o[ref.slice(0, i ? -1 : ref.length)]) }
function dot_deref(o, ref) { return ref.split('[').reduce(arr_deref, o); }
return !reference ? object : reference.split('.').reduce(dot_deref, object);
};
See my working jsFiddle example here: http://jsfiddle.net/sc0ttyd/q7zyd/
You can obtain value of an object member by dot notation with a single line of code:
new Function('_', 'return _.' + path)(obj);
In you case:
var obj = { a: { b: '1', c: '2' } }
var val = new Function('_', 'return _.a.b')(obj);
To make it simple you may write a function like this:
function objGet(obj, path){
return new Function('_', 'return _.' + path)(obj);
}
Explanation:
The Function constructor creates a new Function object. In JavaScript every function is actually a Function object. Syntax to create a function explicitly with Function constructor is:
new Function ([arg1[, arg2[, ...argN]],] functionBody)
where arguments(arg1 to argN) must be a string that corresponds to a valid javaScript identifier and functionBody is a string containing the javaScript statements comprising the function definition.
In our case we take the advantage of string function body to retrieve object member with dot notation.
Hope it helps.
var find = function(root, path) {
var segments = path.split('.'),
cursor = root,
target;
for (var i = 0; i < segments.length; ++i) {
target = cursor[segments[i]];
if (typeof target == "undefined") return void 0;
cursor = target;
}
return cursor;
};
var obj = { a: { b: '1', c: '2' } }
find(obj, "a.b"); // 1
var set = function (root, path, value) {
var segments = path.split('.'),
cursor = root,
target;
for (var i = 0; i < segments.length - 1; ++i) {
cursor = cursor[segments[i]] || { };
}
cursor[segments[segments.length - 1]] = value;
};
set(obj, "a.k", function () { console.log("hello world"); });
find(obj, "a.k")(); // hello world
Use this function:
function dotToObject(data) {
function index(parent, key, value) {
const [mainKey, ...children] = key.split(".");
parent[mainKey] = parent[mainKey] || {};
if (children.length === 1) {
parent[mainKey][children[0]] = value;
} else {
index(parent[mainKey], children.join("."), value);
}
}
const result = Object.entries(data).reduce((acc, [key, value]) => {
if (key.includes(".")) {
index(acc, key, value);
} else {
acc[key] = value;
}
return acc;
}, {});
return result;
}
module.exports = { dotToObject };
Ex:
const user = {
id: 1,
name: 'My name',
'address.zipCode': '123',
'address.name': 'Some name',
'address.something.id': 1,
}
const mappedUser = dotToObject(user)
console.log(JSON.stringify(mappedUser, null, 2))
Output:
{
"id": 1,
"name": "My name",
"address": {
"zipCode": "123",
"name": "Some name",
"something": {
"id": 1
}
}
}
using Array Reduce function will get/set based on path provided.
I tested it with a.b.c and a.b.2.c {a:{b:[0,1,{c:7}]}} and its works for both getting key or mutating object to set value
function setOrGet(obj, path=[], newValue){
const l = typeof path === 'string' ? path.split('.') : path;
return l.reduce((carry,item, idx)=>{
const leaf = carry[item];
// is this last item in path ? cool lets set/get value
if( l.length-idx===1) {
// mutate object if newValue is set;
carry[item] = newValue===undefined ? leaf : newValue;
// return value if its a get/object if it was a set
return newValue===undefined ? leaf : obj ;
}
carry[item] = leaf || {}; // mutate if key not an object;
return carry[item]; // return object ref: to continue reduction;
}, obj)
}
console.log(
setOrGet({a: {b:1}},'a.b') === 1 ||
'Test Case: Direct read failed'
)
console.log(
setOrGet({a: {b:1}},'a.c',22).a.c===22 ||
'Test Case: Direct set failed'
)
console.log(
setOrGet({a: {b:[1,2]}},'a.b.1',22).a.b[1]===22 ||
'Test Case: Direct set on array failed'
)
console.log(
setOrGet({a: {b:{c: {e:1} }}},'a.b.c.e',22).a.b.c. e===22 ||
'Test Case: deep get failed'
)
// failed !. Thats your homework :)
console.log(
setOrGet({a: {b:{c: {e:[1,2,3,4,5]} }}},'a.b.c.e.3 ',22)
)
my personal recommendation.
do not use such a thing unless there is no other way!
i saw many examples people use it for translations for example from json; so you see function like locale('app.homepage.welcome') . this is just bad. if you already have data in an object/json; and you know path.. then just use it directly example locale().app.homepage.welcome by changing you function to return object you get typesafe, with autocomplete, less prone to typo's ..
I copied the following from Ricardo Tomasi's answer and modified to also create sub-objects that don't yet exist as necessary. It's a little less efficient (more ifs and creating of empty objects), but should be pretty good.
Also, it'll allow us to do Object.prop(obj, 'a.b', false) where we couldn't before. Unfortunately, it still won't let us assign undefined...Not sure how to go about that one yet.
/**
* Object.prop()
*
* Allows dot-notation access to object properties for both getting and setting.
*
* #param {Object} obj The object we're getting from or setting
* #param {string} prop The dot-notated string defining the property location
* #param {mixed} val For setting only; the value to set
*/
Object.prop = function(obj, prop, val){
var props = prop.split('.'),
final = props.pop(),
p;
for (var i = 0; i < props.length; i++) {
p = props[i];
if (typeof obj[p] === 'undefined') {
// If we're setting
if (typeof val !== 'undefined') {
// If we're not at the end of the props, keep adding new empty objects
if (i != props.length)
obj[p] = {};
}
else
return undefined;
}
obj = obj[p]
}
return typeof val !== "undefined" ? (obj[final] = val) : obj[final]
}
Few years later, I found this that handles scope and array. e.g. a['b']["c"].d.etc
function getScopedObj(scope, str) {
let obj=scope, arr;
try {
arr = str.split(/[\[\]\.]/) // split by [,],.
.filter(el => el) // filter out empty one
.map(el => el.replace(/^['"]+|['"]+$/g, '')); // remove string quotation
arr.forEach(el => obj = obj[el])
} catch(e) {
obj = undefined;
}
return obj;
}
window.a = {b: {c: {d: {etc: 'success'}}}}
getScopedObj(window, `a.b.c.d.etc`) // success
getScopedObj(window, `a['b']["c"].d.etc`) // success
getScopedObj(window, `a['INVALID']["c"].d.etc`) // undefined
If you wish to convert any object that contains dot notation keys into an arrayed version of those keys you can use this.
This will convert something like
{
name: 'Andy',
brothers.0: 'Bob'
brothers.1: 'Steve'
brothers.2: 'Jack'
sisters.0: 'Sally'
}
to
{
name: 'Andy',
brothers: ['Bob', 'Steve', 'Jack']
sisters: ['Sally']
}
convertDotNotationToArray(objectWithDotNotation) {
Object.entries(objectWithDotNotation).forEach(([key, val]) => {
// Is the key of dot notation
if (key.includes('.')) {
const [name, index] = key.split('.');
// If you have not created an array version, create one
if (!objectWithDotNotation[name]) {
objectWithDotNotation[name] = new Array();
}
// Save the value in the newly created array at the specific index
objectWithDotNotation[name][index] = val;
// Delete the current dot notation key val
delete objectWithDotNotation[key];
}
});
}
If you want to convert a string dot notation into an object, I've made a handy little helper than can turn a string like a.b.c.d with a value of e with dotPathToObject("a.b.c.d", "value") returning this:
{
"a": {
"b": {
"c": {
"d": "value"
}
}
}
}
https://gist.github.com/ahallora/9731d73efb15bd3d3db647efa3389c12
Solution:
function deepFind(key, data){
return key.split('.').reduce((ob,i)=> ob?.[i], data)
}
Usage:
const obj = {
company: "Pet Shop",
person: {
name: "John"
},
animal: {
name: "Lucky"
}
}
const company = deepFind("company", obj)
const personName = deepFind("person.name", obj)
const animalName = deepFind("animal.name", obj)
Here is my implementation
Implementation 1
Object.prototype.access = function() {
var ele = this[arguments[0]];
if(arguments.length === 1) return ele;
return ele.access.apply(ele, [].slice.call(arguments, 1));
}
Implementation 2 (using array reduce instead of slice)
Object.prototype.access = function() {
var self = this;
return [].reduce.call(arguments,function(prev,cur) {
return prev[cur];
}, self);
}
Examples:
var myobj = {'a':{'b':{'c':{'d':'abcd','e':[11,22,33]}}}};
myobj.access('a','b','c'); // returns: {'d':'abcd', e:[0,1,2,3]}
myobj.a.b.access('c','d'); // returns: 'abcd'
myobj.access('a','b','c','e',0); // returns: 11
it can also handle objects inside arrays as for
var myobj2 = {'a': {'b':[{'c':'ab0c'},{'d':'ab1d'}]}}
myobj2.access('a','b','1','d'); // returns: 'ab1d'
I used this code in my project
const getValue = (obj, arrPath) => (
arrPath.reduce((x, y) => {
if (y in x) return x[y]
return {}
}, obj)
)
Usage:
const obj = { id: { user: { local: 104 } } }
const path = [ 'id', 'user', 'local' ]
getValue(obj, path) // return 104
Using object-scan seems a bit overkill, but you can simply do
// const objectScan = require('object-scan');
const get = (obj, p) => objectScan([p], { abort: true, rtn: 'value' })(obj);
const obj = { a: { b: '1', c: '2' } };
console.log(get(obj, 'a.b'));
// => 1
console.log(get(obj, '*.c'));
// => 2
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.7.1"></script>
Disclaimer: I'm the author of object-scan
There are a lot more advanced examples in the readme.
This is one of those cases, where you ask 10 developers and you get 10 answers.
Below is my [simplified] solution for OP, using dynamic programming.
The idea is that you would pass an existing DTO object that you wish to UPDATE. This makes the method most useful in the case where you have a form with several input elements having name attributes set with dot (fluent) syntax.
Example use:
<input type="text" name="person.contact.firstName" />
Code snippet:
const setFluently = (obj, path, value) => {
if (typeof path === "string") {
return setFluently(obj, path.split("."), value);
}
if (path.length <= 1) {
obj[path[0]] = value;
return obj;
}
const key = path[0];
obj[key] = setFluently(obj[key] ? obj[key] : {}, path.slice(1), value);
return obj;
};
const origObj = {
a: {
b: "1",
c: "2"
}
};
setFluently(origObj, "a.b", "3");
setFluently(origObj, "a.c", "4");
console.log(JSON.stringify(origObj, null, 3));
function at(obj, path, val = undefined) {
// If path is an Array,
if (Array.isArray(path)) {
// it returns the mapped array for each result of the path
return path.map((path) => at(obj, path, val));
}
// Uniting several RegExps into one
const rx = new RegExp(
[
/(?:^(?:\.\s*)?([_a-zA-Z][_a-zA-Z0-9]*))/,
/(?:^\[\s*(\d+)\s*\])/,
/(?:^\[\s*'([^']*(?:\\'[^']*)*)'\s*\])/,
/(?:^\[\s*"([^"]*(?:\\"[^"]*)*)"\s*\])/,
/(?:^\[\s*`([^`]*(?:\\`[^`]*)*)`\s*\])/,
]
.map((r) => r.source)
.join("|")
);
let rm;
while (rm = rx.exec(path.trim())) {
// Matched resource
let [rf, rp] = rm.filter(Boolean);
// If no one matches found,
if (!rm[1] && !rm[2]) {
// it will replace escape-chars
rp = rp.replace(/\\(.)/g, "$1");
}
// If the new value is set,
if ("undefined" != typeof val && path.length == rf.length) {
// assign a value to the object property and return it
return (obj[rp] = val);
}
// Going one step deeper
obj = obj[rp];
// Removing a step from the path
path = path.substr(rf.length).trim();
}
if (path) {
throw new SyntaxError();
}
return obj;
}
// Test object schema
let o = { a: { b: [ [ { c: { d: { '"e"': { f: { g: "xxx" } } } } } ] ] } };
// Print source object
console.log(JSON.stringify(o));
// Set value
console.log(at(o, '.a["b"][0][0].c[`d`]["\\"e\\""][\'f\']["g"]', "zzz"));
// Get value
console.log(at(o, '.a["b"][0][0].c[`d`]["\\"e\\""][\'f\']["g"]'));
// Print result object
console.log(JSON.stringify(o));
Here is my code without using eval. It’s easy to understand too.
function value(obj, props) {
if (!props)
return obj;
var propsArr = props.split('.');
var prop = propsArr.splice(0, 1);
return value(obj[prop], propsArr.join('.'));
}
var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}};
console.log(value(obj, 'a.d.a.b')); // Returns blah
Yes, extending base prototypes is not usually good idea but, if you keep all extensions in one place, they might be useful.
So, here is my way to do this.
Object.defineProperty(Object.prototype, "getNestedProperty", {
value : function (propertyName) {
var result = this;
var arr = propertyName.split(".");
while (arr.length && result) {
result = result[arr.shift()];
}
return result;
},
enumerable: false
});
Now you will be able to get nested property everywhere without importing module with function or copy/pasting function.
Example:
{a:{b:11}}.getNestedProperty('a.b'); // Returns 11
The Next.js extension broke Mongoose in my project. Also I've read that it might break jQuery. So, never do it in the Next.js way:
Object.prototype.getNestedProperty = function (propertyName) {
var result = this;
var arr = propertyName.split(".");
while (arr.length && result) {
result = result[arr.shift()];
}
return result;
};
This is my extended solution proposed by ninjagecko.
For me, simple string notation was not enough, so the below version supports things like:
index(obj, 'data.accounts[0].address[0].postcode');
/**
* Get object by index
* #supported
* - arrays supported
* - array indexes supported
* #not-supported
* - multiple arrays
* #issues:
* index(myAccount, 'accounts[0].address[0].id') - works fine
* index(myAccount, 'accounts[].address[0].id') - doesnt work
* #Example:
* index(obj, 'data.accounts[].id') => returns array of id's
* index(obj, 'data.accounts[0].id') => returns id of 0 element from array
* index(obj, 'data.accounts[0].addresses.list[0].id') => error
* #param obj
* #param path
* #returns {any}
*/
var index = function(obj, path, isArray?, arrIndex?){
// is an array
if(typeof isArray === 'undefined') isArray = false;
// array index,
// if null, will take all indexes
if(typeof arrIndex === 'undefined') arrIndex = null;
var _arrIndex = null;
var reduceArrayTag = function(i, subArrIndex){
return i.replace(/(\[)([\d]{0,})(\])/, (i) => {
var tmp = i.match(/(\[)([\d]{0,})(\])/);
isArray = true;
if(subArrIndex){
_arrIndex = (tmp[2] !== '') ? tmp[2] : null;
}else{
arrIndex = (tmp[2] !== '') ? tmp[2] : null;
}
return '';
});
}
function byIndex(obj, i) {
// if is an array
if(isArray){
isArray = false;
i = reduceArrayTag(i, true);
// if array index is null,
// return an array of with values from every index
if(!arrIndex){
var arrValues = [];
_.forEach(obj, (el) => {
arrValues.push(index(el, i, isArray, arrIndex));
})
return arrValues;
}
// if array index is specified
var value = obj[arrIndex][i];
if(isArray){
arrIndex = _arrIndex;
}else{
arrIndex = null;
}
return value;
}else{
// remove [] from notation,
// if [] has been removed, check the index of array
i = reduceArrayTag(i, false);
return obj[i]
}
}
// reduce with the byIndex method
return path.split('.').reduce(byIndex, obj)
}