Related
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;
};
I have string like the following:
11222233344444445666
What I would like to do is output the number followed the times it was displayed:
112433475163
Question is, I want this to be efficient. I can store this in an object as the following:
1: { id: 1, displayed: 2},
2: { id: 2, displayed: 1},
3: { id: 3, displayed: 2},
etc.
I can access this object and increment displayed.
My issues is, there is no guarantee in the order. I would like to store the keys in the order they are in the string. How do I accomplish the importance of the order in the object?
This is a proposal for run length coding with an array which holds infomation about one charcter and the count of it:
{
"char": "1",
"count": 2
},
var string = "11222233344444445666",
array = function () {
var r = [], o = {};
string.split('').forEach(function (a, i, aa) {
if (a !== aa[i - 1]) {
o[a] = { char: a, count: 0 };
r.push(o[a]);
}
o[a].count++;
});
return r;
}(string);
document.write('<pre>' + JSON.stringify(array, 0, 4) + '</pre>');
Quick solution with for loop:
var str = "7771122229933344444445666",
obj = {},
len = str.length,
val = null,
count_str = "",
key = "";
for (var i = 0; i < len; i++) {
val = str[i], key = 'k' + val;
if (!obj[key]) {
obj[key] = {'id': val, 'displayed': 1};
} else {
obj[key].displayed++;
}
}
for (var p in obj) {
count_str += obj[p]['id'] + obj[p]['displayed'];
}
console.log(count_str); // "7312249233475163"
because you have such a small set of distinct numbers, I seen no reason why you can't use a array (yeah it's not super ideal memorywise if you skip values and it becomes sparse, but for such a small subset it won't affect you enough to worry of it). Then you can use (number-1) as the index and increment that number as needed.
var counts = [];
var str = "11222233344444445666";
for(var i in str){
var index = parseInt(str[i])-1
counts[index] = (counts[index]||0)+1;
}
for(var i in counts){
var which = 1+parseInt(i);
var count = counts[i];
console.log("# of " + which +"'s: "+count);
}
https://jsfiddle.net/ga0fqpqn/
note: You shouldn't need the parseInt(i)... just +i should work but I think jsfiddle has a bug with it about it defaulting i to handle like a string.
You could store an additional array with the order of the numbers, which you only append to if the object doesn't yet contain the given number. Then once you're done counting, iterate through that array and output the number and the count from the lookup dictionary.
var chars = "1234576123452345".split("");
var order = [];
var hash = {};
chars.forEach(function(char) {
if (!hash[char]) {
hash[char] = 1;
order.push(char);
} else {
hash[char]++;
}
});
console.log(order.map(function(char) {
return char + hash[char];
}).join(""));
// "12233343537161"
I threw some code together to flatten and un-flatten complex/nested JavaScript objects. It works, but it's a bit slow (triggers the 'long script' warning).
For the flattened names I want "." as the delimiter and [INDEX] for arrays.
Examples:
un-flattened | flattened
---------------------------
{foo:{bar:false}} => {"foo.bar":false}
{a:[{b:["c","d"]}]} => {"a[0].b[0]":"c","a[0].b[1]":"d"}
[1,[2,[3,4],5],6] => {"[0]":1,"[1].[0]":2,"[1].[1].[0]":3,"[1].[1].[1]":4,"[1].[2]":5,"[2]":6}
I created a benchmark that ~simulates my use case http://jsfiddle.net/WSzec/
Get a nested object
Flatten it
Look through it and possibly modify it while flattened
Unflatten it back to it's original nested format to be shipped away
I would like faster code: For clarification, code that completes the JSFiddle benchmark (http://jsfiddle.net/WSzec/) significantly faster (~20%+ would be nice) in IE 9+, FF 24+, and Chrome 29+.
Here's the relevant JavaScript code: Current Fastest: http://jsfiddle.net/WSzec/6/
var unflatten = function(data) {
"use strict";
if (Object(data) !== data || Array.isArray(data))
return data;
var result = {}, cur, prop, idx, last, temp;
for(var p in data) {
cur = result, prop = "", last = 0;
do {
idx = p.indexOf(".", last);
temp = p.substring(last, idx !== -1 ? idx : undefined);
cur = cur[prop] || (cur[prop] = (!isNaN(parseInt(temp)) ? [] : {}));
prop = temp;
last = idx + 1;
} while(idx >= 0);
cur[prop] = data[p];
}
return result[""];
}
var flatten = function(data) {
var result = {};
function recurse (cur, prop) {
if (Object(cur) !== cur) {
result[prop] = cur;
} else if (Array.isArray(cur)) {
for(var i=0, l=cur.length; i<l; i++)
recurse(cur[i], prop ? prop+"."+i : ""+i);
if (l == 0)
result[prop] = [];
} else {
var isEmpty = true;
for (var p in cur) {
isEmpty = false;
recurse(cur[p], prop ? prop+"."+p : p);
}
if (isEmpty)
result[prop] = {};
}
}
recurse(data, "");
return result;
}
EDIT 1 Modified the above to #Bergi 's implementation which is currently the fastest. As an aside, using ".indexOf" instead of "regex.exec" is around 20% faster in FF but 20% slower in Chrome; so I'll stick with the regex since it's simpler (here's my attempt at using indexOf to replace the regex http://jsfiddle.net/WSzec/2/).
EDIT 2 Building on #Bergi 's idea I managed to created a faster non-regex version (3x faster in FF and ~10% faster in Chrome). http://jsfiddle.net/WSzec/6/ In the this (the current) implementation the rules for key names are simply, keys cannot start with an integer or contain a period.
Example:
{"foo":{"bar":[0]}} => {"foo.bar.0":0}
EDIT 3 Adding #AaditMShah 's inline path parsing approach (rather than String.split) helped to improve the unflatten performance. I'm very happy with the overall performance improvement reached.
The latest jsfiddle and jsperf:
http://jsfiddle.net/WSzec/14/
http://jsperf.com/flatten-un-flatten/4
Here's my much shorter implementation:
Object.unflatten = function(data) {
"use strict";
if (Object(data) !== data || Array.isArray(data))
return data;
var regex = /\.?([^.\[\]]+)|\[(\d+)\]/g,
resultholder = {};
for (var p in data) {
var cur = resultholder,
prop = "",
m;
while (m = regex.exec(p)) {
cur = cur[prop] || (cur[prop] = (m[2] ? [] : {}));
prop = m[2] || m[1];
}
cur[prop] = data[p];
}
return resultholder[""] || resultholder;
};
flatten hasn't changed much (and I'm not sure whether you really need those isEmpty cases):
Object.flatten = function(data) {
var result = {};
function recurse (cur, prop) {
if (Object(cur) !== cur) {
result[prop] = cur;
} else if (Array.isArray(cur)) {
for(var i=0, l=cur.length; i<l; i++)
recurse(cur[i], prop + "[" + i + "]");
if (l == 0)
result[prop] = [];
} else {
var isEmpty = true;
for (var p in cur) {
isEmpty = false;
recurse(cur[p], prop ? prop+"."+p : p);
}
if (isEmpty && prop)
result[prop] = {};
}
}
recurse(data, "");
return result;
}
Together, they run your benchmark in about the half of the time (Opera 12.16: ~900ms instead of ~ 1900ms, Chrome 29: ~800ms instead of ~1600ms).
Note: This and most other solutions answered here focus on speed and are susceptible to prototype pollution and shold not be used on untrusted objects.
I wrote two functions to flatten and unflatten a JSON object.
Flatten a JSON object:
var flatten = (function (isArray, wrapped) {
return function (table) {
return reduce("", {}, table);
};
function reduce(path, accumulator, table) {
if (isArray(table)) {
var length = table.length;
if (length) {
var index = 0;
while (index < length) {
var property = path + "[" + index + "]", item = table[index++];
if (wrapped(item) !== item) accumulator[property] = item;
else reduce(property, accumulator, item);
}
} else accumulator[path] = table;
} else {
var empty = true;
if (path) {
for (var property in table) {
var item = table[property], property = path + "." + property, empty = false;
if (wrapped(item) !== item) accumulator[property] = item;
else reduce(property, accumulator, item);
}
} else {
for (var property in table) {
var item = table[property], empty = false;
if (wrapped(item) !== item) accumulator[property] = item;
else reduce(property, accumulator, item);
}
}
if (empty) accumulator[path] = table;
}
return accumulator;
}
}(Array.isArray, Object));
Performance:
It's faster than the current solution in Opera. The current solution is 26% slower in Opera.
It's faster than the current solution in Firefox. The current solution is 9% slower in Firefox.
It's faster than the current solution in Chrome. The current solution is 29% slower in Chrome.
Unflatten a JSON object:
function unflatten(table) {
var result = {};
for (var path in table) {
var cursor = result, length = path.length, property = "", index = 0;
while (index < length) {
var char = path.charAt(index);
if (char === "[") {
var start = index + 1,
end = path.indexOf("]", start),
cursor = cursor[property] = cursor[property] || [],
property = path.slice(start, end),
index = end + 1;
} else {
var cursor = cursor[property] = cursor[property] || {},
start = char === "." ? index + 1 : index,
bracket = path.indexOf("[", start),
dot = path.indexOf(".", start);
if (bracket < 0 && dot < 0) var end = index = length;
else if (bracket < 0) var end = index = dot;
else if (dot < 0) var end = index = bracket;
else var end = index = bracket < dot ? bracket : dot;
var property = path.slice(start, end);
}
}
cursor[property] = table[path];
}
return result[""];
}
Performance:
It's faster than the current solution in Opera. The current solution is 5% slower in Opera.
It's slower than the current solution in Firefox. My solution is 26% slower in Firefox.
It's slower than the current solution in Chrome. My solution is 6% slower in Chrome.
Flatten and unflatten a JSON object:
Overall my solution performs either equally well or even better than the current solution.
Performance:
It's faster than the current solution in Opera. The current solution is 21% slower in Opera.
It's as fast as the current solution in Firefox.
It's faster than the current solution in Firefox. The current solution is 20% slower in Chrome.
Output format:
A flattened object uses the dot notation for object properties and the bracket notation for array indices:
{foo:{bar:false}} => {"foo.bar":false}
{a:[{b:["c","d"]}]} => {"a[0].b[0]":"c","a[0].b[1]":"d"}
[1,[2,[3,4],5],6] => {"[0]":1,"[1][0]":2,"[1][1][0]":3,"[1][1][1]":4,"[1][2]":5,"[2]":6}
In my opinion this format is better than only using the dot notation:
{foo:{bar:false}} => {"foo.bar":false}
{a:[{b:["c","d"]}]} => {"a.0.b.0":"c","a.0.b.1":"d"}
[1,[2,[3,4],5],6] => {"0":1,"1.0":2,"1.1.0":3,"1.1.1":4,"1.2":5,"2":6}
Advantages:
Flattening an object is faster than the current solution.
Flattening and unflattening an object is as fast as or faster than the current solution.
Flattened objects use both the dot notation and the bracket notation for readability.
Disadvantages:
Unflattening an object is slower than the current solution in most (but not all) cases.
The current JSFiddle demo gave the following values as output:
Nested : 132175 : 63
Flattened : 132175 : 564
Nested : 132175 : 54
Flattened : 132175 : 508
My updated JSFiddle demo gave the following values as output:
Nested : 132175 : 59
Flattened : 132175 : 514
Nested : 132175 : 60
Flattened : 132175 : 451
I'm not really sure what that means, so I'll stick with the jsPerf results. After all jsPerf is a performance benchmarking utility. JSFiddle is not.
ES6 version:
const flatten = (obj, path = '') => {
if (!(obj instanceof Object)) return {[path.replace(/\.$/g, '')]:obj};
return Object.keys(obj).reduce((output, key) => {
return obj instanceof Array ?
{...output, ...flatten(obj[key], path + '[' + key + '].')}:
{...output, ...flatten(obj[key], path + key + '.')};
}, {});
}
Example:
console.log(flatten({a:[{b:["c","d"]}]}));
console.log(flatten([1,[2,[3,4],5],6]));
3 ½ Years later...
For my own project I wanted to flatten JSON objects in mongoDB dot notation and came up with a simple solution:
/**
* Recursively flattens a JSON object using dot notation.
*
* NOTE: input must be an object as described by JSON spec. Arbitrary
* JS objects (e.g. {a: () => 42}) may result in unexpected output.
* MOREOVER, it removes keys with empty objects/arrays as value (see
* examples bellow).
*
* #example
* // returns {a:1, 'b.0.c': 2, 'b.0.d.e': 3, 'b.1': 4}
* flatten({a: 1, b: [{c: 2, d: {e: 3}}, 4]})
* // returns {a:1, 'b.0.c': 2, 'b.0.d.e.0': true, 'b.0.d.e.1': false, 'b.0.d.e.2.f': 1}
* flatten({a: 1, b: [{c: 2, d: {e: [true, false, {f: 1}]}}]})
* // return {a: 1}
* flatten({a: 1, b: [], c: {}})
*
* #param obj item to be flattened
* #param {Array.string} [prefix=[]] chain of prefix joined with a dot and prepended to key
* #param {Object} [current={}] result of flatten during the recursion
*
* #see https://docs.mongodb.com/manual/core/document/#dot-notation
*/
function flatten (obj, prefix, current) {
prefix = prefix || []
current = current || {}
// Remember kids, null is also an object!
if (typeof (obj) === 'object' && obj !== null) {
Object.keys(obj).forEach(key => {
this.flatten(obj[key], prefix.concat(key), current)
})
} else {
current[prefix.join('.')] = obj
}
return current
}
Features and/or caveats
It only accepts JSON objects. So if you pass something like {a: () => {}} you might not get what you wanted!
It removes empty arrays and objects. So this {a: {}, b: []} is flattened to {}.
Use this library:
npm install flat
Usage (from https://www.npmjs.com/package/flat):
Flatten:
var flatten = require('flat')
flatten({
key1: {
keyA: 'valueI'
},
key2: {
keyB: 'valueII'
},
key3: { a: { b: { c: 2 } } }
})
// {
// 'key1.keyA': 'valueI',
// 'key2.keyB': 'valueII',
// 'key3.a.b.c': 2
// }
Un-flatten:
var unflatten = require('flat').unflatten
unflatten({
'three.levels.deep': 42,
'three.levels': {
nested: true
}
})
// {
// three: {
// levels: {
// deep: 42,
// nested: true
// }
// }
// }
Here's another approach that runs slower (about 1000ms) than the above answer, but has an interesting idea :-)
Instead of iterating through each property chain, it just picks the last property and uses a look-up-table for the rest to store the intermediate results. This look-up-table will be iterated until there are no property chains left and all values reside on uncocatenated properties.
JSON.unflatten = function(data) {
"use strict";
if (Object(data) !== data || Array.isArray(data))
return data;
var regex = /\.?([^.\[\]]+)$|\[(\d+)\]$/,
props = Object.keys(data),
result, p;
while(p = props.shift()) {
var m = regex.exec(p),
target;
if (m.index) {
var rest = p.slice(0, m.index);
if (!(rest in data)) {
data[rest] = m[2] ? [] : {};
props.push(rest);
}
target = data[rest];
} else {
target = result || (result = (m[2] ? [] : {}));
}
target[m[2] || m[1]] = data[p];
}
return result;
};
It currently uses the data input parameter for the table, and puts lots of properties on it - a non-destructive version should be possible as well. Maybe a clever lastIndexOf usage performs better than the regex (depends on the regex engine).
See it in action here.
You can use https://github.com/hughsk/flat
Take a nested Javascript object and flatten it, or unflatten an object with delimited keys.
Example from the doc
var flatten = require('flat')
flatten({
key1: {
keyA: 'valueI'
},
key2: {
keyB: 'valueII'
},
key3: { a: { b: { c: 2 } } }
})
// {
// 'key1.keyA': 'valueI',
// 'key2.keyB': 'valueII',
// 'key3.a.b.c': 2
// }
var unflatten = require('flat').unflatten
unflatten({
'three.levels.deep': 42,
'three.levels': {
nested: true
}
})
// {
// three: {
// levels: {
// deep: 42,
// nested: true
// }
// }
// }
This code recursively flattens out JSON objects.
I included my timing mechanism in the code and it gives me 1ms but I'm not sure if that's the most accurate one.
var new_json = [{
"name": "fatima",
"age": 25,
"neighbour": {
"name": "taqi",
"location": "end of the street",
"property": {
"built in": 1990,
"owned": false,
"years on market": [1990, 1998, 2002, 2013],
"year short listed": [], //means never
}
},
"town": "Mountain View",
"state": "CA"
},
{
"name": "qianru",
"age": 20,
"neighbour": {
"name": "joe",
"location": "opposite to the park",
"property": {
"built in": 2011,
"owned": true,
"years on market": [1996, 2011],
"year short listed": [], //means never
}
},
"town": "Pittsburgh",
"state": "PA"
}]
function flatten(json, flattened, str_key) {
for (var key in json) {
if (json.hasOwnProperty(key)) {
if (json[key] instanceof Object && json[key] != "") {
flatten(json[key], flattened, str_key + "." + key);
} else {
flattened[str_key + "." + key] = json[key];
}
}
}
}
var flattened = {};
console.time('flatten');
flatten(new_json, flattened, "");
console.timeEnd('flatten');
for (var key in flattened){
console.log(key + ": " + flattened[key]);
}
Output:
flatten: 1ms
.0.name: fatima
.0.age: 25
.0.neighbour.name: taqi
.0.neighbour.location: end of the street
.0.neighbour.property.built in: 1990
.0.neighbour.property.owned: false
.0.neighbour.property.years on market.0: 1990
.0.neighbour.property.years on market.1: 1998
.0.neighbour.property.years on market.2: 2002
.0.neighbour.property.years on market.3: 2013
.0.neighbour.property.year short listed:
.0.town: Mountain View
.0.state: CA
.1.name: qianru
.1.age: 20
.1.neighbour.name: joe
.1.neighbour.location: opposite to the park
.1.neighbour.property.built in: 2011
.1.neighbour.property.owned: true
.1.neighbour.property.years on market.0: 1996
.1.neighbour.property.years on market.1: 2011
.1.neighbour.property.year short listed:
.1.town: Pittsburgh
.1.state: PA
Here's mine. It runs in <2ms in Google Apps Script on a sizable object. It uses dashes instead of dots for separators, and it doesn't handle arrays specially like in the asker's question, but this is what I wanted for my use.
function flatten (obj) {
var newObj = {};
for (var key in obj) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
var temp = flatten(obj[key])
for (var key2 in temp) {
newObj[key+"-"+key2] = temp[key2];
}
} else {
newObj[key] = obj[key];
}
}
return newObj;
}
Example:
var test = {
a: 1,
b: 2,
c: {
c1: 3.1,
c2: 3.2
},
d: 4,
e: {
e1: 5.1,
e2: 5.2,
e3: {
e3a: 5.31,
e3b: 5.32
},
e4: 5.4
},
f: 6
}
Logger.log("start");
Logger.log(JSON.stringify(flatten(test),null,2));
Logger.log("done");
Example output:
[17-02-08 13:21:05:245 CST] start
[17-02-08 13:21:05:246 CST] {
"a": 1,
"b": 2,
"c-c1": 3.1,
"c-c2": 3.2,
"d": 4,
"e-e1": 5.1,
"e-e2": 5.2,
"e-e3-e3a": 5.31,
"e-e3-e3b": 5.32,
"e-e4": 5.4,
"f": 6
}
[17-02-08 13:21:05:247 CST] done
Object.prototype.flatten = function (obj) {
let ans = {};
let anotherObj = { ...obj };
function performFlatten(anotherObj) {
Object.keys(anotherObj).forEach((key, idx) => {
if (typeof anotherObj[key] !== 'object') {
ans[key] = anotherObj[key];
console.log('ans so far : ', ans);
} else {
console.log(key, { ...anotherObj[key] });
performFlatten(anotherObj[key]);
}
})
}
performFlatten(anotherObj);
return ans;
}
let ans = flatten(obj);
console.log(ans);
I added +/- 10-15% efficiency to the selected answer by minor code refactoring and moving the recursive function outside of the function namespace.
See my question: Are namespaced functions reevaluated on every call? for why this slows nested functions down.
function _flatten (target, obj, path) {
var i, empty;
if (obj.constructor === Object) {
empty = true;
for (i in obj) {
empty = false;
_flatten(target, obj[i], path ? path + '.' + i : i);
}
if (empty && path) {
target[path] = {};
}
}
else if (obj.constructor === Array) {
i = obj.length;
if (i > 0) {
while (i--) {
_flatten(target, obj[i], path + '[' + i + ']');
}
} else {
target[path] = [];
}
}
else {
target[path] = obj;
}
}
function flatten (data) {
var result = {};
_flatten(result, data, null);
return result;
}
See benchmark.
Here's a recursive solution for flatten I put together in PowerShell:
#---helper function for ConvertTo-JhcUtilJsonTable
#
function getNodes {
param (
[Parameter(Mandatory)]
[System.Object]
$job,
[Parameter(Mandatory)]
[System.String]
$path
)
$t = $job.GetType()
$ct = 0
$h = #{}
if ($t.Name -eq 'PSCustomObject') {
foreach ($m in Get-Member -InputObject $job -MemberType NoteProperty) {
getNodes -job $job.($m.Name) -path ($path + '.' + $m.Name)
}
}
elseif ($t.Name -eq 'Object[]') {
foreach ($o in $job) {
getNodes -job $o -path ($path + "[$ct]")
$ct++
}
}
else {
$h[$path] = $job
$h
}
}
#---flattens a JSON document object into a key value table where keys are proper JSON paths corresponding to their value
#
function ConvertTo-JhcUtilJsonTable {
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[System.Object[]]
$jsonObj
)
begin {
$rootNode = 'root'
}
process {
foreach ($o in $jsonObj) {
$table = getNodes -job $o -path $rootNode
# $h = #{}
$a = #()
$pat = '^' + $rootNode
foreach ($i in $table) {
foreach ($k in $i.keys) {
# $h[$k -replace $pat, ''] = $i[$k]
$a += New-Object -TypeName psobject -Property #{'Key' = $($k -replace $pat, ''); 'Value' = $i[$k]}
# $h[$k -replace $pat, ''] = $i[$k]
}
}
# $h
$a
}
}
end{}
}
Example:
'{"name": "John","Address": {"house": "1234", "Street": "Boogie Ave"}, "pets": [{"Type": "Dog", "Age": 4, "Toys": ["rubberBall", "rope"]},{"Type": "Cat", "Age": 7, "Toys": ["catNip"]}]}' | ConvertFrom-Json | ConvertTo-JhcUtilJsonTable
Key Value
--- -----
.Address.house 1234
.Address.Street Boogie Ave
.name John
.pets[0].Age 4
.pets[0].Toys[0] rubberBall
.pets[0].Toys[1] rope
.pets[0].Type Dog
.pets[1].Age 7
.pets[1].Toys[0] catNip
.pets[1].Type Cat
I wanted an approach so that I could be able to easily convert my json data into a csv file.
The scenario is: I query data from somewhere and I receive an array of some model, like a bank extract.
This approach below is used to parse each one of these entries.
function jsonFlatter(data, previousKey, obj) {
obj = obj || {}
previousKey = previousKey || ""
Object.keys(data).map(key => {
let newKey = `${previousKey}${previousKey ? "_" : ""}${key}`
let _value = data[key]
let isArray = Array.isArray(_value)
if (typeof _value !== "object" || isArray || _value == null) {
if (isArray) {
_value = JSON.stringify(_value)
} else if (_value == null) {
_value = "null"
}
obj[newKey] = _value
} else if (typeof _value === "object") {
if (!Object.keys(_value).length) {
obj[newKey] = "null"
} else {
return jsonFlatter(_value, newKey, obj)
}
}
})
return obj
}
This way, I can count on the uniformity of the keys and inner keys of my object model, but arrays are simply stringified since I can't rely on their uniformity. Also, empty objects become the string "null", since I still want it's key to appear in the final result.
Usage example:
const test_data = {
a: {
aa: {
aaa: 4354,
aab: 654
},
ab: 123
},
b: 234,
c: {},
d: []
}
console.log('result', jsonFlatter(test_data))
#### output
{
"a_aa_aaa": 4354,
"a_aa_aab": 654,
"a_ab": 123,
"b": 234,
"c": "null",
"d": "[]"
}
try this one:
function getFlattenObject(data, response = {}) {
for (const key in data) {
if (typeof data[key] === 'object' && !Array.isArray(data[key])) {
getFlattenObject(data[key], response);
} else {
response[key] = data[key];
}
}
return response;
}
I'd like to add a new version of flatten case (this is what i needed :)) which, according to my probes with the above jsFiddler, is slightly faster then the currently selected one.
Moreover, me personally see this snippet a bit more readable, which is of course important for multi-developer projects.
function flattenObject(graph) {
let result = {},
item,
key;
function recurr(graph, path) {
if (Array.isArray(graph)) {
graph.forEach(function (itm, idx) {
key = path + '[' + idx + ']';
if (itm && typeof itm === 'object') {
recurr(itm, key);
} else {
result[key] = itm;
}
});
} else {
Reflect.ownKeys(graph).forEach(function (p) {
key = path + '.' + p;
item = graph[p];
if (item && typeof item === 'object') {
recurr(item, key);
} else {
result[key] = item;
}
});
}
}
recurr(graph, '');
return result;
}
Here is some code I wrote to flatten an object I was working with. It creates a new class that takes every nested field and brings it into the first layer. You could modify it to unflatten by remembering the original placement of the keys. It also assumes the keys are unique even across nested objects. Hope it helps.
class JSONFlattener {
ojson = {}
flattenedjson = {}
constructor(original_json) {
this.ojson = original_json
this.flattenedjson = {}
this.flatten()
}
flatten() {
Object.keys(this.ojson).forEach(function(key){
if (this.ojson[key] == null) {
} else if (this.ojson[key].constructor == ({}).constructor) {
this.combine(new JSONFlattener(this.ojson[key]).returnJSON())
} else {
this.flattenedjson[key] = this.ojson[key]
}
}, this)
}
combine(new_json) {
//assumes new_json is a flat array
Object.keys(new_json).forEach(function(key){
if (!this.flattenedjson.hasOwnProperty(key)) {
this.flattenedjson[key] = new_json[key]
} else {
console.log(key+" is a duplicate key")
}
}, this)
}
returnJSON() {
return this.flattenedjson
}
}
console.log(new JSONFlattener(dad_dictionary).returnJSON())
As an example, it converts
nested_json = {
"a": {
"b": {
"c": {
"d": {
"a": 0
}
}
}
},
"z": {
"b":1
},
"d": {
"c": {
"c": 2
}
}
}
into
{ a: 0, b: 1, c: 2 }
You can try out the package jpflat.
It flattens, inflates, resolves promises, flattens arrays, has customizable path creation and customizable value serialization.
The reducers and serializers receive the whole path as an array of it's parts, so more complex operations can be done to the path instead of modifying a single key or changing the delimiter.
Json path is the default, hence "jp"flat.
https://www.npmjs.com/package/jpflat
let flatFoo = await require('jpflat').flatten(foo)
I threw some code together to flatten and un-flatten complex/nested JavaScript objects. It works, but it's a bit slow (triggers the 'long script' warning).
For the flattened names I want "." as the delimiter and [INDEX] for arrays.
Examples:
un-flattened | flattened
---------------------------
{foo:{bar:false}} => {"foo.bar":false}
{a:[{b:["c","d"]}]} => {"a[0].b[0]":"c","a[0].b[1]":"d"}
[1,[2,[3,4],5],6] => {"[0]":1,"[1].[0]":2,"[1].[1].[0]":3,"[1].[1].[1]":4,"[1].[2]":5,"[2]":6}
I created a benchmark that ~simulates my use case http://jsfiddle.net/WSzec/
Get a nested object
Flatten it
Look through it and possibly modify it while flattened
Unflatten it back to it's original nested format to be shipped away
I would like faster code: For clarification, code that completes the JSFiddle benchmark (http://jsfiddle.net/WSzec/) significantly faster (~20%+ would be nice) in IE 9+, FF 24+, and Chrome 29+.
Here's the relevant JavaScript code: Current Fastest: http://jsfiddle.net/WSzec/6/
var unflatten = function(data) {
"use strict";
if (Object(data) !== data || Array.isArray(data))
return data;
var result = {}, cur, prop, idx, last, temp;
for(var p in data) {
cur = result, prop = "", last = 0;
do {
idx = p.indexOf(".", last);
temp = p.substring(last, idx !== -1 ? idx : undefined);
cur = cur[prop] || (cur[prop] = (!isNaN(parseInt(temp)) ? [] : {}));
prop = temp;
last = idx + 1;
} while(idx >= 0);
cur[prop] = data[p];
}
return result[""];
}
var flatten = function(data) {
var result = {};
function recurse (cur, prop) {
if (Object(cur) !== cur) {
result[prop] = cur;
} else if (Array.isArray(cur)) {
for(var i=0, l=cur.length; i<l; i++)
recurse(cur[i], prop ? prop+"."+i : ""+i);
if (l == 0)
result[prop] = [];
} else {
var isEmpty = true;
for (var p in cur) {
isEmpty = false;
recurse(cur[p], prop ? prop+"."+p : p);
}
if (isEmpty)
result[prop] = {};
}
}
recurse(data, "");
return result;
}
EDIT 1 Modified the above to #Bergi 's implementation which is currently the fastest. As an aside, using ".indexOf" instead of "regex.exec" is around 20% faster in FF but 20% slower in Chrome; so I'll stick with the regex since it's simpler (here's my attempt at using indexOf to replace the regex http://jsfiddle.net/WSzec/2/).
EDIT 2 Building on #Bergi 's idea I managed to created a faster non-regex version (3x faster in FF and ~10% faster in Chrome). http://jsfiddle.net/WSzec/6/ In the this (the current) implementation the rules for key names are simply, keys cannot start with an integer or contain a period.
Example:
{"foo":{"bar":[0]}} => {"foo.bar.0":0}
EDIT 3 Adding #AaditMShah 's inline path parsing approach (rather than String.split) helped to improve the unflatten performance. I'm very happy with the overall performance improvement reached.
The latest jsfiddle and jsperf:
http://jsfiddle.net/WSzec/14/
http://jsperf.com/flatten-un-flatten/4
Here's my much shorter implementation:
Object.unflatten = function(data) {
"use strict";
if (Object(data) !== data || Array.isArray(data))
return data;
var regex = /\.?([^.\[\]]+)|\[(\d+)\]/g,
resultholder = {};
for (var p in data) {
var cur = resultholder,
prop = "",
m;
while (m = regex.exec(p)) {
cur = cur[prop] || (cur[prop] = (m[2] ? [] : {}));
prop = m[2] || m[1];
}
cur[prop] = data[p];
}
return resultholder[""] || resultholder;
};
flatten hasn't changed much (and I'm not sure whether you really need those isEmpty cases):
Object.flatten = function(data) {
var result = {};
function recurse (cur, prop) {
if (Object(cur) !== cur) {
result[prop] = cur;
} else if (Array.isArray(cur)) {
for(var i=0, l=cur.length; i<l; i++)
recurse(cur[i], prop + "[" + i + "]");
if (l == 0)
result[prop] = [];
} else {
var isEmpty = true;
for (var p in cur) {
isEmpty = false;
recurse(cur[p], prop ? prop+"."+p : p);
}
if (isEmpty && prop)
result[prop] = {};
}
}
recurse(data, "");
return result;
}
Together, they run your benchmark in about the half of the time (Opera 12.16: ~900ms instead of ~ 1900ms, Chrome 29: ~800ms instead of ~1600ms).
Note: This and most other solutions answered here focus on speed and are susceptible to prototype pollution and shold not be used on untrusted objects.
I wrote two functions to flatten and unflatten a JSON object.
Flatten a JSON object:
var flatten = (function (isArray, wrapped) {
return function (table) {
return reduce("", {}, table);
};
function reduce(path, accumulator, table) {
if (isArray(table)) {
var length = table.length;
if (length) {
var index = 0;
while (index < length) {
var property = path + "[" + index + "]", item = table[index++];
if (wrapped(item) !== item) accumulator[property] = item;
else reduce(property, accumulator, item);
}
} else accumulator[path] = table;
} else {
var empty = true;
if (path) {
for (var property in table) {
var item = table[property], property = path + "." + property, empty = false;
if (wrapped(item) !== item) accumulator[property] = item;
else reduce(property, accumulator, item);
}
} else {
for (var property in table) {
var item = table[property], empty = false;
if (wrapped(item) !== item) accumulator[property] = item;
else reduce(property, accumulator, item);
}
}
if (empty) accumulator[path] = table;
}
return accumulator;
}
}(Array.isArray, Object));
Performance:
It's faster than the current solution in Opera. The current solution is 26% slower in Opera.
It's faster than the current solution in Firefox. The current solution is 9% slower in Firefox.
It's faster than the current solution in Chrome. The current solution is 29% slower in Chrome.
Unflatten a JSON object:
function unflatten(table) {
var result = {};
for (var path in table) {
var cursor = result, length = path.length, property = "", index = 0;
while (index < length) {
var char = path.charAt(index);
if (char === "[") {
var start = index + 1,
end = path.indexOf("]", start),
cursor = cursor[property] = cursor[property] || [],
property = path.slice(start, end),
index = end + 1;
} else {
var cursor = cursor[property] = cursor[property] || {},
start = char === "." ? index + 1 : index,
bracket = path.indexOf("[", start),
dot = path.indexOf(".", start);
if (bracket < 0 && dot < 0) var end = index = length;
else if (bracket < 0) var end = index = dot;
else if (dot < 0) var end = index = bracket;
else var end = index = bracket < dot ? bracket : dot;
var property = path.slice(start, end);
}
}
cursor[property] = table[path];
}
return result[""];
}
Performance:
It's faster than the current solution in Opera. The current solution is 5% slower in Opera.
It's slower than the current solution in Firefox. My solution is 26% slower in Firefox.
It's slower than the current solution in Chrome. My solution is 6% slower in Chrome.
Flatten and unflatten a JSON object:
Overall my solution performs either equally well or even better than the current solution.
Performance:
It's faster than the current solution in Opera. The current solution is 21% slower in Opera.
It's as fast as the current solution in Firefox.
It's faster than the current solution in Firefox. The current solution is 20% slower in Chrome.
Output format:
A flattened object uses the dot notation for object properties and the bracket notation for array indices:
{foo:{bar:false}} => {"foo.bar":false}
{a:[{b:["c","d"]}]} => {"a[0].b[0]":"c","a[0].b[1]":"d"}
[1,[2,[3,4],5],6] => {"[0]":1,"[1][0]":2,"[1][1][0]":3,"[1][1][1]":4,"[1][2]":5,"[2]":6}
In my opinion this format is better than only using the dot notation:
{foo:{bar:false}} => {"foo.bar":false}
{a:[{b:["c","d"]}]} => {"a.0.b.0":"c","a.0.b.1":"d"}
[1,[2,[3,4],5],6] => {"0":1,"1.0":2,"1.1.0":3,"1.1.1":4,"1.2":5,"2":6}
Advantages:
Flattening an object is faster than the current solution.
Flattening and unflattening an object is as fast as or faster than the current solution.
Flattened objects use both the dot notation and the bracket notation for readability.
Disadvantages:
Unflattening an object is slower than the current solution in most (but not all) cases.
The current JSFiddle demo gave the following values as output:
Nested : 132175 : 63
Flattened : 132175 : 564
Nested : 132175 : 54
Flattened : 132175 : 508
My updated JSFiddle demo gave the following values as output:
Nested : 132175 : 59
Flattened : 132175 : 514
Nested : 132175 : 60
Flattened : 132175 : 451
I'm not really sure what that means, so I'll stick with the jsPerf results. After all jsPerf is a performance benchmarking utility. JSFiddle is not.
ES6 version:
const flatten = (obj, path = '') => {
if (!(obj instanceof Object)) return {[path.replace(/\.$/g, '')]:obj};
return Object.keys(obj).reduce((output, key) => {
return obj instanceof Array ?
{...output, ...flatten(obj[key], path + '[' + key + '].')}:
{...output, ...flatten(obj[key], path + key + '.')};
}, {});
}
Example:
console.log(flatten({a:[{b:["c","d"]}]}));
console.log(flatten([1,[2,[3,4],5],6]));
3 ½ Years later...
For my own project I wanted to flatten JSON objects in mongoDB dot notation and came up with a simple solution:
/**
* Recursively flattens a JSON object using dot notation.
*
* NOTE: input must be an object as described by JSON spec. Arbitrary
* JS objects (e.g. {a: () => 42}) may result in unexpected output.
* MOREOVER, it removes keys with empty objects/arrays as value (see
* examples bellow).
*
* #example
* // returns {a:1, 'b.0.c': 2, 'b.0.d.e': 3, 'b.1': 4}
* flatten({a: 1, b: [{c: 2, d: {e: 3}}, 4]})
* // returns {a:1, 'b.0.c': 2, 'b.0.d.e.0': true, 'b.0.d.e.1': false, 'b.0.d.e.2.f': 1}
* flatten({a: 1, b: [{c: 2, d: {e: [true, false, {f: 1}]}}]})
* // return {a: 1}
* flatten({a: 1, b: [], c: {}})
*
* #param obj item to be flattened
* #param {Array.string} [prefix=[]] chain of prefix joined with a dot and prepended to key
* #param {Object} [current={}] result of flatten during the recursion
*
* #see https://docs.mongodb.com/manual/core/document/#dot-notation
*/
function flatten (obj, prefix, current) {
prefix = prefix || []
current = current || {}
// Remember kids, null is also an object!
if (typeof (obj) === 'object' && obj !== null) {
Object.keys(obj).forEach(key => {
this.flatten(obj[key], prefix.concat(key), current)
})
} else {
current[prefix.join('.')] = obj
}
return current
}
Features and/or caveats
It only accepts JSON objects. So if you pass something like {a: () => {}} you might not get what you wanted!
It removes empty arrays and objects. So this {a: {}, b: []} is flattened to {}.
Use this library:
npm install flat
Usage (from https://www.npmjs.com/package/flat):
Flatten:
var flatten = require('flat')
flatten({
key1: {
keyA: 'valueI'
},
key2: {
keyB: 'valueII'
},
key3: { a: { b: { c: 2 } } }
})
// {
// 'key1.keyA': 'valueI',
// 'key2.keyB': 'valueII',
// 'key3.a.b.c': 2
// }
Un-flatten:
var unflatten = require('flat').unflatten
unflatten({
'three.levels.deep': 42,
'three.levels': {
nested: true
}
})
// {
// three: {
// levels: {
// deep: 42,
// nested: true
// }
// }
// }
Here's another approach that runs slower (about 1000ms) than the above answer, but has an interesting idea :-)
Instead of iterating through each property chain, it just picks the last property and uses a look-up-table for the rest to store the intermediate results. This look-up-table will be iterated until there are no property chains left and all values reside on uncocatenated properties.
JSON.unflatten = function(data) {
"use strict";
if (Object(data) !== data || Array.isArray(data))
return data;
var regex = /\.?([^.\[\]]+)$|\[(\d+)\]$/,
props = Object.keys(data),
result, p;
while(p = props.shift()) {
var m = regex.exec(p),
target;
if (m.index) {
var rest = p.slice(0, m.index);
if (!(rest in data)) {
data[rest] = m[2] ? [] : {};
props.push(rest);
}
target = data[rest];
} else {
target = result || (result = (m[2] ? [] : {}));
}
target[m[2] || m[1]] = data[p];
}
return result;
};
It currently uses the data input parameter for the table, and puts lots of properties on it - a non-destructive version should be possible as well. Maybe a clever lastIndexOf usage performs better than the regex (depends on the regex engine).
See it in action here.
You can use https://github.com/hughsk/flat
Take a nested Javascript object and flatten it, or unflatten an object with delimited keys.
Example from the doc
var flatten = require('flat')
flatten({
key1: {
keyA: 'valueI'
},
key2: {
keyB: 'valueII'
},
key3: { a: { b: { c: 2 } } }
})
// {
// 'key1.keyA': 'valueI',
// 'key2.keyB': 'valueII',
// 'key3.a.b.c': 2
// }
var unflatten = require('flat').unflatten
unflatten({
'three.levels.deep': 42,
'three.levels': {
nested: true
}
})
// {
// three: {
// levels: {
// deep: 42,
// nested: true
// }
// }
// }
This code recursively flattens out JSON objects.
I included my timing mechanism in the code and it gives me 1ms but I'm not sure if that's the most accurate one.
var new_json = [{
"name": "fatima",
"age": 25,
"neighbour": {
"name": "taqi",
"location": "end of the street",
"property": {
"built in": 1990,
"owned": false,
"years on market": [1990, 1998, 2002, 2013],
"year short listed": [], //means never
}
},
"town": "Mountain View",
"state": "CA"
},
{
"name": "qianru",
"age": 20,
"neighbour": {
"name": "joe",
"location": "opposite to the park",
"property": {
"built in": 2011,
"owned": true,
"years on market": [1996, 2011],
"year short listed": [], //means never
}
},
"town": "Pittsburgh",
"state": "PA"
}]
function flatten(json, flattened, str_key) {
for (var key in json) {
if (json.hasOwnProperty(key)) {
if (json[key] instanceof Object && json[key] != "") {
flatten(json[key], flattened, str_key + "." + key);
} else {
flattened[str_key + "." + key] = json[key];
}
}
}
}
var flattened = {};
console.time('flatten');
flatten(new_json, flattened, "");
console.timeEnd('flatten');
for (var key in flattened){
console.log(key + ": " + flattened[key]);
}
Output:
flatten: 1ms
.0.name: fatima
.0.age: 25
.0.neighbour.name: taqi
.0.neighbour.location: end of the street
.0.neighbour.property.built in: 1990
.0.neighbour.property.owned: false
.0.neighbour.property.years on market.0: 1990
.0.neighbour.property.years on market.1: 1998
.0.neighbour.property.years on market.2: 2002
.0.neighbour.property.years on market.3: 2013
.0.neighbour.property.year short listed:
.0.town: Mountain View
.0.state: CA
.1.name: qianru
.1.age: 20
.1.neighbour.name: joe
.1.neighbour.location: opposite to the park
.1.neighbour.property.built in: 2011
.1.neighbour.property.owned: true
.1.neighbour.property.years on market.0: 1996
.1.neighbour.property.years on market.1: 2011
.1.neighbour.property.year short listed:
.1.town: Pittsburgh
.1.state: PA
Here's mine. It runs in <2ms in Google Apps Script on a sizable object. It uses dashes instead of dots for separators, and it doesn't handle arrays specially like in the asker's question, but this is what I wanted for my use.
function flatten (obj) {
var newObj = {};
for (var key in obj) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
var temp = flatten(obj[key])
for (var key2 in temp) {
newObj[key+"-"+key2] = temp[key2];
}
} else {
newObj[key] = obj[key];
}
}
return newObj;
}
Example:
var test = {
a: 1,
b: 2,
c: {
c1: 3.1,
c2: 3.2
},
d: 4,
e: {
e1: 5.1,
e2: 5.2,
e3: {
e3a: 5.31,
e3b: 5.32
},
e4: 5.4
},
f: 6
}
Logger.log("start");
Logger.log(JSON.stringify(flatten(test),null,2));
Logger.log("done");
Example output:
[17-02-08 13:21:05:245 CST] start
[17-02-08 13:21:05:246 CST] {
"a": 1,
"b": 2,
"c-c1": 3.1,
"c-c2": 3.2,
"d": 4,
"e-e1": 5.1,
"e-e2": 5.2,
"e-e3-e3a": 5.31,
"e-e3-e3b": 5.32,
"e-e4": 5.4,
"f": 6
}
[17-02-08 13:21:05:247 CST] done
Object.prototype.flatten = function (obj) {
let ans = {};
let anotherObj = { ...obj };
function performFlatten(anotherObj) {
Object.keys(anotherObj).forEach((key, idx) => {
if (typeof anotherObj[key] !== 'object') {
ans[key] = anotherObj[key];
console.log('ans so far : ', ans);
} else {
console.log(key, { ...anotherObj[key] });
performFlatten(anotherObj[key]);
}
})
}
performFlatten(anotherObj);
return ans;
}
let ans = flatten(obj);
console.log(ans);
I added +/- 10-15% efficiency to the selected answer by minor code refactoring and moving the recursive function outside of the function namespace.
See my question: Are namespaced functions reevaluated on every call? for why this slows nested functions down.
function _flatten (target, obj, path) {
var i, empty;
if (obj.constructor === Object) {
empty = true;
for (i in obj) {
empty = false;
_flatten(target, obj[i], path ? path + '.' + i : i);
}
if (empty && path) {
target[path] = {};
}
}
else if (obj.constructor === Array) {
i = obj.length;
if (i > 0) {
while (i--) {
_flatten(target, obj[i], path + '[' + i + ']');
}
} else {
target[path] = [];
}
}
else {
target[path] = obj;
}
}
function flatten (data) {
var result = {};
_flatten(result, data, null);
return result;
}
See benchmark.
Here's a recursive solution for flatten I put together in PowerShell:
#---helper function for ConvertTo-JhcUtilJsonTable
#
function getNodes {
param (
[Parameter(Mandatory)]
[System.Object]
$job,
[Parameter(Mandatory)]
[System.String]
$path
)
$t = $job.GetType()
$ct = 0
$h = #{}
if ($t.Name -eq 'PSCustomObject') {
foreach ($m in Get-Member -InputObject $job -MemberType NoteProperty) {
getNodes -job $job.($m.Name) -path ($path + '.' + $m.Name)
}
}
elseif ($t.Name -eq 'Object[]') {
foreach ($o in $job) {
getNodes -job $o -path ($path + "[$ct]")
$ct++
}
}
else {
$h[$path] = $job
$h
}
}
#---flattens a JSON document object into a key value table where keys are proper JSON paths corresponding to their value
#
function ConvertTo-JhcUtilJsonTable {
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[System.Object[]]
$jsonObj
)
begin {
$rootNode = 'root'
}
process {
foreach ($o in $jsonObj) {
$table = getNodes -job $o -path $rootNode
# $h = #{}
$a = #()
$pat = '^' + $rootNode
foreach ($i in $table) {
foreach ($k in $i.keys) {
# $h[$k -replace $pat, ''] = $i[$k]
$a += New-Object -TypeName psobject -Property #{'Key' = $($k -replace $pat, ''); 'Value' = $i[$k]}
# $h[$k -replace $pat, ''] = $i[$k]
}
}
# $h
$a
}
}
end{}
}
Example:
'{"name": "John","Address": {"house": "1234", "Street": "Boogie Ave"}, "pets": [{"Type": "Dog", "Age": 4, "Toys": ["rubberBall", "rope"]},{"Type": "Cat", "Age": 7, "Toys": ["catNip"]}]}' | ConvertFrom-Json | ConvertTo-JhcUtilJsonTable
Key Value
--- -----
.Address.house 1234
.Address.Street Boogie Ave
.name John
.pets[0].Age 4
.pets[0].Toys[0] rubberBall
.pets[0].Toys[1] rope
.pets[0].Type Dog
.pets[1].Age 7
.pets[1].Toys[0] catNip
.pets[1].Type Cat
I wanted an approach so that I could be able to easily convert my json data into a csv file.
The scenario is: I query data from somewhere and I receive an array of some model, like a bank extract.
This approach below is used to parse each one of these entries.
function jsonFlatter(data, previousKey, obj) {
obj = obj || {}
previousKey = previousKey || ""
Object.keys(data).map(key => {
let newKey = `${previousKey}${previousKey ? "_" : ""}${key}`
let _value = data[key]
let isArray = Array.isArray(_value)
if (typeof _value !== "object" || isArray || _value == null) {
if (isArray) {
_value = JSON.stringify(_value)
} else if (_value == null) {
_value = "null"
}
obj[newKey] = _value
} else if (typeof _value === "object") {
if (!Object.keys(_value).length) {
obj[newKey] = "null"
} else {
return jsonFlatter(_value, newKey, obj)
}
}
})
return obj
}
This way, I can count on the uniformity of the keys and inner keys of my object model, but arrays are simply stringified since I can't rely on their uniformity. Also, empty objects become the string "null", since I still want it's key to appear in the final result.
Usage example:
const test_data = {
a: {
aa: {
aaa: 4354,
aab: 654
},
ab: 123
},
b: 234,
c: {},
d: []
}
console.log('result', jsonFlatter(test_data))
#### output
{
"a_aa_aaa": 4354,
"a_aa_aab": 654,
"a_ab": 123,
"b": 234,
"c": "null",
"d": "[]"
}
try this one:
function getFlattenObject(data, response = {}) {
for (const key in data) {
if (typeof data[key] === 'object' && !Array.isArray(data[key])) {
getFlattenObject(data[key], response);
} else {
response[key] = data[key];
}
}
return response;
}
I'd like to add a new version of flatten case (this is what i needed :)) which, according to my probes with the above jsFiddler, is slightly faster then the currently selected one.
Moreover, me personally see this snippet a bit more readable, which is of course important for multi-developer projects.
function flattenObject(graph) {
let result = {},
item,
key;
function recurr(graph, path) {
if (Array.isArray(graph)) {
graph.forEach(function (itm, idx) {
key = path + '[' + idx + ']';
if (itm && typeof itm === 'object') {
recurr(itm, key);
} else {
result[key] = itm;
}
});
} else {
Reflect.ownKeys(graph).forEach(function (p) {
key = path + '.' + p;
item = graph[p];
if (item && typeof item === 'object') {
recurr(item, key);
} else {
result[key] = item;
}
});
}
}
recurr(graph, '');
return result;
}
Here is some code I wrote to flatten an object I was working with. It creates a new class that takes every nested field and brings it into the first layer. You could modify it to unflatten by remembering the original placement of the keys. It also assumes the keys are unique even across nested objects. Hope it helps.
class JSONFlattener {
ojson = {}
flattenedjson = {}
constructor(original_json) {
this.ojson = original_json
this.flattenedjson = {}
this.flatten()
}
flatten() {
Object.keys(this.ojson).forEach(function(key){
if (this.ojson[key] == null) {
} else if (this.ojson[key].constructor == ({}).constructor) {
this.combine(new JSONFlattener(this.ojson[key]).returnJSON())
} else {
this.flattenedjson[key] = this.ojson[key]
}
}, this)
}
combine(new_json) {
//assumes new_json is a flat array
Object.keys(new_json).forEach(function(key){
if (!this.flattenedjson.hasOwnProperty(key)) {
this.flattenedjson[key] = new_json[key]
} else {
console.log(key+" is a duplicate key")
}
}, this)
}
returnJSON() {
return this.flattenedjson
}
}
console.log(new JSONFlattener(dad_dictionary).returnJSON())
As an example, it converts
nested_json = {
"a": {
"b": {
"c": {
"d": {
"a": 0
}
}
}
},
"z": {
"b":1
},
"d": {
"c": {
"c": 2
}
}
}
into
{ a: 0, b: 1, c: 2 }
You can try out the package jpflat.
It flattens, inflates, resolves promises, flattens arrays, has customizable path creation and customizable value serialization.
The reducers and serializers receive the whole path as an array of it's parts, so more complex operations can be done to the path instead of modifying a single key or changing the delimiter.
Json path is the default, hence "jp"flat.
https://www.npmjs.com/package/jpflat
let flatFoo = await require('jpflat').flatten(foo)
JavaScript objects have no order stored for properties (according to the spec). Firefox seems to preserve the order of definition of properties when using a for...in loop. Is this behaviour something that I can rely on? If not is there a piece of JavaScript code somewhere that implements an ordered hash type?
JavaScript in 2016, specifically EcmaScript 6, supports the Map built-in class.
A Map object iterates its elements in insertion order — a for...of loop returns an array of [key, value] for each iteration.
That's what you need. (I wonder why that is the first info in the description of this data structure, though.)
For example,
m = new Map()
m.set(3,'three')
m.set(1,'one')
m.set(2,'two')
m // Map { 3 => 'three', 1 => 'one', 2 => 'two' }
[...m.keys()] // [ 3, 1, 2 ]
or the example from the docs:
var myMap = new Map();
myMap.set(0, 'zero');
myMap.set(1, 'one');
myMap // Map { 0 => 'zero', 1 => 'one' }
for (var [key, value] of myMap) {
console.log(key + " = " + value);
}
for (var key of myMap.keys()) {
console.log(key);
}
for (var value of myMap.values()) {
console.log(value);
}
for (var [key, value] of myMap.entries()) {
console.log(key + " = " + value);
}
#Vardhan 's answer in plain JavaScript, using closure instead of classical OO and adding an insert() method:
function makeOrderedHash() {
let keys = [];
let vals = {};
return {
push: function(k,v) {
if (!vals[k]) keys.push(k);
vals[k] = v;
},
insert: function(pos,k,v) {
if (!vals[k]) {
keys.splice(pos,0,k);
vals[k] = v;
}
},
val: function(k) {return vals[k]},
length: function(){return keys.length},
keys: function(){return keys},
values: function(){return vals}
};
};
let myHash = makeOrderedHash();
No, since the Object type is specified to be an unordered collection of properties, you can not rely on that. (Or: You can only rely on that an object is an unordered collection of properties.)
If you want to have an ordered hash set, you will need to implement it on your own.
This question come up as the top search result. After not finding a ordered hash, i just wrote this small coffescript. Hopefully this will help folks landing on this page:
## OrderedHash
# f = new OrderedHash
# f.push('a', 1)
# f.keys()
#
class OrderedHash
constructor: ->
#m_keys = []
#m_vals = {}
push: (k,v) ->
if not #m_vals[k]
#m_keys.push k
#m_vals[k] = v
length: () -> return #m_keys.length
keys: () -> return #m_keys
val: (k) -> return #m_vals[k]
vals: () -> return #m_vals
One trick I do is to store the data in a regular unordered hash, and then store the preferred order in an array. In JS, you can even make the order array part of the hash itself.
var myHash = {
a: "2",
b: "3",
c: "1"
};
myHash.order = [ myHash.c, myHash.a, myHash.b ];
alert("I can access values by key. Here's B: " + myHash.b);
var message = "I can access also loop over the values in order: ";
for (var i=0;i<myHash.order.length;i++)
{
message = message + myHash.order[i] + ", ";
}
alert(message)
It's not exactly elegant, but it gets the job done.
Realize its late but I needed this and couldn't find it elsewhere.
*UPDATE
Added necessary non-enumerable methods and properties.
Quick ES 5 implementation (polyfill as needed):
function orderedHash(object) {
'use strict'
var obj = object || {}
Object.defineProperties(this, {
'length': {
value: 0,
writable: true
},
'keys' : {
value: [],
writable: true
},
'sortedBy': {
value: '',
writable: true
}
})
this.hash(obj)
obj = null
}
Object.defineProperties(orderedHash.prototype, {
'sortByKeys': {
value: function sortByKeys() {
var i, len, name
this.keys.sort(function(a, b) {
return a >= b ? 1 : -1
})
for (i=0, len = this.keys.length; i < len; ++i) {
name = this.keys[i]
this[i] = this[name]
}
this.sortedBy = 'keys'
return null
}
},
'sortByValues': {
value: function sortByValues() {
var i, len, newIndex, name, ordered = [], names = this.keys.splice(0)
this.keys = []
for (i=0, len = this.length; i < len; ++i) {
ordered.push(this[i])
ordered.sort(function(a, b) {
return a >= b ? 1 : -1
})
newIndex = ordered.lastIndexOf(this[i])
name = names[i]
this.keys.splice(newIndex, 0 , name)
}
for (i=0, len = ordered.length; i < len; ++i) {
this[i] = ordered[i]
}
this.sortedBy = 'values'
return null
}
},
'insert': {
value: function insert(name, val) {
this[this.length] = val
this.length += 1
this.keys.push(name)
Object.defineProperty(this, name, {
value: val,
writable: true,
configurable: true
})
if (this.sortedBy == 'keys') {
this.sortByKeys()
} else {
this.sortByValues()
}
return null
}
},
'remove': {
value: function remove(name) {
var keys, index, i, len
delete this[name]
index = this.keys[name]
this.keys.splice(index, 1)
keys = Object.keys(this)
keys.sort(function(a, b) {
return a >= b ? 1 : -1
})
for (i=0, len = this.length; i < len; ++i) {
if (i >= index) {
this[i] = this[i + 1]
}
}
delete this[this.length - 1]
this.length -= 1
return null
}
},
'toString': {
value: function toString() {
var i, len, string = ""
for (i=0, len = this.length; i < len; ++i) {
string += this.keys[i]
string += ':'
string += this[i].toString()
if (!(i == len - 1)) {
string += ', '
}
}
return string
}
},
'toArray': {
value: function toArray() {
var i, len, arr = []
for (i=0, len = this.length; i < len; ++i) {
arr.push(this[i])
}
return arr
}
},
'getKeys': {
value: function getKeys() {
return this.keys.splice(0)
}
},
'hash': {
value: function hash(obj) {
var i, len, keys, name, val
keys = Object.keys(obj)
for (i=0, len = keys.length; i < len; ++i) {
name = keys[i]
val = obj[name]
this[this.length] = val
this.length += 1
this.keys.push(name)
Object.defineProperty(this, name, {
value: val,
writable: true,
configurable: true
})
}
if (this.sortedBy == 'keys') {
this.sortByKeys()
} else {
this.sortByValues()
}
return null
}
}
})
What happens here is that by using Object.defineProperty() instead of assignment can we make the properties non-enumerable, so when we iterate over the hash using for...in or Object.keys() we only get the ordered values but if we check hash.propertyname it will be there.
There are methods provided for insertion, removal, assimilating other objects (hash()), sorting by key, sorting by value, converting to array or string, getting the original index names, etc. I added them to the prototype but they are also non-enumerable, for...in loops still work.
I didn't take time to test it on non-primitives, but it works fine for strings, numbers, etc.
Taking #Craig_Walker solution, if you are only interested to know in which order the properties have been inserted, an easy solution would be :
var obj ={ }
var order = [];
function add(key, value) {
obj[key] = value;
order.push(key);
}
function getOldestKey() {
var key = order.shift();
return obj[key]
}
function getNewsetKey() {
var key = order.pop();
return obj[key]
}
You can now use a native Map since it preserves the insertion order when looped over with for in
A fairly simple way is to use an array to store the order.
You need to write a custom compare function to establish the order you require.
The down side is that you have to sort the array and keep track of relations, each time you change the hash table.
var order=[];
var hash={"h1":4,"h2":2,"h3":3,"h4":1};
function cmp(a,b) {
if (hash[a] < hash[b]) return -1;
if (hash[a] > hash[b]) return 1;
return 0;
}
// Add initial hash object to order array
for(i in hash) order.push(i);
order.sort(cmp);
// h4:1 h2:2 h3:3 h1:4
// Add entry
hash['h5']=2.5;
order.push('h5');
order.sort(cmp);
// h4:1 h2:2 h5:2.5 h3:3 h1:4
// Delete entry
order.splice(order.indexOf('h5'), 1);
delete hash['h5'];
// h4:1 h2:2 h3:3 h1:4
// Display ordered hash array (with keys)
for(i in order) console.log(order[i],hash[order[i]]);
class #OrderedHash
constructor: (h_as_array=[])->
#keys = []
#vals = {}
if h_as_array.length > 0
i = 0
while i < h_as_array.length
#push(h_as_array[i], h_as_array[i+1])
i += 2
#
push: (k,v)->
#keys.push k if not #vals[k]
#vals[k] = v
length: ()-> return #keys.length
keys: ()-> return #keys
val: (k)-> return #vals[k]
vals: ()-> return #vals
each: (callback)->
return unless callback
for k in #keys
callback(#vals[k])