318

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
nevf
  • 4,596
  • 6
  • 31
  • 32
  • 41
    @Andrey `eval` is evil; don't use it – Kevin Ji Jun 18 '11 at 04:49
  • 5
    FYI: Here are some interesting speed tests I just did: http://jsperf.com/dereference-object-property-path-from-string – James Wilkins Oct 30 '13 at 05:35
  • if perf is a serious consideration and you're reusing the same paths a lot (e.g. inside an array filter function), use the Function constructor as described in my answer below. When the same path is used thousands of times, the Function method can be more than 10x as fast as evaling or splitting and reducing the path on every dereference. – Kevin May 20 '15 at 22:25
  • related: [Fastest way to flatten / un-flatten nested JSON objects](http://stackoverflow.com/q/19098797/1048572) – Bergi Dec 09 '15 at 22:09
  • 1
    See also [Accessing nested JavaScript objects with string key](http://stackoverflow.com/q/6491463/1048572) – Bergi Dec 15 '15 at 12:45
  • 1
    there are just some cases where you HAVE to use eval, or new Function(), one especially, when you want to create a function from a template, just like JSP pages are converted to JAVA, there isn't a more efficient way to do templates, this eval is evil dogma, is that, just a dogma, what IS evil is to eval a script you have not created yourself, of course in this precise case there is no reason to use eval – Martijn Scheffer Nov 02 '17 at 14:26
  • Does this answer your question? [Accessing nested JavaScript objects and arrays by string path](https://stackoverflow.com/questions/6491463/accessing-nested-javascript-objects-and-arrays-by-string-path) – Rafael Tavares Sep 22 '21 at 14:21

34 Answers34

592

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)...:

  1. 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.

  2. 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.)

ninjagecko
  • 88,546
  • 24
  • 137
  • 145
  • 4
    `reduce` is not supported in all currently used browsers. – Ricardo Tomasi Jun 18 '11 at 06:01
  • 17
    @Ricardo: `Array.reduce` is part of the ECMA-262 standard. If you really wish to support outdated browsers, you can define `Array.prototype.reduce` to the sample implementation given somewhere (e.g. https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/Reduce#Compatibility ). – ninjagecko Jun 18 '11 at 06:12
  • 1
    @ninjagecko - a different approach. What about assigning a new value, as @tylermwashburn & @CD Sanchez have shown. – nevf Jun 18 '11 at 07:03
  • @ninjagecko - forget the assignment question. I have worked it out. A very interesting approach indeed. – nevf Jun 18 '11 at 07:11
  • But you have to include the `.split('.').reduce(index, obj)` every time. Usually the goal isn't to reduce as much code as possible, it's to make it easy to use. The code the user has to use to get the value is longer than your "one liner". This definitely isn't easy to use. – McKayla Jun 18 '11 at 07:29
  • 2
    Yes but it's easy enough to put the two lines into a function. `var setget = function( obj, path ){ function index( robj,i ) {return robj[i]}; return path.split('.').reduce( index, obj ); } ` – nevf Jun 18 '11 at 07:59
  • What nevf said. =) You can always use functions to abstract, and in fact I was expecting people would do so when I wrote the answer. – ninjagecko Jun 18 '11 at 08:31
  • It's a shame any benefit from the brevity of this solution is practically nil due to the requirement to include the rather large ES5 shim for reduce. That is, if you want your application to work for a non-trivial percentage of the population... – Cristian Sanchez Jun 18 '11 at 08:36
  • 3
    I love this elegant example, thanks ninjagecko. I've extended it to handle array style notation, as well as empty strings - see my example here: http://jsfiddle.net/sc0ttyd/q7zyd/ – Sc0ttyD Jan 18 '13 at 13:17
  • 2
    @ninjagecko 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 '13 at 21:42
  • 1
    @Sc0ttyD That's great, thank you! One thing: I'd put the _string_to_ref()_ return line into a try/catch block, so any failure to find a value returns _undefined_, e.g. `try { var value = reference.split('.').reduce(dot_deref, object); } catch(err) { return undefined; } return value;` – chichilatte Sep 25 '13 at 14:05
  • This is the best answer if you're not using the same dereference paths over and over, or if you're reluctant to trust the integrity of the "deref path" variable. If you need to use this code in a hot spot, like inside an array filter, you might want to try one of the Function constructor methods below. – Kevin May 20 '15 at 22:13
  • 1
    "If one needs to convert dot-notation strings like "x.a.b.c" into references, it's probably a sign that there is something very wrong going on" -- A sign, maybe, but not always. Dots are allowed in dom node ids, and to be able to wire into them their position within a json packet for instance can be a godsend. In a project in the last half of 2015 I was able to use just this technique to populate values and save data store values with just a couple of 10-15/line functions -- very useful for having approximately 100 total fields. (this was with no framework, just vanilla + jQuery (+UI) – Dexygen Sep 29 '17 at 13:49
  • Brillant answer. But in case we're not sure that the key exists in the object, it's worth having an additional check instead of returning undefined which would throw an error at the next iteration. This will do the trick `key.split('.').reduce((o,i)=>o[i] || '', dict)` – Christophe Vidal Nov 01 '18 at 07:23
  • @ChristopheVidal: I don't think you'd want `''`, but I added a section to address the concern. – ninjagecko Nov 12 '18 at 07:52
  • Hi @ninjagecko, great analysis and piece of code. Though, the line `return obj[is[0]] = value;` is not eslint-valid : `error: Return statement should not contain assignment (no-return-assign)` – Maxime Freschard Mar 08 '19 at 15:41
  • We should stop estimate usefulness of answers because we never know. This technique may be tremendiously useful in query string of URL. Just as example. –  May 26 '19 at 14:15
  • @ninjagecko I agree with your - aaggh, why are people doing this? I want it for some generic tests - being able to list some test cases like 'foo.bar.baz' in some test data is clearer to me than alternatives. – Marvin Mar 19 '20 at 14:29
  • @ChristopheVidal: What do you think of the approach to have two methods. One will return the typescript error, when trying to access something which doesn't exist, and the safer one (e.g. safeGetFromNestedObject(...)) which has a try/catch and returns undefined if an error occured? `try { return this.getFromNestedObject(object, path); } catch { return undefined };` – user2622344 Oct 03 '21 at 08:38
  • Don't be horrified. There are definite use-cases for your one-liner. I just used it to take column definitions passed into a grid display component in Vue.js. Works perfectly. – Jamie Carl Dec 07 '22 at 05:49
107

If you can use Lodash, there is a function, which does exactly that:

_.get(object, path, [defaultValue])

var val = _.get(obj, "a.b");
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Petar Ivanov
  • 91,536
  • 11
  • 82
  • 95
  • 5
    Note: `_.get(object, path)` doesn't break if a path wasn't found. `'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)` does. For my specific case - not for every case - exactly what I needed. Thanks! – Mr. B. Nov 03 '16 at 17:52
  • 1
    @Mr.B. the latest version of Lodash has a third, optional, argument for `defaultValue`. The `_.get()` method returns the default value if `_.get()` resolves to `undefined`, so set it to whatever you want and watch for the value you set. – steampowered Apr 21 '17 at 21:35
  • 17
    For anyone wondering, it also supports `_.set(object, path, value)`. – Jeffrey Roosendaal May 30 '17 at 09:55
  • JavaScript have more idioms now, you can prevent object access from breaking by using elvis operator `?.`: 'a.b.etc'.split('.').reduce((o,i)=>o?.[i], obj) – Michael Buen Aug 02 '22 at 09:44
31

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';
Lee Goddard
  • 10,680
  • 4
  • 46
  • 63
DarkCrazy
  • 527
  • 5
  • 6
26

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
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • 1
    It's a wonderful new world we live in. I continue to be impressed that after all these years people are still responding to my initial post. – nevf Oct 06 '21 at 12:07
  • 1
    amazing bro, but, how about set new value, can you? – Alauddin Afif Cassandra Feb 08 '22 at 07:07
  • Could definitely use a "set", but my brain breaks trying to figure it out. – tempranova Mar 04 '22 at 20:20
  • Set function wouldn't be that difficult. Something like: const setByDotNotation = (object, notation, value) => { notation = notation.split("."); while (notation.length > 1) { object = object[notation.shift()]; } object[notation[0]] = value; }; – floodlitworld Aug 02 '22 at 01:56
25

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
mplungjan
  • 169,008
  • 28
  • 173
  • 236
joekarl
  • 2,118
  • 14
  • 23
15

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'));
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
11

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.

LPG
  • 822
  • 1
  • 11
  • 20
10

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");
Kevin
  • 5,874
  • 3
  • 28
  • 35
  • 1
    if you need to dereference the same paths a long time apart, the jsperf.com link above shows an example of how to save and look up the function later. The act of calling the Function constructor is fairly slow, so high-perf code should memoize the results to avoid repeating it if possible. – Kevin May 20 '15 at 22:06
7

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] } }
Ricardo Tomasi
  • 34,573
  • 2
  • 55
  • 66
  • An explanation would be in order. Please respond by [editing (changing) your answer](https://stackoverflow.com/posts/6394197/edit), not here in comments (***without*** "Edit:", "Update:", or similar - the answer should appear as if it was written today). – Peter Mortensen Aug 22 '21 at 15:27
  • @PeterMortensen you're about ten years late. I don't think this answer is particularly good anymore either way. – Ricardo Tomasi Jun 13 '22 at 15:16
5

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Tamlyn
  • 22,122
  • 12
  • 111
  • 127
5

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!
Jeremias Santos
  • 377
  • 3
  • 5
5
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.

McKayla
  • 6,879
  • 5
  • 36
  • 48
4

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/

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Sc0ttyD
  • 1,576
  • 1
  • 14
  • 22
  • Really good solution. There is only one problem, it assumes `[]` notation is always for arrays. there can be object keys represented that way as well for example `obj['some-problem/name'].list[1]` To fix this I had to update `arr_deref` function like this ```javascript function arr_deref(o, ref, i) { return !ref ? o : (o[(ref.slice(0, i ? -1 : ref.length)).replace(/^['"]|['"]$/g, '')]); } ``` – Serkan Yersen Aug 06 '15 at 22:52
  • 1
    Although, nowadays, I would not do this. I'd use Lodash: https://lodash.com/docs#get – Sc0ttyD Aug 10 '15 at 13:25
3

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.

Harish Ambady
  • 12,525
  • 4
  • 29
  • 54
  • Can you explain precisely what this is doing? And how would you pass 'a.b' etc. in as a function parameter? – nevf Apr 12 '15 at 21:20
  • 1
    I gave this a +1 but JSLint warns that ["the Function constructor is a form of eval"](https://jslinterrors.com/the-function-constructor-is-eval). – gabe Aug 20 '15 at 17:48
3

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
    }
  }
}
Allef Douglas
  • 381
  • 2
  • 4
