Accessing nested arrays/properties in javascript - javascript

The utility function from this answer allows to easily access nested properties of objects and returns null (or undefined) if one of the parent properties does not exist.
original Code:
get = function(obj, key) {
return key.split(".").reduce(function(o, x) {
return (typeof o == "undefined" || o === null) ? o : o[x];
}, obj);
}
get(user, 'loc.lat') // 50
get(user, 'loc.foo.bar') // undefined
I really want to use this, but i need to be able to work with nested arrays as well.
Examples:
var childArray = [0,1,2]
var parentArray = [{myArray: childArray}]
var obj = {key: parentArray}
I want to extend the utility function like this:
get(obj, 'key[0].myArray[2]'); // 2
get(obj, 'key[0].foo[2]'); // null
get(obj, 'key[0].myArray[42]'); // null
And ideally it should also be able to evaluate this as well
var childArray = [0,1,2]
var parentArray = [childArray, childArray]
var obj = {key: parentArray}
get(obj, 'key[1][0]'); // 0
get(obj, 'foo[1][0]'); // null
Question:
Is it possible to access an array arr with a given string-reference like "arr[0]" (without regex to remove the brackets...)?
Do you know a more elegant solution that achieves the result presented in the examples above?

The easiest way would be to change the path/key you're passing to get()
From
get(obj, 'key[0].myArray[2]');
To
get(obj, 'key.0.myArray.2');
var get = function(obj, key) {
return key.split(".").reduce(function(o, x) {
return (typeof o == "undefined" || o === null) ? o : o[x];
}, obj);
}
var childArray = [0,1,2]
var parentArray = [{myArray: childArray}]
var obj = {key: parentArray}
console.log(get(obj, 'key.0.myArray.2')); // 2
console.log(get(obj, 'key.0.foo.2')); // null
console.log(get(obj, 'key.0.myArray.42')); // null
var childArray2 = [0,1,2]
var parentArray2 = [childArray2, childArray2]
var obj2 = {key: parentArray2}
console.log(get(obj2, 'key.1.0')); // 0
console.log(get(obj2, 'foo.1.0')); // null

With an invention of Object.prototype.getNestedValue() you can dynamically access deeply nested values through object properties and array indices. All you have to do is to provide the nested properties and indices dynamically as arguments in the correct order.
Object.prototype.getNestedValue = function(...a) {
return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};
var arr = [{fox: [{turn:[857, 432]}]}, {sax: [{pana:[777, 987]}]}, {ton: [{joni:[123, 567]}]}, {piu: [{burn:[666, 37]}]}, {sia: [{foxy:[404, 696]}]}],
myObj = { foo : 1, bar: { baz : 2 }, bee : 3 },
arg1 = 3,
arg2 = "piu",
arg3 = 0,
arg4 = "burn",
arg5 = 1;
document.write(arr.getNestedValue(arg1,arg2,arg3,arg4,arg5));

I would instead use an array of property names, which would allow you easy recursion.
But if you want to go back to your original pattern, you could still transform your expression into this array.
Have a look on this example:
var innerget = function(object, proparr) {
if(proparr.length == 0)
return object;
if(typeof object == "undefined" || object === null)
return object;
return innerget(object[proparr.splice(0, 1)], proparr);
};
var get = function(object, expr) {
var proparr = expr.replace(/\[/g, ".").replace(/\]/g, "").split(".");
return innerget(object, proparr);
}
var object = {
key: [[1, 2, 3], [4, 5, 6]]
};
document.write(innerget(object, ['key', 'key2', 2]) + "<br>");
document.write(innerget(object, ['key', 0, 2]) + "<br>");
document.write(get(object, "key[0][1]"));

Related

Js how to set an item at a nested index in an empty array [duplicate]

