Access sub-property with generic/dynamic property list [duplicate] - javascript

I have a bunch of object attributes coming in as dot-delimited strings like "availability_meta.supplier.price", and I need to assign a corresponding value to record['availability_meta']['supplier']['price'] and so on.
Not everything is 3 levels deep: many are only 1 level deep and many are deeper than 3 levels.
Is there a good way to assign this programmatically in Javascript? For example, I need:
["foo.bar.baz", 1] // --> record.foo.bar.baz = 1
["qux.qaz", "abc"] // --> record.qux.qaz = "abc"
["foshizzle", 200] // --> record.foshizzle = 200
I imagine I could hack something together, but I don't have any good algorithm in mind so would appreciate suggestions. I'm using lodash if that's helpful, and open to other libraries that may make quick work of this.
EDIT this is on the backend and run infrequently, so not necessary to optimize for size, speed, etc. In fact code readability would be a plus here for future devs.
EDIT 2 This is NOT the same as the referenced duplicate. Namely, I need to be able to do this assignment multiple times for the same object, and the "duplicate" answer will simply overwrite sub-keys each time. Please reopen!

You mentioned lodash in your question, so I thought I should add their easy object set() and get() functions. Just do something like:
_.set(record, 'availability_meta.supplier.price', 99);
You can read more about it here: https://lodash.com/docs#set
These functions let you do more complex things too, like specify array indexes, etc :)

Something to get you started:
function assignProperty(obj, path, value) {
var props = path.split(".")
, i = 0
, prop;
for(; i < props.length - 1; i++) {
prop = props[i];
obj = obj[prop];
}
obj[props[i]] = value;
}
Assuming:
var arr = ["foo.bar.baz", 1];
You'd call it using:
assignProperty(record, arr[0], arr[1]);
Example: http://jsfiddle.net/x49g5w8L/

What about this?
function convertDotPathToNestedObject(path, value) {
const [last, ...paths] = path.split('.').reverse();
return paths.reduce((acc, el) => ({ [el]: acc }), { [last]: value });
}
convertDotPathToNestedObject('foo.bar.x', 'FooBar')
// { foo: { bar: { x: 'FooBar' } } }

Just do
record['foo.bar.baz'] = 99;
But how would this work? It's strictly for the adventurous with a V8 environment (Chrome or Node harmony), using Object.observe. We observe the the object and capture the addition of new properties. When the "property" foo.bar.baz is added (via an assignment), we detect that this is a dotted property, and transform it into an assignment to record['foo']['bar.baz'] (creating record['foo'] if it does not exist), which in turn is transformed into an assignment to record['foo']['bar']['baz']. It goes like this:
function enable_dot_assignments(changes) {
// Iterate over changes
changes.forEach(function(change) {
// Deconstruct change record.
var object = change.object;
var type = change.type;
var name = change.name;
// Handle only 'add' type changes
if (type !== 'add') return;
// Break the property into segments, and get first one.
var segments = name.split('.');
var first_segment = segments.shift();
// Skip non-dotted property.
if (!segments.length) return;
// If the property doesn't exist, create it as object.
if (!(first_segment in object)) object[first_segment] = {};
var subobject = object[first_segment];
// Ensure subobject also enables dot assignments.
Object.observe(subobject, enable_dot_assignments);
// Set value on subobject using remainder of dot path.
subobject[segments.join('.')] = object[name];
// Make subobject assignments synchronous.
Object.deliverChangeRecords(enable_dot_assignments);
// We don't need the 'a.b' property on the object.
delete object[name];
});
}
Now you can just do
Object.observe(record, enable_dot_assignments);
record['foo.bar.baz'] = 99;
Beware, however, that such assignments will be asynchronous, which may or may not work for you. To solve this, call Object.deliverChangeRecords immediately after the assignment. Or, although not as syntactically pleasing, you could write a helper function, also setting up the observer:
function dot_assignment(object, path, value) {
Object.observe(object, enable_dot_assignments);
object[path] = value;
Object.deliverChangeRecords(enable_dot_assignments);
}
dot_assignment(record, 'foo.bar.baz', 99);