2

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 ..

starball
  • 20,030
  • 7
  • 43
  • 238
Zalaboza
  • 8,899
  • 16
  • 77
  • 142
2
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
Lebovski
  • 166
  • 1
  • 10
Cristian Sanchez
  • 31,171
  • 11
  • 57
  • 63
  • Thanks for all the quick responses. Don't like the eval() solutions. This and the similar posts looks best. However I'm still having a problem. I am trying to set the value obj.a.b = new value. To be precise b's value is a function so I need to use obj.a.b( new_value ). The function is called but the value isn't set. I think it's a scope issue but I'm still digging. I realize this is outside of the scope of the original question. My code is using Knockout.js and b is an ko.observable. – nevf Jun 18 '11 at 05:16
  • @nevf: I added a second function that I think does what you want. You can customize it to your liking depending on the behavior you want (e.g. should it create the objects if they do not exist?, etc.). – Cristian Sanchez Jun 18 '11 at 05:25
  • @nevf But mine does it with one function. ;D – McKayla Jun 18 '11 at 05:32
  • thanks for the update which I was able to use. @tylermwashburn - and thanks for your shorter implementation which also works a treat. Have a great w/e all. – nevf Jun 18 '11 at 06:59
1

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]
 }
Community
  • 1
  • 1
Chris V.
  • 96
  • 2
  • 7