I hope someone can help me with this Javascript.
I have an Object called "Settings" and I would like to write a function that adds new settings to that object.
The new setting's name and value are provided as strings. The string giving the setting's name is then split by the underscores into an array. The new setting should get added to the existing "Settings" object by creating new nested objects with the names given by each part of the array, except the last part which should be a string giving the setting's value. I should then be able to refer to the setting and e.g. alert its value. I can do this in a static way like this...
var Settings = {};
var newSettingName = "Modules_Video_Plugin";
var newSettingValue = "JWPlayer";
var newSettingNameArray = newSettingName.split("_");
Settings[newSettingNameArray[0]] = {};
Settings[newSettingNameArray[0]][newSettingNameArray[1]] = {};
Settings[newSettingNameArray[0]][newSettingNameArray[1]][newSettingNameArray[2]] = newSettingValue;
alert(Settings.Modules.Mediaplayers.Video.Plugin);
... the part that creates the nested objects is doing this ...
Settings["Modules"] = {};
Settings["Modules"]["Video"] = {};
Settings["Modules"]["Video"]["Plugin"] = "JWPlayer";
However, as the number of parts that make up the setting name can vary, e.g. a newSettingName could be "Modules_Floorplan_Image_Src", I'd like to do this dynamically using a function such as...
createSetting (newSettingNameArray, newSettingValue);
function createSetting(setting, value) {
// code to create new setting goes here
}
Can anyone help me work out how to do this dynamically?
I presume there has to be a for...loop in there to itterate through the array, but I haven't been able to work out a way to create the nested objects.
If you've got this far thanks very much for taking the time to read even if you can't help.
Put in a function, short and fast (no recursion).
var createNestedObject = function( base, names ) {
for( var i = 0; i < names.length; i++ ) {
base = base[ names[i] ] = base[ names[i] ] || {};
}
};
// Usage:
createNestedObject( window, ["shapes", "triangle", "points"] );
// Now window.shapes.triangle.points is an empty object, ready to be used.
It skips already existing parts of the hierarchy. Useful if you are not sure whether the hierarchy was already created.
Or:
A fancier version where you can directly assign the value to the last object in the hierarchy, and you can chain function calls because it returns the last object.
// Function: createNestedObject( base, names[, value] )
// base: the object on which to create the hierarchy
// names: an array of strings contaning the names of the objects
// value (optional): if given, will be the last object in the hierarchy
// Returns: the last object in the hierarchy
var createNestedObject = function( base, names, value ) {
// If a value is given, remove the last name and keep it for later:
var lastName = arguments.length === 3 ? names.pop() : false;
// Walk the hierarchy, creating new objects where needed.
// If the lastName was removed, then the last object is not set yet:
for( var i = 0; i < names.length; i++ ) {
base = base[ names[i] ] = base[ names[i] ] || {};
}
// If a value was given, set it to the last name:
if( lastName ) base = base[ lastName ] = value;
// Return the last object in the hierarchy:
return base;
};
// Usages:
createNestedObject( window, ["shapes", "circle"] );
// Now window.shapes.circle is an empty object, ready to be used.
var obj = {}; // Works with any object other that window too
createNestedObject( obj, ["shapes", "rectangle", "width"], 300 );
// Now we have: obj.shapes.rectangle.width === 300
createNestedObject( obj, "shapes.rectangle.height".split('.'), 400 );
// Now we have: obj.shapes.rectangle.height === 400
Note: if your hierarchy needs to be built from values other that standard objects (ie. not {}), see also TimDog's answer below.
Edit: uses regular loops instead of for...in loops. It's safer in cases where a library modifies the Array prototype.
function assign(obj, keyPath, value) {
lastKeyIndex = keyPath.length-1;
for (var i = 0; i < lastKeyIndex; ++ i) {
key = keyPath[i];
if (!(key in obj)){
obj[key] = {}
}
obj = obj[key];
}
obj[keyPath[lastKeyIndex]] = value;
}
Usage:
var settings = {};
assign(settings, ['Modules', 'Video', 'Plugin'], 'JWPlayer');
My ES2015 solution. Keeps existing values.
const set = (obj, path, val) => {
const keys = path.split('.');
const lastKey = keys.pop();
const lastObj = keys.reduce((obj, key) =>
obj[key] = obj[key] || {},
obj);
lastObj[lastKey] = val;
};
Example:
const obj = {'a': {'prop': {'that': 'exists'}}};
set(obj, 'a.very.deep.prop', 'value');
console.log(JSON.stringify(obj));
// {"a":{"prop":{"that":"exists"},"very":{"deep":{"prop":"value"}}}}
Using ES6 is shorten. Set your path into an array.
first, you have to reverse the array, to start filling the object.
let obj = ['a','b','c'] // {a:{b:{c:{}}}
obj.reverse();
const nestedObject = obj.reduce((prev, current) => (
{[current]:{...prev}}
), {});
Another recursive solution:
var nest = function(obj, keys, v) {
if (keys.length === 1) {
obj[keys[0]] = v;
} else {
var key = keys.shift();
obj[key] = nest(typeof obj[key] === 'undefined' ? {} : obj[key], keys, v);
}
return obj;
};
Example usage:
var dog = {bark: {sound: 'bark!'}};
nest(dog, ['bark', 'loudness'], 66);
nest(dog, ['woff', 'sound'], 'woff!');
console.log(dog); // {bark: {loudness: 66, sound: "bark!"}, woff: {sound: "woff!"}}
I love this ES6 immutable way to set certain value on nested field:
const setValueToField = (fields, value) => {
const reducer = (acc, item, index, arr) => ({ [item]: index + 1 < arr.length ? acc : value });
return fields.reduceRight(reducer, {});
};
And then use it with creating your target object.
const targetObject = setValueToField(['one', 'two', 'three'], 'nice');
console.log(targetObject); // Output: { one: { two: { three: 'nice' } } }
Lodash has a _.set method to achieve this
let obj = {}
_.set(obj, ['a', 'b', 'c', 'd'], 'e')
or
_.set(obj, 'a.b.c.d', 'e')
// which generate the following object
{
"a": {
"b": {
"c": {
"d": "e"
}
}
}
}
Here is a simple tweak to jlgrall's answer that allows setting distinct values on each element in the nested hierarchy:
var createNestedObject = function( base, names, values ) {
for( var i in names ) base = base[ names[i] ] = base[ names[i] ] || (values[i] || {});
};
Hope it helps.
Here is a functional solution to dynamically create nested objects.
const nest = (path, obj) => {
const reversedPath = path.split('.').reverse();
const iter = ([head, ...tail], obj) => {
if (!head) {
return obj;
}
const newObj = {[head]: {...obj}};
return iter(tail, newObj);
}
return iter(reversedPath, obj);
}
Example:
const data = {prop: 'someData'};
const path = 'a.deep.path';
const result = nest(path, data);
console.log(JSON.stringify(result));
// {"a":{"deep":{"path":{"prop":"someData"}}}}
Inspired by ImmutableJS setIn method which will never mutate the original. This works with mixed array and object nested values.
function setIn(obj = {}, [prop, ...rest], value) {
const newObj = Array.isArray(obj) ? [...obj] : {...obj};
newObj[prop] = rest.length ? setIn(obj[prop], rest, value) : value;
return newObj;
}
var obj = {
a: {
b: {
c: [
{d: 5}
]
}
}
};
const newObj = setIn(obj, ["a", "b", "c", 0, "x"], "new");
//obj === {a: {b: {c: [{d: 5}]}}}
//newObj === {a: {b: {c: [{d: 5, x: "new"}]}}}
Appreciate that this question is mega old! But after coming across a need to do something like this in node, I made a module and published it to npm.
Nestob
var nestob = require('nestob');
//Create a new nestable object - instead of the standard js object ({})
var newNested = new nestob.Nestable();
//Set nested object properties without having to create the objects first!
newNested.setNested('biscuits.oblong.marmaduke', 'cheese');
newNested.setNested(['orange', 'tartan', 'pipedream'], { poppers: 'astray', numbers: [123,456,789]});
console.log(newNested, newNested.orange.tartan.pipedream);
//{ biscuits: { oblong: { marmaduke: 'cheese' } },
orange: { tartan: { pipedream: [Object] } } } { poppers: 'astray', numbers: [ 123, 456, 789 ] }
//Get nested object properties without having to worry about whether the objects exist
//Pass in a default value to be returned if desired
console.log(newNested.getNested('generic.yoghurt.asguard', 'autodrome'));
//autodrome
//You can also pass in an array containing the object keys
console.log(newNested.getNested(['chosp', 'umbridge', 'dollar'], 'symbols'));
//symbols
//You can also use nestob to modify objects not created using nestob
var normalObj = {};
nestob.setNested(normalObj, 'running.out.of', 'words');
console.log(normalObj);
//{ running: { out: { of: 'words' } } }
console.log(nestob.getNested(normalObj, 'random.things', 'indigo'));
//indigo
console.log(nestob.getNested(normalObj, 'improbable.apricots'));
//false
Inside your loop you can use lodash.set and will create the path for you:
...
const set = require('lodash.set');
const p = {};
const [type, lang, name] = f.split('.');
set(p, [lang, type, name], '');
console.log(p);
// { lang: { 'type': { 'name': '' }}}
try using recursive function:
function createSetting(setting, value, index) {
if (typeof index !== 'number') {
index = 0;
}
if (index+1 == setting.length ) {
settings[setting[index]] = value;
}
else {
settings[setting[index]] = {};
createSetting(setting, value, ++index);
}
}
I think, this is shorter:
Settings = {};
newSettingName = "Modules_Floorplan_Image_Src";
newSettingValue = "JWPlayer";
newSettingNameArray = newSettingName.split("_");
a = Settings;
for (var i = 0 in newSettingNameArray) {
var x = newSettingNameArray[i];
a[x] = i == newSettingNameArray.length-1 ? newSettingValue : {};
a = a[x];
}
I found #jlgrall's answer was great but after simplifying it, it didn't work in Chrome. Here's my fixed should anyone want a lite version:
var callback = 'fn.item1.item2.callbackfunction',
cb = callback.split('.'),
baseObj = window;
function createNestedObject(base, items){
$.each(items, function(i, v){
base = base[v] = (base[v] || {});
});
}
callbackFunction = createNestedObject(baseObj, cb);
console.log(callbackFunction);
I hope this is useful and relevant. Sorry, I've just smashed this example out...
You can define your own Object methods; also I'm using underscore for brevity:
var _ = require('underscore');
// a fast get method for object, by specifying an address with depth
Object.prototype.pick = function(addr) {
if (!_.isArray(addr)) return this[addr]; // if isn't array, just get normally
var tmpo = this;
while (i = addr.shift())
tmpo = tmpo[i];
return tmpo;
};
// a fast set method for object, put value at obj[addr]
Object.prototype.put = function(addr, val) {
if (!_.isArray(addr)) this[addr] = val; // if isn't array, just set normally
this.pick(_.initial(addr))[_.last(addr)] = val;
};
Sample usage:
var obj = {
'foo': {
'bar': 0 }}
obj.pick('foo'); // returns { bar: 0 }
obj.pick(['foo','bar']); // returns 0
obj.put(['foo', 'bar'], -1) // obj becomes {'foo': {'bar': -1}}
A snippet for those who need to create a nested objects with support of array keys to set a value to the end of path. Path is the string like: modal.product.action.review.2.write.survey.data. Based on jlgrall version.
var updateStateQuery = function(state, path, value) {
var names = path.split('.');
for (var i = 0, len = names.length; i < len; i++) {
if (i == (len - 1)) {
state = state[names[i]] = state[names[i]] || value;
}
else if (parseInt(names[i+1]) >= 0) {
state = state[names[i]] = state[names[i]] || [];
}
else {
state = state[names[i]] = state[names[i]] || {};
}
}
};
Set Nested Data:
function setNestedData(root, path, value) {
var paths = path.split('.');
var last_index = paths.length - 1;
paths.forEach(function(key, index) {
if (!(key in root)) root[key] = {};
if (index==last_index) root[key] = value;
root = root[key];
});
return root;
}
var obj = {'existing': 'value'};
setNestedData(obj, 'animal.fish.pet', 'derp');
setNestedData(obj, 'animal.cat.pet', 'musubi');
console.log(JSON.stringify(obj));
// {"existing":"value","animal":{"fish":{"pet":"derp"},"cat":{"pet":"musubi"}}}
Get Nested Data:
function getNestedData(obj, path) {
var index = function(obj, i) { return obj && obj[i]; };
return path.split('.').reduce(index, obj);
}
getNestedData(obj, 'animal.cat.pet')
// "musubi"
getNestedData(obj, 'animal.dog.pet')
// undefined
Try this: https://github.com/silkyland/object-to-formdata
var obj2fd = require('obj2fd/es5').default
var fd = obj2fd({
a:1,
b:[
{c: 3},
{d: 4}
]
})
Result :
fd = [
a => 1,
b => [
c => 3,
d => 4
]
]
Here is a decomposition to several useful functions, that each preserve existing data. Does not handle arrays.
setDeep: Answers question. Non-destructive to other data in the object.
setDefaultDeep: Same, but only sets if not already set.
setDefault: Sets a key if not already set. Same as Python's setdefault.
setStructure: Helper function that builds the path.
// Create a nested structure of objects along path within obj. Only overwrites the final value.
let setDeep = (obj, path, value) =>
setStructure(obj, path.slice(0, -1))[path[path.length - 1]] = value
// Create a nested structure of objects along path within obj. Does not overwrite any value.
let setDefaultDeep = (obj, path, value) =>
setDefault(setStructure(obj, path.slice(0, -1)), path[path.length - 1], value)
// Set obj[key] to value if key is not in object, and return obj[key]
let setDefault = (obj, key, value) =>
obj[key] = key in obj ? obj[key] : value;
// Create a nested structure of objects along path within obj. Does not overwrite any value.
let setStructure = (obj, path) =>
path.reduce((obj, segment) => setDefault(obj, segment, {}), obj);
// EXAMPLES
let temp = {};
// returns the set value, similar to assignment
console.log('temp.a.b.c.d:',
setDeep(temp, ['a', 'b', 'c', 'd'], 'one'))
// not destructive to 'one'
setDeep(temp, ['a', 'b', 'z'], 'two')
// does not overwrite, returns previously set value
console.log('temp.a.b.z: ',
setDefaultDeep(temp, ['a', 'b', 'z'], 'unused'))
// creates new, returns current value
console.log('temp["a.1"]: ',
setDefault(temp, 'a.1', 'three'))
// can also be used as a getter
console.log("temp.x.y.z: ",
setStructure(temp, ['x', 'y', 'z']))
console.log("final object:", temp)
I'm not sure why anyone would want string paths:
They are ambiguous for keys with periods
You have to build the strings in the first place
Since I started with something from this page, I wanted to contribute back
Other examples overwrote the final node even if it was set, and that wasn't what I wanted.
Also, if returnObj is set to true, it returns the base object. By default, falsy, it returns the deepest node.
function param(obj, path, value, returnObj) {
if (typeof path == 'string') path = path.split(".");
var child = obj;
path.forEach((key, i) => {
if (!(key in child)) {
child[key] = (i < path.length-1) ? {} : value || {};
}
child = child[key];
});
return returnObj ? obj : child;
}
var x = {};
var xOut = param(x, "y.z", "setting")
console.log(xOut);
xOut = param(x, "y.z", "overwrite") // won't set
console.log(xOut);
xOut = param(x, "y.a", "setting2")
console.log(xOut);
xOut = param(x, "y.a", "setting2", true) // get object rather than deepest node.
console.log(xOut);
You can also do something where numeric keys are placed in arrays (if they don't already exist). Note that numeric keys won't convert to arrays for the first element of the path, since that's set by the type of your base-object.
function isNumber(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
function param(obj, path, value, returnObj) {
if (typeof path == 'string') path = path.split(".");
var child = obj;
path.forEach((key, i) => {
var nextKey = path[i+1];
if (!(key in child)) {
child[key] = (nextKey == undefined && value != undefined
? value
: isNumber(nextKey)
? []
: {});
}
child = child[key];
});
return returnObj ? obj : child;
}
var x = {};
var xOut = param(x, "y.z", "setting")
console.log(xOut);
xOut = param(x, "y.z", "overwrite") // won't set
console.log(xOut);
xOut = param(x, "y.a", "setting2")
console.log(xOut);
xOut = param(x, "y.a", "setting2", true) // get object rather than deepest node.
xOut = param(x, "1.0.2.a", "setting")
xOut = param(x, "1.0.1.a", "try to override") // won't set
xOut = param(x, "1.0.5.a", "new-setting", true) // get object rather than deepest node.
console.log(xOut);
Naturally, when the numeric keys are greater than 0, you might see some undefined gaps.
Practical uses of this might be
function AddNote(book, page, line) {
// assume a global global notes collection
var myNotes = param(allNotes, [book, page, line], []);
myNotes.push('This was a great twist!')
return myNotes;
}
var allNotes = {}
var youthfulHopes = AddNote('A Game of Thrones', 4, 2, "I'm already hooked, at least I won't have to wait long for the books to come out!");
console.log(allNotes)
// {"A Game of Thrones": [undefined, undefined, undefined, undefined, [undefined, undefined, ["I'm already hooked, at least I won't have to wait long for the books to come out!"]]]}
console.log(youthfulHopes)
// ["I'm already hooked, at least I won't have to wait long for the books to come out!"]
function initPath(obj, path) {
path.split('.').reduce((o, key) => (
Object.assign(o, {[key]: Object(o[key])}),
o[key]
), obj);
return obj;
}
Usage
const obj = { a: { b: 'value1' } };
initPath(obj, 'a.c.d').a.c.d='value2';
/*
{
"a": {
"b": "value1",
"c": {
"d": "value2"
}
}
}
*/
simple answer. on es6, im using this
const assign = (obj, path, value) => {
let keyPath = path.split('.')
let lastKeyIndex = keyPath.length - 1
for (let i = 0; i < lastKeyIndex; ++i) {
let key = keyPath[i]
if (!(key in obj)) {
obj[key] = {}
}
obj = obj[key]
}
obj[keyPath[lastKeyIndex]] = value
}
example json
const obj = {
b: 'hello'
}
you can add new key
assign(obj, 'c.d.e', 'this value')
and you get like bellow
console.log(obj)
//response example
obj = {
b: 'hello',
c: {
d: {
e: 'this value'
}
}
}
function createObj(keys, value) {
let obj = {}
let schema = obj
keys = keys.split('.')
for (let i = 0; i < keys.length - 1; i++) {
schema[keys[i]] = {}
schema = schema[keys[i]]
}
schema[keys.pop()] = value
return obj
}
let keys = 'value1.value2.value3'
let value = 'Hello'
let obj = createObj(keys, value)
Eval is probably overkill but the result is simple to visualize, with no nested loops or recursion.
function buildDir(obj, path){
var paths = path.split('_');
var final = paths.pop();
for (let i = 1; i <= paths.length; i++) {
var key = "obj['" + paths.slice(0, i).join("']['") + "']"
console.log(key)
eval(`${key} = {}`)
}
eval(`${key} = '${final}'`)
return obj
}
var newSettingName = "Modules_Video_Plugin_JWPlayer";
var Settings = buildDir( {}, newSettingName );
Basically you are progressively writing a string "obj['one']= {}", "obj['one']['two']"= {} and evaling it;

How to access Javascript object by path? [duplicate]

This question already has answers here:
Accessing nested JavaScript objects and arrays by string path
(44 answers)
Closed 7 years ago.
I'm temporarily stuck with what appears to be a very simple JavaScript problem, but maybe I'm just missing the right search keywords!
Say we have an object
var r = { a:1, b: {b1:11, b2: 99}};
There are several ways to access the 99:
r.b.b2
r['b']['b2']
What I want is to be able to define a string
var s = "b.b2";
and then access the 99 using
r.s or r[s] //(which of course won't work)
One way is to write a function for it that splits the string on dot and maybe recursively/iteratively gets the property. But is there any simpler/more efficient way? Anything useful in any of the jQuery APIs here?
Here's a naive function I wrote a while ago, but it works for basic object properties:
function getDescendantProp(obj, desc) {
var arr = desc.split(".");
while(arr.length && (obj = obj[arr.shift()]));
return obj;
}
console.log(getDescendantProp(r, "b.b2"));
//-> 99
Although there are answers that extend this to "allow" array index access, that's not really necessary as you can just specify numerical indexes using dot notation with this method:
getDescendantProp({ a: [ 1, 2, 3 ] }, 'a.2');
//-> 3
split and reduce while passing the object as the initalValue
Update
(thanks to comment posted by TeChn4K)
With ES6 syntax, it is even shorter
var r = { a:1, b: {b1:11, b2: 99}};
var s = "b.b2";
var value = s.split('.').reduce((a, b) => a[b], r);
console.log(value);
Old version
var r = { a:1, b: {b1:11, b2: 99}};
var s = "b.b2";
var value = s.split('.').reduce(function(a, b) {
return a[b];
}, r);
console.log(value);
You can use lodash get() and set() methods.
Getting
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.get(object, 'a[0].b.c');
// → 3
Setting
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.set(object, 'a[0].b.c', 4);
console.log(object.a[0].b.c);
// → 4
If it's possible in your scenario that you could put the entire array variable you're after into a string you could use the eval() function.
var r = { a:1, b: {b1:11, b2: 99}};
var s = "r.b.b2";
alert(eval(s)); // 99
I can feel people reeling in horror
Extending #JohnB's answer, I added a setter value as well. Check out the plunkr at
http://plnkr.co/edit/lo0thC?p=preview
function getSetDescendantProp(obj, desc, value) {
var arr = desc ? desc.split(".") : [];
while (arr.length && obj) {
var comp = arr.shift();
var match = new RegExp("(.+)\\[([0-9]*)\\]").exec(comp);
// handle arrays
if ((match !== null) && (match.length == 3)) {
var arrayData = {
arrName: match[1],
arrIndex: match[2]
};
if (obj[arrayData.arrName] !== undefined) {
if (typeof value !== 'undefined' && arr.length === 0) {
obj[arrayData.arrName][arrayData.arrIndex] = value;
}
obj = obj[arrayData.arrName][arrayData.arrIndex];
} else {
obj = undefined;
}
continue;
}
// handle regular things
if (typeof value !== 'undefined') {
if (obj[comp] === undefined) {
obj[comp] = {};
}
if (arr.length === 0) {
obj[comp] = value;
}
}
obj = obj[comp];
}
return obj;
}
This is the simplest i could do:
var accessProperties = function(object, string){
var explodedString = string.split('.');
for (i = 0, l = explodedString.length; i<l; i++){
object = object[explodedString[i]];
}
return object;
}
var r = { a:1, b: {b1:11, b2: 99}};
var s = "b.b2";
var o = accessProperties(r, s);
alert(o);//99
you could also do
var s = "['b'].b2";
var num = eval('r'+s);
Here is an extension of Andy E's code, that recurses into arrays and returns all values:
function GetDescendantProps(target, pathString) {
var arr = pathString.split(".");
while(arr.length && (target = target[arr.shift()])){
if (arr.length && target.length && target.forEach) { // handle arrays
var remainder = arr.join('.');
var results = [];
for (var i = 0; i < target.length; i++){
var x = this.GetDescendantProps(target[i], remainder);
if (x) results = results.concat(x);
}
return results;
}
}
return (target) ? [target] : undefined; //single result, wrap in array for consistency
}
So given this target:
var t =
{a:
{b: [
{'c':'x'},
{'not me':'y'},
{'c':'z'}
]
}
};
We get:
GetDescendantProps(t, "a.b.c") === ["x", "z"]; // true
I don't know a supported jQuery API function but I have this function:
var ret = data; // Your object
var childexpr = "b.b2"; // Your expression
if (childexpr != '') {
var childs = childexpr.split('.');
var i;
for (i = 0; i < childs.length && ret != undefined; i++) {
ret = ret[childs[i]];
}
}
return ret;
I've extended Andy E's answer, so that it can also handle arrays:
function getDescendantProp(obj, desc) {
var arr = desc.split(".");
//while (arr.length && (obj = obj[arr.shift()]));
while (arr.length && obj) {
var comp = arr.shift();
var match = new RegExp("(.+)\\[([0-9]*)\\]").exec(comp);
if ((match !== null) && (match.length == 3)) {
var arrayData = { arrName: match[1], arrIndex: match[2] };
if (obj[arrayData.arrName] != undefined) {
obj = obj[arrayData.arrName][arrayData.arrIndex];
} else {
obj = undefined;
}
} else {
obj = obj[comp]
}
}
return obj;
}
There are probably more efficient ways to do the Regex, but it's compact.
You can now do stuff like:
var model = {
"m1": {
"Id": "22345",
"People": [
{ "Name": "John", "Numbers": ["07263", "17236", "1223"] },
{ "Name": "Jenny", "Numbers": ["2", "3", "6"] },
{ "Name": "Bob", "Numbers": ["12", "3333", "4444"] }
]
}
}
// Should give you "6"
var x = getDescendantProp(model, "m1.People[1].Numbers[2]");
Performance tests for Andy E's, Jason More's, and my own solution are available at http://jsperf.com/propertyaccessor. Please feel free to run tests using your own browser to add to the data collected.
The prognosis is clear, Andy E's solution is the fastest by far!
For anyone interested, here is the code for my solution to the original question.
function propertyAccessor(object, keys, array) {
/*
Retrieve an object property with a dot notation string.
#param {Object} object Object to access.
#param {String} keys Property to access using 0 or more dots for notation.
#param {Object} [array] Optional array of non-dot notation strings to use instead of keys.
#return {*}
*/
array = array || keys.split('.')
if (array.length > 1) {
// recurse by calling self
return propertyAccessor(object[array.shift()], null, array)
} else {
return object[array]
}
}
Short answer: No, there is no native .access function like you want it. As you correctly mentioned, you would have to define your own function which splits the string and loops/checks over its parts.
Of course, what you always can do (even if its considered bad practice) is to use eval().
Like
var s = 'b.b2';
eval('r.' + s); // 99
Here is a a little better way then #andy's answer, where the obj (context) is optional, it falls back to window if not provided..
function getDescendantProp(desc, obj) {
obj = obj || window;
var arr = desc.split(".");
while (arr.length && (obj = obj[arr.shift()]));
return obj;
};

Getting an Objects nested value from an array of nested properties

I have a situation where I need to zip two Objects together retaining both of the values. I can iterate through both the objects and build an array of all the keys.
var traverse = function (obj, chain) {
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
var tempChain = [].concat(chain || []);
tempChain.push(prop);
if (typeof obj[prop] === 'object') {
traverse(obj[prop], tempChain);
}
console.log(tempChain);
}
}
};
Passing in the following:
traverse({
'a': {
'b': 'hello world',
'b1': 'hello world 1',
'c': {
'd': 'dello'
}
}
})
Will return me:
[a]
[a,b]
[a,b1]
[a,c]
[a, c, d]
So I now have an array of nested properties in an object. How can I access essentially obj[[a,c,d]]? I know I can solve the problem through eval but I can't trust the content.
eval('window.' + ['a','c','d'].join('.'));
If I can loop through that array and check to see if the property exists in both of them, then build a new object of the combined 'zipped' values.
Perhaps something like this?
function getValueAt(obj, keyPathArray) {
var emptyObj = {};
return keyPathArray.reduce(function (o, key) {
return (o || emptyObj)[key];
}, obj);
}
Then you can use it like:
var o = { a: { c: { d: 1 } } };
getValueAt(o, ['a', 'c', 'd']); //1
However it's not efficient for non-existing properties, since it will not short-circuit.
Here's another approach without using reduce:
function getValueAt(o, keyPathArray) {
var i = 0,
len = keyPathArray.length;
while (o != null && i < len) o = o[keyPathArray[i++]];
return o;
}