Something like this example perhaps. It will extend a supplied object or create one if it no object is supplied. It is destructive in nature, if you supply keys that already exist in the object, but you can change that if that is not what you want. Uses ECMA5.
/*global console */
/*members split, pop, reduce, trim, forEach, log, stringify */
(function () {
'use strict';
function isObject(arg) {
return arg && typeof arg === 'object';
}
function convertExtend(arr, obj) {
if (!isObject(obj)) {
obj = {};
}
var str = arr[0],
last = obj,
props,
valProp;
if (typeof str === 'string') {
props = str.split('.');
valProp = props.pop();
props.reduce(function (nest, prop) {
prop = prop.trim();
last = nest[prop];
if (!isObject(last)) {
nest[prop] = last = {};
}
return last;
}, obj);
last[valProp] = arr[1];
}
return obj;
}
var x = ['fum'],
y = [
['foo.bar.baz', 1],
['foo.bar.fum', new Date()],
['qux.qaz', 'abc'],
['foshizzle', 200]
],
z = ['qux.qux', null],
record = convertExtend(x);
y.forEach(function (yi) {
convertExtend(yi, record);
});
convertExtend(z, record);
document.body.textContent = JSON.stringify(record, function (key, value, Undefined) {
/*jslint unparam:true */
/*jshint unused:false */
if (value === Undefined) {
value = String(value);
}
return value;
});
}());

it's an old question, but if anyone still looking for a solution can try this
function restructureObject(object){
let result = {};
for(let key in object){
const splittedKeys = key.split('.');
if(splittedKeys.length === 1){
result[key] = object[key];
}
else if(splittedKeys.length > 2){
result = {...result, ...{[splittedKeys.splice(0,1)]: {}} ,...restructureObject({[splittedKeys.join('.')]: object[key]})}
}else{
result[splittedKeys[0]] = {[splittedKeys[1]]: object[key]}
}
}
return result
}

Related

How to convert a string with dot notation and an index into a valid object [duplicate]

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)
}

JavaScript: Access object field that has dot as key [duplicate]

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)
}

(Don't) split string and use as object property accessor [duplicate]

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)
}

Convert a JavaScript string in dot notation into an object reference

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 to determine if object is in array [duplicate]