1

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
allenhwkim
  • 27,270
  • 18
  • 89
  • 122
1

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

}
Matthew Mullin
  • 7,116
  • 4
  • 21
  • 35
1

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

holm50
  • 821
  • 8
  • 7
1

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) 
Kuza Grave
  • 1,256
  • 14
  • 15
0

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
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Alvin Magalona
  • 771
  • 3
  • 13
0

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;
};
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • Is it actually about Next.js? If not, what does "next extension" refer to? – Peter Mortensen Aug 22 '21 at 15:35
  • Hey @PeterMortensen. It's been a while. I can't remember the situation at all. From the context: most likely next.js is extending object prototype with some property. That property is enumerable by default and it breaks some other libraries. Or, at least it was true 6 years ago – Michael Kapustey Feb 14 '22 at 13:55
0

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'
Xeltor
  • 4,626
  • 3
  • 24
  • 26
0

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)
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Yabree
  • 231
  • 2
  • 3
0

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
0

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.

vincent
  • 1,953
  • 3
  • 18
  • 24
0

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));
CodeDreamer68
  • 420
  • 5
  • 10
0

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));
DiD
  • 173
  • 8
-1

At the risk of beating a dead horse... I find this most useful in traversing nested objects to reference where you're at with respect to the base object or to a similar object with the same structure. To that end, this is useful with a nested object traversal function. Note that I've used an array to hold the path. It would be trivial to modify this to use either a string path or an array. Also note that you can assign "undefined" to the value, unlike some of the other implementations.