sort object properties and JSON.stringify

My application has a large array of objects, which I stringify and save them to the disk. Unfortunately, when the objects in the array are manipulated, and sometimes replaced, the properties on the objects are listed in different orders (their creation order?). When I do JSON.stringify() on the array and save it, a diff shows the properties getting listed in different orders, which is annoying when trying to merge the data further with diff and merging tools.
Ideally I would like to sort the properties of the objects in alphabetical order prior to performing the stringify, or as part of the stringify operation. There is code for manipulating the array objects in many places, and altering these to always create properties in an explicit order would be difficult.
Suggestions would be most welcome!
A condensed example:
obj = {}; obj.name="X"; obj.os="linux";
JSON.stringify(obj);
obj = {}; obj.os="linux"; obj.name="X";
JSON.stringify(obj);
The output of these two stringify calls are different, and showing up in a diff of my data, but my application doesn't care about the ordering of properties. The objects are constructed in many ways and places.
The simpler, modern and currently browser supported approach is simply this:
JSON.stringify(sortMyObj, Object.keys(sortMyObj).sort());
However, this method does remove any nested objects that aren't referenced and does not apply to objects within arrays. You will want to flatten the sorting object as well if you want something like this output:
{"a":{"h":4,"z":3},"b":2,"c":1}
You can do that with this:
var flattenObject = function(ob) {
var toReturn = {};
for (var i in ob) {
if (!ob.hasOwnProperty(i)) continue;
if ((typeof ob[i]) == 'object') {
var flatObject = flattenObject(ob[i]);
for (var x in flatObject) {
if (!flatObject.hasOwnProperty(x)) continue;
toReturn[i + '.' + x] = flatObject[x];
}
} else {
toReturn[i] = ob[i];
}
}
return toReturn;
};
var myFlattenedObj = flattenObject(sortMyObj);
JSON.stringify(myFlattenedObj, Object.keys(myFlattenedObj).sort());
To do it programmatically with something you can tweak yourself, you need to push the object property names into an array, then sort the array alphabetically and iterate through that array (which will be in the right order) and select each value from the object in that order. "hasOwnProperty" is checked also so you definitely have only the object's own properties. Here's an example:
var obj = {"a":1,"b":2,"c":3};
function iterateObjectAlphabetically(obj, callback) {
var arr = [],
i;
for (i in obj) {
if (obj.hasOwnProperty(i)) {
arr.push(i);
}
}
arr.sort();
for (i = 0; i < arr.length; i++) {
var key = obj[arr[i]];
//console.log( obj[arr[i]] ); //here is the sorted value
//do what you want with the object property
if (callback) {
// callback returns arguments for value, key and original object
callback(obj[arr[i]], arr[i], obj);
}
}
}
iterateObjectAlphabetically(obj, function(val, key, obj) {
//do something here
});
Again, this should guarantee that you iterate through in alphabetical order.
Finally, taking it further for the simplest way, this library will recursively allow you to sort any JSON you pass into it: https://www.npmjs.com/package/json-stable-stringify
var stringify = require('json-stable-stringify');
var obj = { c: 8, b: [{z:6,y:5,x:4},7], a: 3 };
console.log(stringify(obj));
Output
{"a":3,"b":[{"x":4,"y":5,"z":6},7],"c":8}
I don't understand why the complexity of the current best answers is needed, to get all the keys recursively. Unless perfect performance is needed, it seems to me that we can just call JSON.stringify() twice, the first time to get all the keys, and the second time, to really do the job. That way, all the recursion complexity is handled by stringify, and we know that it knows its stuff, and how to handle each object type:
function JSONstringifyOrder(obj, space)
{
const allKeys = new Set();
JSON.stringify(obj, (key, value) => (allKeys.add(key), value));
return JSON.stringify(obj, Array.from(allKeys).sort(), space);
}
Or if you want to support older browsers:
function JSONstringifyOrder(obj, space)
{
var allKeys = [];
var seen = {};
JSON.stringify(obj, function (key, value) {
if (!(key in seen)) {
allKeys.push(key);
seen[key] = null;
}
return value;
});
allKeys.sort();
return JSON.stringify(obj, allKeys, space);
}
I think that if you are in control of the JSON generation (and it sounds like you are), then for your purposes this might be a good solution: json-stable-stringify
From the project website:
deterministic JSON.stringify() with custom sorting to get
deterministic hashes from stringified results
If the JSON produced is deterministic you should be able to easily diff/merge it.
You can pass a sorted array of the property names as the second argument of JSON.stringify():
JSON.stringify(obj, Object.keys(obj).sort())
JSON.stringify() replacer function for having object keys sorted in output (supports deeply nested objects).
const replacer = (key, value) =>
value instanceof Object && !(value instanceof Array) ?
Object.keys(value)
.sort()
.reduce((sorted, key) => {
sorted[key] = value[key];
return sorted
}, {}) :
value;
// Usage
// JSON.stringify({c: 1, a: { d: 0, c: 1, e: {a: 0, 1: 4}}}, replacer);
GitHub Gist page here.
Update 2018-7-24:
This version sorts nested objects and supports array as well:
function sortObjByKey(value) {
return (typeof value === 'object') ?
(Array.isArray(value) ?
value.map(sortObjByKey) :
Object.keys(value).sort().reduce(
(o, key) => {
const v = value[key];
o[key] = sortObjByKey(v);
return o;
}, {})
) :
value;
}
function orderedJsonStringify(obj) {
return JSON.stringify(sortObjByKey(obj));
}
Test case:
describe('orderedJsonStringify', () => {
it('make properties in order', () => {
const obj = {
name: 'foo',
arr: [
{ x: 1, y: 2 },
{ y: 4, x: 3 },
],
value: { y: 2, x: 1, },
};
expect(orderedJsonStringify(obj))
.to.equal('{"arr":[{"x":1,"y":2},{"x":3,"y":4}],"name":"foo","value":{"x":1,"y":2}}');
});
it('support array', () => {
const obj = [
{ x: 1, y: 2 },
{ y: 4, x: 3 },
];
expect(orderedJsonStringify(obj))
.to.equal('[{"x":1,"y":2},{"x":3,"y":4}]');
});
});
Deprecated answer:
A concise version in ES2016.
Credit to #codename , from https://stackoverflow.com/a/29622653/94148
function orderedJsonStringify(o) {
return JSON.stringify(Object.keys(o).sort().reduce((r, k) => (r[k] = o[k], r), {}));
}
This is same as Satpal Singh's answer
function stringifyJSON(obj){
keys = [];
if(obj){
for(var key in obj){
keys.push(key);
}
}
keys.sort();
var tObj = {};
var key;
for(var index in keys){
key = keys[index];
tObj[ key ] = obj[ key ];
}
return JSON.stringify(tObj);
}
obj1 = {}; obj1.os="linux"; obj1.name="X";
stringifyJSON(obj1); //returns "{"name":"X","os":"linux"}"
obj2 = {}; obj2.name="X"; obj2.os="linux";
stringifyJSON(obj2); //returns "{"name":"X","os":"linux"}"
A recursive and simplified answer:
function sortObject(obj) {
if(typeof obj !== 'object')
return obj
var temp = {};
var keys = [];
for(var key in obj)
keys.push(key);
keys.sort();
for(var index in keys)
temp[keys[index]] = sortObject(obj[keys[index]]);
return temp;
}
var str = JSON.stringify(sortObject(obj), undefined, 4);
You can sort object by property name in EcmaScript 2015
function sortObjectByPropertyName(obj) {
return Object.keys(obj).sort().reduce((c, d) => (c[d] = obj[d], c), {});
}
You can add a custom toJSON function to your object which you can use to customise the output. Inside the function, adding current properties to a new object in a specific order should preserve that order when stringified.
See here:
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/JSON/stringify
There's no in-built method for controlling ordering because JSON data is meant to be accessed by keys.
Here's a jsfiddle with a small example:
http://jsfiddle.net/Eq2Yw/
Try commenting out the toJSON function - the order of the properties is reversed. Please be aware that this may be browser-specific, i.e. ordering is not officially supported in the specification. It works in the current version of Firefox, but if you want a 100% robust solution, you may have to write your own stringifier function.
Edit:
Also see this SO question regarding stringify's non-deterministic output, especially Daff's details about browser differences:
How to deterministically verify that a JSON object hasn't been modified?
I took the answer from #Jason Parham and made some improvements
function sortObject(obj, arraySorter) {
if(typeof obj !== 'object')
return obj
if (Array.isArray(obj)) {
if (arraySorter) {
obj.sort(arraySorter);
}
for (var i = 0; i < obj.length; i++) {
obj[i] = sortObject(obj[i], arraySorter);
}
return obj;
}
var temp = {};
var keys = [];
for(var key in obj)
keys.push(key);
keys.sort();
for(var index in keys)
temp[keys[index]] = sortObject(obj[keys[index]], arraySorter);
return temp;
}
This fixes the issue of arrays being converted to objects, and it also allows you to define how to sort arrays.
Example:
var data = { content: [{id: 3}, {id: 1}, {id: 2}] };
sortObject(data, (i1, i2) => i1.id - i2.id)
output:
{content:[{id:1},{id:2},{id:3}]}
I just rewrote one of mentioned examples to use it in stringify
const stringifySort = (key, value) => {
if (!value || typeof value !== 'object' || Array.isArray(value)) return value;
return Object.keys(value).sort().reduce((obj, key) => (obj[key]=value[key], obj), {});
};
JSON.stringify({name:"X", os:"linux"}, stringifySort);
The accepted answer does not work for me for nested objects for some reason. This led me to code up my own. As it's late 2019 when I write this, there are a few more options available within the language.
Update: I believe David Furlong's answer is a preferable approach to my earlier attempt, and I have riffed off that. Mine relies on support for Object.entries(...), so no Internet Explorer support.
function normalize(sortingFunction) {
return function(key, value) {
if (typeof value === 'object' && !Array.isArray(value)) {
return Object
.entries(value)
.sort(sortingFunction || undefined)
.reduce((acc, entry) => {
acc[entry[0]] = entry[1];
return acc;
}, {});
}
return value;
}
}
JSON.stringify(obj, normalize(), 2);
--
KEEPING THIS OLDER VERSION FOR HISTORICAL REFERENCE
I found that a simple, flat array of all keys in the object will work. In almost all browsers (not Edge or Internet explorer, predictably) and Node 12+ there is a fairly short solution now that Array.prototype.flatMap(...) is available. (The lodash equivalent would work too.) I have only tested in Safari, Chrome, and Firefox, but I see no reason why it wouldn't work anywhere else that supports flatMap and standard JSON.stringify(...).
function flattenEntries([key, value]) {
return (typeof value !== 'object')
? [ [ key, value ] ]
: [ [ key, value ], ...Object.entries(value).flatMap(flattenEntries) ];
}
function sortedStringify(obj, sorter, indent = 2) {
const allEntries = Object.entries(obj).flatMap(flattenEntries);
const sorted = allEntries.sort(sorter || undefined).map(entry => entry[0]);
return JSON.stringify(obj, sorted, indent);
}
With this, you can stringify with no 3rd-party dependencies and even pass in your own sort algorithm that sorts on the key-value entry pairs, so you can sort by key, payload, or a combination of the two. Works for nested objects, arrays, and any mixture of plain old data types.
const obj = {
"c": {
"z": 4,
"x": 3,
"y": [
2048,
1999,
{
"x": false,
"g": "help",
"f": 5
}
]
},
"a": 2,
"b": 1
};
console.log(sortedStringify(obj, null, 2));
Prints:
{
"a": 2,
"b": 1,
"c": {
"x": 3,
"y": [
2048,
1999,
{
"f": 5,
"g": "help",
"x": false
}
],
"z": 4
}
}
If you must have compatibility with older JavaScript engines, you could use these slightly more verbose versions that emulate flatMap behavior. Client must support at least ES5, so no Internet Explorer 8 or below.
These will return the same result as above.
function flattenEntries([key, value]) {
if (typeof value !== 'object') {
return [ [ key, value ] ];
}
const nestedEntries = Object
.entries(value)
.map(flattenEntries)
.reduce((acc, arr) => acc.concat(arr), []);
nestedEntries.unshift([ key, value ]);
return nestedEntries;
}
function sortedStringify(obj, sorter, indent = 2) {
const sortedKeys = Object
.entries(obj)
.map(flattenEntries)
.reduce((acc, arr) => acc.concat(arr), [])
.sort(sorter || undefined)
.map(entry => entry[0]);
return JSON.stringify(obj, sortedKeys, indent);
}
An additional solution that works for nested objects as well:
const myFunc = (key) =>
JSON.stringify(key, (_, v) =>
v.constructor === Object ? Object.entries(v).sort() : v
);
const jsonFunc = JSON.stringify;
const obj1 = {
key1: "value1",
key2: {
key3: "value2",
key4: "value3",
},
};
const obj2 = {
key2: {
key4: "value3",
key3: "value2",
},
key1: "value1",
};
console.log(`JSON: ${jsonFunc(obj1) === jsonFunc(obj2)}`);
console.log(`My: ${myFunc(obj1) === myFunc(obj2)}`);
Works with lodash, nested objects, any value of object attribute:
function sort(myObj) {
var sortedObj = {};
Object.keys(myObj).sort().forEach(key => {
sortedObj[key] = _.isPlainObject(myObj[key]) ? sort(myObj[key]) : myObj[key]
})
return sortedObj;
}
JSON.stringify(sort(yourObj), null, 2)
It relies on Chrome's and Node's behaviour that the first key assigned to an object is outputted first by JSON.stringify.
After all, it needs an Array that caches all keys in the nested object (otherwise it will omit the uncached keys.) The oldest answer is just plain wrong, because second argument doesn't care about dot-notation. So, the answer (using Set) becomes.
function stableStringify (obj) {
const keys = new Set()
const getAndSortKeys = (a) => {
if (a) {
if (typeof a === 'object' && a.toString() === '[object Object]') {
Object.keys(a).map((k) => {
keys.add(k)
getAndSortKeys(a[k])
})
} else if (Array.isArray(a)) {
a.map((el) => getAndSortKeys(el))
}
}
}
getAndSortKeys(obj)
return JSON.stringify(obj, Array.from(keys).sort())
}
Try:
function obj(){
this.name = '';
this.os = '';
}
a = new obj();
a.name = 'X',
a.os = 'linux';
JSON.stringify(a);
b = new obj();
b.os = 'linux';
b.name = 'X',
JSON.stringify(b);
I made a function to sort object, and with callback .. which actually create a new object
function sortObj( obj , callback ) {
var r = [] ;
for ( var i in obj ){
if ( obj.hasOwnProperty( i ) ) {
r.push( { key: i , value : obj[i] } );
}
}
return r.sort( callback ).reduce( function( obj , n ){
obj[ n.key ] = n.value ;
return obj;
},{});
}
and call it with object .
var obj = {
name : "anu",
os : "windows",
value : 'msio',
};
var result = sortObj( obj , function( a, b ){
return a.key < b.key ;
});
JSON.stringify( result )
which prints {"value":"msio","os":"windows","name":"anu"} , and for sorting with value .
var result = sortObj( obj , function( a, b ){
return a.value < b.value ;
});
JSON.stringify( result )
which prints {"os":"windows","value":"msio","name":"anu"}
If objects in the list does not have same properties, generate a combined master object before stringify:
let arr=[ <object1>, <object2>, ... ]
let o = {}
for ( let i = 0; i < arr.length; i++ ) {
Object.assign( o, arr[i] );
}
JSON.stringify( arr, Object.keys( o ).sort() );
function FlatternInSort( obj ) {
if( typeof obj === 'object' )
{
if( obj.constructor === Object )
{ //here use underscore.js
let PaireStr = _( obj ).chain().pairs().sortBy( p => p[0] ).map( p => p.map( FlatternInSort ).join( ':' )).value().join( ',' );
return '{' + PaireStr + '}';
}
return '[' + obj.map( FlatternInSort ).join( ',' ) + ']';
}
return JSON.stringify( obj );
}
// example as below. in each layer, for objects like {}, flattened in key sort. for arrays, numbers or strings, flattened like/with JSON.stringify.
FlatternInSort( { c:9, b: { y: 4, z: 2, e: 9 }, F:4, a:[{j:8, h:3},{a:3,b:7}] } )
"{"F":4,"a":[{"h":3,"j":8},{"a":3,"b":7}],"b":{"e":9,"y":4,"z":2},"c":9}"
Extending AJP's answer, to handle arrays:
function sort(myObj) {
var sortedObj = {};
Object.keys(myObj).sort().forEach(key => {
sortedObj[key] = _.isPlainObject(myObj[key]) ? sort(myObj[key]) : _.isArray(myObj[key])? myObj[key].map(sort) : myObj[key]
})
return sortedObj;
}
Surprised nobody has mentioned lodash's isEqual function.
Performs a deep comparison between two values to determine if they are
equivalent.
Note: This method supports comparing arrays, array buffers, booleans,
date objects, error objects, maps, numbers, Object objects, regexes,
sets, strings, symbols, and typed arrays. Object objects are compared
by their own, not inherited, enumerable properties. Functions and DOM
nodes are compared by strict equality, i.e. ===.
https://lodash.com/docs/4.17.11#isEqual
With the original problem - keys being inconsistently ordered - it's a great solution - and of course it will just stop if it finds a conflict instead of blindly serializing the whole object.
To avoid importing the whole library you do this:
import { isEqual } from "lodash-es";
Bonus example:
You can also use this with RxJS with this custom operator
export const distinctUntilEqualChanged = <T>(): MonoTypeOperatorFunction<T> =>
pipe(distinctUntilChanged(isEqual));
Here is a clone approach...clone the object before converting to json:
function sort(o: any): any {
if (null === o) return o;
if (undefined === o) return o;
if (typeof o !== "object") return o;
if (Array.isArray(o)) {
return o.map((item) => sort(item));
}
const keys = Object.keys(o).sort();
const result = <any>{};
keys.forEach((k) => (result[k] = sort(o[k])));
return result;
}
If is very new but seems to work on package.json files fine.
Don't be confused with the object monitoring of Chrome debugger. It shows sorted keys in object, even though actually it is not sorted. You have to sort the object before you stringify it.
Before I found libs like fast-json-stable-stringify (haven't tested it in production myself), I was doing it this way:
import { flatten } from "flat";
import { set } from 'lodash/fp';
const sortJson = (jsonString) => {
const object = JSON.parse(jsonString);
const flatObject = flatten(object);
const propsSorted = Object.entries(flatObject).map(([key, value]) => ({ key, value })).sort((a, b) => a.key.localeCompare(b.key));
const objectSorted = propsSorted.reduce((object, { key, value }) => set(key, value, object), {});
return JSON.stringify(objectSorted);
};
const originalJson = JSON.stringify({ c: { z: 3, x: 1, y: 2 }, a: true, b: [ 'a', 'b', 'c' ] });
console.log(sortJson(originalJson)); // {"a":true,"b":["a","b","c"],"c":{"x":1,"y":2,"z":3}}
There is Array.sort method which can be helpful for you. For example:
yourBigArray.sort(function(a,b){
//custom sorting mechanism
});

Convert string in dot notation to get the object reference [duplicate]

This question already has answers here:
Convert a JavaScript string in dot notation into an object reference
(34 answers)
Closed 7 years ago.
Consider this object in javascript,
var obj = { a : { b: 1, c: 2 } };
given the string "obj.a.b" how can I get the object this refers to, so that I may alter its value? i.e. I want to be able to do something like
obj.a.b = 5;
obj.a.c = 10;
where "obj.a.b" & "obj.a.c" are strings (not obj references).
I came across this post where I can get the value the dot notation string is referring to obj but what I need is a way I can get at the object itself?
The nesting of the object may be even deeper than this. i.e. maybe
var obj = { a: { b: 1, c : { d : 3, e : 4}, f: 5 } }
To obtain the value, consider:
function ref(obj, str) {
str = str.split(".");
for (var i = 0; i < str.length; i++)
obj = obj[str[i]];
return obj;
}
var obj = { a: { b: 1, c : { d : 3, e : 4}, f: 5 } }
str = 'a.c.d'
ref(obj, str) // 3
or in a more fancy way, using reduce:
function ref(obj, str) {
return str.split(".").reduce(function(o, x) { return o[x] }, obj);
}
Returning an assignable reference to an object member is not possible in javascript, you'll have to use a function like the following:
function set(obj, str, val) {
str = str.split(".");
while (str.length > 1)
obj = obj[str.shift()];
return obj[str.shift()] = val;
}
var obj = { a: { b: 1, c : { d : 3, e : 4}, f: 5 } }
str = 'a.c.d'
set(obj, str, 99)
console.log(obj.a.c.d) // 99
or use ref given above to obtain the reference to the containing object and then apply the [] operator to it:
parts = str.split(/\.(?=[^.]+$)/) // Split "foo.bar.baz" into ["foo.bar", "baz"]
ref(obj, parts[0])[parts[1]] = 99
Similar to thg435's answer, but with argument checks and supports nest levels where one of the ancestor levels isn't yet defined or isn't an object.
setObjByString = function(obj, str, val) {
var keys, key;
//make sure str is a string with length
if (!str || !str.length || Object.prototype.toString.call(str) !== "[object String]") {
return false;
}
if (obj !== Object(obj)) {
//if it's not an object, make it one
obj = {};
}
keys = str.split(".");
while (keys.length > 1) {
key = keys.shift();
if (obj !== Object(obj)) {
//if it's not an object, make it one
obj = {};
}
if (!(key in obj)) {
//if obj doesn't contain the key, add it and set it to an empty object
obj[key] = {};
}
obj = obj[key];
}
return obj[keys[0]] = val;
};
Usage:
var obj;
setObjByString(obj, "a.b.c.d.e.f", "hello");
If this javascript runs in a browser then you can access the object like this:
window['obj']['a']['b'] = 5
So given the string "obj.a.b" you have to split the it by .:
var s = "obj.a.b"
var e = s.split(".")
window[e[0]][e[1]][e[2]] = 5
Returning an assignable reference to an object member is not possible in javascript. You can assign value to a deep object member by dot notation with a single line of code like this.
new Function('_', 'val', '_.' + path + ' = val')(obj, value);
In you case:
var obj = { a : { b: 1, c: 2 } };
new Function('_', 'val', '_.a.b' + ' = val')(obj, 5); // Now obj.a.b will be equal to 5
var obj = { a : { b: 1, c: 2 } };
walkObject(obj,"a.b"); // 1
function walkObject( obj, path ){
var parts = path.split("."), i=0, part;
while (obj && (part=parts[i++])) obj=obj[part];
return obj;
}
Or if you like your code terse:
function walkObject( o, path ){
for (var a,p=path.split('.'),i=0; o&&(a=p[i++]); o=o[a]);
return o;
}
Below is a simple class wrapper around dict:
class Dots(dict):
def __init__(self, *args, **kargs):
super(Dots, self).__init__(*args, **kargs)
def __getitem__(self, key):
try:
item = super(Dots, self).__getitem__(key)
except KeyError:
item = Dots()
self.__setitem__(key, item)
return Dots(item) if type(item) == dict else item
def __setitem__(self, key, value):
if type(value) == dict: value = Dots(value)
super(Dots, self).__setitem__(key, value)
__getattr__ = __getitem__
__setattr__ = __setitem__
Example:
>>> a = Dots()
>>> a.b.c = 123
>>> a.b.c
123
>>> a.b
{'c': 123}
>>> a
{'b': {'c': 123}}
Missing key are created on the fly as empty Dots():
>>> if a.Missing: print "Exists"
...
>>> a
{'Missing': {}, 'b': {'c': 123}}

Categories