This question already has answers here:
How do I check if an array includes a value in JavaScript?
(60 answers)
Closed 29 days ago.
I need to determine if an object already exists in an array in javascript.
eg (dummycode):
var carBrands = [];
var car1 = {name:'ford'};
var car2 = {name:'lexus'};
var car3 = {name:'maserati'};
var car4 = {name:'ford'};
carBrands.push(car1);
carBrands.push(car2);
carBrands.push(car3);
carBrands.push(car4);
now the "carBrands" array contains all instances.
I'm now looking a fast solution to check if an instance of car1, car2, car3 or car4 is already in the carBrands array.
eg:
var contains = carBrands.Contains(car1); //<--- returns bool.
car1 and car4 contain the same data but are different instances they should be tested as not equal.
Do I have add something like a hash to the objects on creation? Or is there a faster way to do this in Javascript.
I am looking for the fastest solution here, if dirty, so it has to be ;) In my app it has to deal with around 10000 instances.
no jquery
Use something like this:
function containsObject(obj, list) {
var i;
for (i = 0; i < list.length; i++) {
if (list[i] === obj) {
return true;
}
}
return false;
}
In this case, containsObject(car4, carBrands) is true. Remove the carBrands.push(car4); call and it will return false instead. If you later expand to using objects to store these other car objects instead of using arrays, you could use something like this instead:
function containsObject(obj, list) {
var x;
for (x in list) {
if (list.hasOwnProperty(x) && list[x] === obj) {
return true;
}
}
return false;
}
This approach will work for arrays too, but when used on arrays it will be a tad slower than the first option.
Why don't you use the indexOf method of javascript arrays?
Check this out: MDN indexOf Arrays
Simply do:
carBrands.indexOf(car1);
It will return you the index (position in the array) of car1. It will return -1 if car1 was not found in the array.
http://jsfiddle.net/Fraximus/r154cd9o
Edit: Note that in the question, the requirements are to check for the same object referenced in the array, and NOT a new object. Even if the new object is identical in content to the object in the array, it is still a different object.
As mentioned in the comments, objects are passed by reference in JS and the same object can exist multiple times in multiple structures.
If you want to create a new object and check if the array contains objects identical to your new one, this answer won't work (Julien's fiddle below), if you want to check for that same object's existence in the array, then this answer will work. Check out the fiddles here and in the comments.
Having been recently bitten by the FP bug reading many wonderful accounts of how neatly the functional paradigm fits with Javascript
I replicate the code for completeness sake and suggest two ways this can be done functionally.
var carBrands = [];
var car1 = {name:'ford'};
var car2 = {name:'lexus'};
var car3 = {name:'maserati'};
var car4 = {name:'ford'};
var car5 = {name:'toyota'};
carBrands.push(car1);
carBrands.push(car2);
carBrands.push(car3);
carBrands.push(car4);
// ES6 approach which uses the includes method (Chrome47+, Firefox43+)
carBrands.includes(car1) // -> true
carBrands.includes(car5) // -> false
If you need to support older browsers use the polyfill, it seems IE9+ and Edge do NOT support it. Located in polyfill section of MSDN page
Alternatively I would like to propose an updated answer to cdhowie
// ES2015 syntax
function containsObject(obj, list) {
return list.some(function(elem) {
return elem === obj
})
}
// or ES6+ syntax with cool fat arrows
function containsObject(obj, list) {
return list.some(elem => elem === obj)
}
try Array.prototype.some()
MDN Array.prototype.some
function isBiggerThan10(element, index, array) {
return element > 10;
}
[2, 5, 8, 1, 4].some(isBiggerThan10); // false
[12, 5, 8, 1, 4].some(isBiggerThan10); // true
You could use jQuery's grep method:
$.grep(carBrands, function(obj) { return obj.name == "ford"; });
But as you specify no jQuery, you could just make a derivative of the function. From the source code:
function grepArray( elems, callback, inv ) {
var ret = [];
// Go through the array, only saving the items
// that pass the validator function
for ( var i = 0, length = elems.length; i < length; i++ ) {
if ( !inv !== !callback( elems[ i ], i ) ) {
ret.push( elems[ i ] );
}
}
return ret;
}
grepArray(carBrands, function(obj) { return obj.name == "ford"; });
I used underscore javascript library to tweak this issue.
function containsObject(obj, list) {
var res = _.find(list, function(val){ return _.isEqual(obj, val)});
return (_.isObject(res))? true:false;
}
please refer to underscore.js documentation for the underscore functions used in the above example.
note: This is not a pure javascript solution. Shared for educational purposes.
You can just use the equality operator: ==. Objects are checked by reference by default, so you don't even need to use the === operator.
try this, just make sure you're using the correct variable reference in the place of car1:
var i, car, l = cars.length;
for (i = 0; i < l; i++)
{
if ((car = cars[i]) == car1)
{
break;
}
else car = null;
}
Edit to add:
An array extension was mentioned, so here's the code for it:
Array.prototype.contains = Array.prototype.contains || function(obj)
{
var i, l = this.length;
for (i = 0; i < l; i++)
{
if (this[i] == obj) return true;
}
return false;
};
Note that I'm caching the length value, as the Array's length property is actually an accessor, which is marginally slower than an internal variable.
I would use a generic iterator of property/value over the array. No jQuery required.
arr = [{prop1: 'val1', prop2: 'val2'}, {prop1: 'val3', prop2: 'val4'}];
objectPropInArray(arr, 'prop1', 'val3'); // <-- returns true
function objectPropInArray(list, prop, val) {
if (list.length > 0 ) {
for (i in list) {
if (list[i][prop] === val) {
return true;
}
}
}
return false;
}
You could try sorting the array based on a property, like so:
carBrands = carBrands.sort(function(x,y){
return (x == y) ? 0 : (x > y) ? 1 : -1;
});
Then you can use an iterative routine to check whether
carBrands[Math.floor(carBrands.length/2)]
// change carBrands.length to a var that keeps
// getting divided by 2 until result is the target
// or no valid target exists
is greater or lesser than the target, and so on, which will let you go through the array quickly to find whether the object exists or not.
try this ,
You can use the JavaScript some() method to find out if a JavaScript array contains an object.
<script>
// An array of objects
var persons = [{name: "Harry"}, {name: "Alice"}, {name: "Peter"}];
// Find if the array contains an object by comparing the property value
if(persons.some(person => person.name === "Peter")){
alert("Object found inside the array.");
} else{
alert("Object not found.");
}
</script>
EDIT 05/18/2022
The most simple way using ES6:
const arrayContainsObject = <T extends Record<string, unknown>>(array: T[], object: T) => {
return array.some(item => Object.keys(item).every(key => item[key] === object[key]))
}
Use like so:
const arr = [{
prop1: 'value1',
prop2: 'value2'
}]
const obj1 = {
prop1: 'value1',
prop2: 'value2'
}
const obj2 = {
prop2: 'value2',
prop1: 'value1'
}
const obj3 = {
prop0: 'value0',
prop1: 'value1'
}
arrayContainsObject(arr, obj1) // true
arrayContainsObject(arr, obj2) // true, even when props are arranged in different order
arrayContainsObject(arr, obj3) // false
Previous answer, don't use (because the order of props in an object needs to be identical)
const arr = [{
prop: 'value'
}]
const obj = {
prop: 'value'
}
arr.some((e) => Object.entries(e).toString() === Object.entries(obj).toString()) // true
i know this is an old post, but i wanted to provide a JQuery plugin version and my code.
// Find the first occurrence of object in list, Similar to $.grep, but stops searching
function findFirst(a,b){
var i; for (i = 0; i < a.length; ++i) { if (b(a[i], i)) return a[i]; } return undefined;
}
usage:
var product = $.findFirst(arrProducts, function(p) { return p.id == 10 });
This function is to check for a unique field.
Arg 1: the array with selected data
Arg 2: key to check
Arg 3: value that must be "validated"
function objectUnique( array, field, value )
{
var unique = true;
array.forEach(function ( entry )
{
if ( entry[field] == value )
{
unique = false;
}
});
return unique;
}
you can use Array.find().
in your case is going to look like this
carBrands.find(function(car){
let result = car.name === 'ford'
if (result == null){
return false;
} else {
return true
}
});
if car is not null it will return the javaScript Object which contains the string 'ford'
The issue with many of the answers here is that they will NOT find an object in an array that is equal to another object. They will only search for an EXISTING object that has a pointer to it in an array.
Quick fix using lodash to see if ANY equal object is in an array:
import _ from 'lodash';
_.find(carBrands, car1); //returns object if true, undefined if false
Working Plunker using this method: https://plnkr.co/edit/y2YX9o7zkQa2r7lJ
if its possible to use es6
carBrands.filter(carBrand => carBrand.name === carX.name).length > 0
if it's true there is a similarity
You can convert both the JSON objects to string and simply check if the bigger json contains the smaller json.
console.log(JSON.stringify(carBrands).includes(JSON.stringify(car1))); // true
console.log(JSON.stringify(carBrands).includes(JSON.stringify(car5))); // false
You could also a the findIndex
var carBrands = [];
var car1 = {name:'ford'};
var car2 = {name:'lexus'};
carBrands.push(car1);
if (carBrands.findIndex(f => f.name === car1.name) === -1) {
console.log('not contain')
} else {
console.log('contain')
}
if (carBrands.findIndex(f => f.name === car2.name) === -1) {
console.log('not contain')
} else {
console.log('contain')
}

Categories

Resources