/*
 * Traverse each key in a nested object and call fn(curObject, key, value, baseObject, path)
 * on each. The path is an array of the keys required to get to curObject from
 * baseObject using objectPath(). If the call to fn() returns falsey, objects below
 * curObject are not traversed. Should be called as objectTaverse(baseObject, fn).
 * The third and fourth arguments are only used by recursion.
 */
function objectTraverse (o, fn, base, path) {
    path = path || [];
    base = base || o;
    Object.keys(o).forEach(function (key) {
        if (fn(o, key, o[key], base, path) && jQuery.isPlainObject(o[key])) {
            path.push(key);
            objectTraverse(o[key], fn, base, path);
            path.pop();
        }
    });
}

/*
 * Get/set a nested key in an object. Path is an array of the keys to reference each level
 * of nesting. If value is provided, the nested key is set.
 * The value of the nested key is returned.
 */
function objectPath (o, path, value) {
    var last = path.pop();

    while (path.length && o) {
        o = o[path.shift()];
    }
    if (arguments.length < 3) {
        return (o? o[last] : o);
    }
    return (o[last] = value);
}
srkleiman
  • 607
  • 8
  • 16
-1

It's not clear what your question is. Given your object, obj.a.b would give you "2" just as it is. If you wanted to manipulate the string to use brackets, you could do this:

var s = 'a.b';
s = 'obj["' + s.replace(/\./g, '"]["') + '"]';
alert(s); // displays obj["a"]["b"]
Mark Eirich
  • 10,016
  • 2
  • 25
  • 27
  • 1
    This doesn't work for `a.b.c`, and doesn't really accomplish what they want.. They want the value, not an `eval` path. – McKayla Jun 18 '11 at 05:35
  • I now fixed it so it works with `a.b.c`, but you are right, apparently he wanted to get/set the value of the property at `obj.a.b`. The question was confusing to me, since he said he wanted to "convert the string".... – Mark Eirich Jun 18 '11 at 05:48
-1

If you want to do this in the fastest possible way, while at the same time handling any issues with the path parsing or property resolution, check out path-value.

const {resolveValue} = require('path-value');

const value = resolveValue(obj, 'a.b.c');

The library is 100% TypeScript, works in NodeJS + all web browsers. And it is fully extendible, you can use lower-level resolvePath, and handle errors your own way, if you want.

const {resolvePath} = require('path-value');

const res = resolvePath(obj, 'a.b.c'); //=> low-level parsing result descriptor
vitaly-t
  • 24,279
  • 15
  • 116
  • 138
-1

Wanted to put this one out there:

function propertyByPath(object, path = '') {
    if (/[,(){}&|;]/.test(path)) {
        throw 'forbidden characters in path';
    }
    return Function(
      ...Object.keys(window).filter(k => window[k] instanceof Window || window[k] instanceof Document),
      "obj",
      `return ((o) => o${!path.startsWith('[') ? '.' : ''}${path})(...arguments, obj);`)
    .bind(object)(object);
}
propertyByPath({ a: { b: 'hello1' } }, "a.b"); // prints 'hello1'
propertyByPath({ a: { b: 'hello2' } }, "['a']?.b"); // returns 'hello2'
propertyByPath({ a: { b: 'hello2' } }, "a.b;console.log()"); // throws exception

The above code evaluates the path while doing an effort to prevent code execution while doing the path evaluation and masking Window and Document objects if somehow the execution prevention didn't succeeded.

I'm not saying it's 100% safe (though I'd love to see comments suggesting possible bypasses) nor efficient, but it might be suitable in cases where the flexibility of the path (optional chaining, etc.) along with some mitigations is needed.

Arik
  • 5,266
  • 1
  • 27
  • 26