Related
I am attempting to get the following JavaScript comparator function for an array which contains objects (keyed) but the below code isn't working.
I have tried many examples online (changing them per my needs) but so far no good.
See below for the code. What am I doing wrong?
Thank you for your time.
(As an FYI, the teContainer object below get's filled via an AJAX call)
var teContainer = {};
for (var i = 0; i < result.d.length; i++) {
var key = result.d[i].PersonId;
teContainer[key] = {
displayText: result.d[i].DLSPersonId + ' ' + result.d[i].PersonLastName + ', ' + result.d[i].PersonFirstName.substring(0, 5),
tEFirstName: result.d[i].PersonFirstName,
tELastName: result.d[i].PersonLastName,
tEID: result.d[i].PersonId,
orgID: result.d[i].DLS_OrgId
};
}
function tECompare(a, b) {
if (a.tELastName < b.tELastName)
return -1;
if (a.tELastName > b.tELastName)
return 1;
return 0;
}
teContainer.sort(tECompare);
Edit: Thanks to the answer; here is the corrected code which works:
var teContainer = [];
for (var i = 0; i < result.d.length; i++) {
teContainer.push = ({
displayText: result.d[i].DLSPersonId + ' ' + result.d[i].PersonLastName + ', ' + result.d[i].PersonFirstName.substring(0, 5),
tEFirstName: result.d[i].PersonFirstName,
tELastName: result.d[i].PersonLastName,
tEID: result.d[i].PersonId,
orgID: result.d[i].DLS_OrgId
});
}
function tECompare(a, b) {
if (a.tELastName < b.tELastName)
return -1;
if (a.tELastName > b.tELastName)
return 1;
return 0;
}
teContainer.sort(tECompare);
It's not running because sort isn't a method of an object. teContainer is an object, not an array. You're probably getting the following error:
Calling undefined method "sort"
In general your code is good. The main thing about sorting, is that the 'sort' function only works with Array, not with Object.
Assuming an Ajax result formatted as:
var result = {
d: [
{
PersonId: 1,
DLSPersonId: 1,
PersonLastName: "Doe",
PersonFirstName: "John",
DLS_OrgId: 1
},
{
PersonId: 2,
DLSPersonId: 2,
PersonLastName: "Smith",
PersonFirstName: "Alex",
DLS_OrgId: 2
},
{
PersonId: 3,
DLSPersonId: 3,
PersonLastName: "Vera",
PersonFirstName: "Luis",
DLS_OrgId: 3
}
]
};
Here is a demo
*In the demo, I didn,t chnage your teContainer object in case you need it like it is.
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 am trying to figure out an efficient way to remove objects that are duplicates from an array and looking for the most efficient answer. I looked around the internet everything seems to be using primitive data... or not scalable for large arrays. This is my current implementation which is can be improved and want to try to avoid labels.
Test.prototype.unique = function (arr, artist, title, cb) {
console.log(arr.length);
var n, y, x, i, r;
r = [];
o: for (i = 0, n = arr.length; i < n; i++) {
for (x = 0, y = r.length; x < y; x++) {
if (r[x].artist == arr[i].artist && r[x].title == arr[i].title) {
continue o;
}
}
r.push(arr[i]);
}
cb(r);
};
and the array looks something like this:
[{title: sky, artist: jon}, {title: rain, artist: Paul}, ....]
Order does not matter, but if sorting makes it more efficient then I am up for the challenge...
and for people who do not know o is a label and it is just saying jump back to the loop instead of pushing to the new array.
Pure javascript please no libs.
ANSWERS SO FAR:
The Performance Test for the answers below:
http://jsperf.com/remove-duplicates-for-loops
I see, the problem there is that the complexity is squared. There is one trick to do it, it's simply by using "Associative arrays".
You can get the array, loop over it, and add the value of the array as a key to the associative array. Since it doesn't allow duplicated keys, you will automatically get rid of the duplicates.
Since you are looking for title and artist when comparing, you can actually try to use something like:
var arrResult = {};
for (i = 0, n = arr.length; i < n; i++) {
var item = arr[i];
arrResult[ item.title + " - " + item.artist ] = item;
}
Then you just loop the arrResult again, and recreate the array.
var i = 0;
var nonDuplicatedArray = [];
for(var item in arrResult) {
nonDuplicatedArray[i++] = arrResult[item];
}
Updated to include Paul's comment. Thanks!
Here is a solution that works for me.
Helper functions:
// sorts an array of objects according to one field
// call like this: sortObjArray(myArray, "name" );
// it will modify the input array
sortObjArray = function(arr, field) {
arr.sort(
function compare(a,b) {
if (a[field] < b[field])
return -1;
if (a[field] > b[field])
return 1;
return 0;
}
);
}
// call like this: uniqueDishes = removeDuplicatesFromObjArray(dishes, "dishName");
// it will NOT modify the input array
// input array MUST be sorted by the same field (asc or desc doesn't matter)
removeDuplicatesFromObjArray = function(arr, field) {
var u = [];
arr.reduce(function (a, b) {
if (a[field] !== b[field]) u.push(b);
return b;
}, []);
return u;
}
and then simply call:
sortObjArray(dishes, "name");
dishes = removeDuplicatesFromObjArray(dishes, "name");
Basic sort-then-unique implementation, fiddle HERE:
function unique(arr) {
var comparer = function compareObject(a, b) {
if (a.title == b.title) {
if (a.artist < b.artist) {
return -1;
} else if (a.artist > b.artist) {
return 1;
} else {
return 0;
}
} else {
if (a.title < b.title) {
return -1;
} else {
return 1;
}
}
}
arr.sort(comparer);
console.log("Sorted: " + JSON.stringify(arr));
for (var i = 0; i < arr.length - 1; ++i) {
if (comparer(arr[i], arr[i+1]) === 0) {
arr.splice(i, 1);
console.log("Splicing: " + JSON.stringify(arr));
}
}
return arr;
}
It may or may not be the most efficient, and should be entirely scalable. I've added some console.logs so you can see it as it works.
EDIT
In the interest of saving on the space the function used, I did that for loop at the end, but it seems likely that didn't properly find only unique results (depsite it passing my simple jsfiddle test). Please try replacing my for loop with the following:
var checker;
var uniqueResults = [];
for (var i = 0; i < arr.length; ++i) {
if (!checker || comparer(checker, arr[i]) != 0) {
checker = arr[i];
uniqueResults.push(checker);
}
}
return uniqueResults;
I use this function. its not doing any sorting, but produces result. Cant say about performance as never measure it.
var unique = function(a){
var seen = [], result = [];
for(var len = a.length, i = len-1; i >= 0; i--){
if(!seen[a[i]]){
seen[a[i]] = true;
result.push(a[i]);
}
}
return result;
}
var ar = [1,2,3,1,1,1,1,1,"", "","","", "a", "b"];
console.log(unique(ar));// this will produce [1,2,3,"", "a", "b"] all unique elements.
Below is Henrique Feijo's answer with ample explanation and an example that you can cut and paste:
Goal: Convert an array of objects that contains duplicate objects (like this one)...
[
{
"id": 10620,
"name": "Things to Print"
},
{
"id": 10620,
"name": "Things to Print"
},
{
"id": 4334,
"name": "Interesting"
}
]
... Into an array of objects without duplicate objects (like this one):
[
{
"id": 10620,
"name": "Things to Print"
},
{
"id": 4334,
"name": "Interesting"
}
]
Explanation provided in the comments:
var allContent = [{
"id": 10620,
"name": "Things to Print"
}, {
"id": 10620,
"name": "Things to Print"
}, {
"id": 4334,
"name": "Interesting"
}]
//Put Objects Into As Associative Array. Each key consists of a composite value generated by each set of values from the objects in allContent.
var noDupeObj = {} //Create an associative array. It will not accept duplicate keys.
for (i = 0, n = allContent.length; i < n; i++) {
var item = allContent[i]; //Store each object as a variable. This helps with clarity in the next line.
noDupeObj[item.id + "|" + item.name] = item; //This is the critical step.
//Here, you create an object within the associative array that has a key composed of the two values from the original object.
// Use a delimiter to not have foo+bar handled like fo+obar
//Since the associative array will not allow duplicate keys, and the keys are determined by the content, then all duplicate content are removed.
//The value assigned to each key is the original object which is along for the ride and used to reconstruct the list in the next step.
}
//Recontructs the list with only the unique objects left in the doDupeObj associative array
var i = 0;
var nonDuplicatedArray = [];
for (var item in noDupeObj) {
nonDuplicatedArray[i++] = noDupeObj[item]; //Populate the array with the values from the noDupeObj.
}
console.log(nonDuplicatedArray)
For those who love ES6 and short stuff, here it's one solution:
const arr = [
{ title: "sky", artist: "Jon" },
{ title: "rain", artist: "Paul" },
{ title: "sky", artist: "Jon" }
];
Array.from(arr.reduce((a, o) => a.set(o.title, o), new Map()).values());
const arr = [
{ title: "sky", artist: "Jon" },
{ title: "rain", artist: "Paul" },
{ title: "sky", artist: "Jon" },
{ title: "rain", artist: "Jon" },
{ title: "cry", artist: "Jon" }
];
const unique = Array.from(arr.reduce((a, o) => a.set(o.title, o), new Map()).values());
console.log(`New array length: ${unique.length}`)
console.log(unique)
The above example only works for a unique title or id. Basically, it creates a new map for songs with duplicate titles.
Below code compares object with JSON as String format and removes duplicates and works fine with simple arrays.
Array.prototype.unique=function(a){
return function(){
return this.filter(a)
}
}(
function(a,b,c){
var tmp=[];
c.forEach(function(el){
tmp.push(JSON.stringify(el))
});
return tmp.indexOf(JSON.stringify(a),b+1)<0
})
If you are using underscore js, it is easy to remove duplicate object.
http://underscorejs.org/#uniq
function remove_duplicates(objectsArray) {
var arr = [], collection = [];
$.each(objectsArray, function (index, value) {
if ($.inArray(value.id, arr) == -1) {
arr.push(value.id);
collection.push(value);
}
});
return collection;
}
Here's what I have in mind:
Given an array of objects:
[
{
"name": "Kirk",
"count": 1
},
{
"name": "Spock",
"count": 1
},
{
"name": "Kirk",
"count": 1
}
]
I am trying to get:
[
{
"name": "Kirk",
"count": 2
},
{
"name": "Spock",
"count": 1
}
]
I am wondering if there's already an algorithm, perhaps combining some higher order functions to achieve this. I could do this easily with loops, but I am looking for a way to solve it using higher order functions. If someone could point me to what I should use to achieve this, it would be great. Again, I'm looking for something as elegant as possible (two maps and a filter would not be a big improvement from loops).
This is my current solution and I'm looking for something better (and by better I mean more expressive):
function mergeDuplicates(input) {
var output = [];
var existingItem = null;
input.forEach(function (inputItem) {
existingItem = _.find(output, function (outputItem) {
return inputItem.name === outputItem.name;
});
existingItem ? existingItem.count += 1 : output.push({
name: inputItem.name,
count: 1
});
existingItem = null;
});
return output;
}
To make line #10 more clear: in the original array, count might be either non-existing or 1, hence I set it to 1.
I think the best way would be to hash each object if it does not already exist, and delete the ones that you found already hashed in your structure. This way, you'd be checking the existence of each object only 1 (depends on your hash scheme).
Just a function if you would like to use.
function merge(arr) {
for(var o = {}, i; i=arr.shift(); o[i.name] = i.count + (o[i.name] || 0));
for(i in o) arr.push({name:i, count:o[i]});
}
Calling :
var myArray = [{"name":"Kirk","count":1},
{"name":"Spock","count":1},
{"name":"Kirk","count":1}];
merge(myArray);
// myArray is now : [{"name":"Kirk","count":2}, {"name":"Spock","count":1}]
You can use reduce which is actually a fold.
a.reduce(function(p, c) {
var n = c.name;
if (p[n])
p[n].count++;
else
p[n] = c;
return p;
}, {})
will give you a object with "Kirk" and "Spock" as the key, what you want as values.
I know this is an old question, but I couldn't resist trying to solve it. Instead of two maps and a filter, we use a sort and then a reduce. This was a fun one to sort out :-)
function mergeDuplicates(list, prop, cb){
return list.sort(function(a,b){
if(a[prop] < b[prop]){ return -1;}
if(a[prop] > b[prop]){return 1;}
return 0;
}).reduce(function(acc, item, index, array){
if(index > 0 && array[index-1][prop] === item[prop]){
cb(acc[acc.length-1], item);
return acc;
}else{
var newItem = Object.assign({}, item);
cb(newItem);
acc.push(newItem);
return acc;
}
}, []);
}
Then use it like this:
var newList = mergeDuplicates(list, "name", function(item, dup){
if(dup){
item.count++;
}else{
item.count = 1;
}
});
EDIT: Here's another take at it using reduce and using an object as a hashmap to store duplicates (similar to some of the other answers). This one uses ramdajs
const mergeDups = (cb, prop, list) => R.pipe(
R.reduce((acc, item) => (
R.has(item[prop], acc) ?
R.assoc(item[prop], cb(acc[item[prop]], item), acc) :
R.assoc(item[prop], cb(item), acc)
), {}),
R.values
)(list);
const cb = (i, d) => ( !R.isNil(d) ?
R.assoc('count', i.count + 1, i) :
R.assoc('count', 1, i) )
mergeDups(cb, 'name', items);
Here it is in the repl on Ramda's site
Try this better i'll useful resolve your issues
cleanup(arrayOfObj, 'name');
function cleanup(arr, prop) {
var new_arr = [];
var lookup = {};
for (var i in arr) {
lookup[arr[i][prop]] = arr[i];
}
for (i in lookup) {
new_arr.push(lookup[i]);
}
return new_arr;
}
Yet another version using reduce function:
var items =
[
{
"name": "Kirk",
"count": 1
},
{
"name": "Spock",
"count": 1
},
{
"name": "Kirk",
"count": 1
}
];
var filtered = items.reduce(function(prev, current,index){
if(!(current.name in prev.keys)) {
prev.keys[current.name] = index;
prev.result.push(current);
}
else{
prev.result[prev.keys[current.name]].count += current.count;
}
return prev;
},{result: [], keys: []}).result;
document.getElementById("output").innerHTML = JSON.stringify(filtered,null,2);
<pre id='output' />
I have an array like this:
employees = [
{
"id": 1,
"shift_id": 1,
"days": {
"2012-03-01": 1,
"2012-03-02": 1,
"2012-03-03": 1,
"2012-03-04": 0,
"2012-03-05": 0,
"2012-03-06": 0
}},
{
"id": 2,
"shift_id": 1,
"days": {
"2012-03-01": 0,
"2012-03-02": 1,
"2012-03-03": 1,
"2012-03-04": 1,
"2012-03-05": 1,
"2012-03-06": 0
}},
{
"id": 3,
"shift_id": 2,
"days": {
"2012-03-01": 0,
"2012-03-02": 0,
"2012-03-03": 1,
"2012-03-04": 1,
"2012-03-05": 1,
"2012-03-06": 1
}}
];
is there a way to access an element in this array using the id value?
maybe something in jquery?
like $(employees('id = 1');
Just loop through your array and check for the id:
var yourId = 1;
for (var i = 0, len = employees.length; i < len; i++) {
if (employees[i].id == yourId) {
// ...
}
}
You can use a function like this, which filters the array appropriately:
var getEmployee = function (id) {
return employees.filter(function(i) { return i.id == id; });
};
You can use .grep() method documented here:
var employee = $.grep(employees, function(e) { return e.id == 1 })[0];
Well, there's a jQuery way of doing it:
var findElementById = function(elements, id) {
return $.grep(elements, function(e) { return e.id === id; })[0];
}
Still I wonder why don't you just index the source array by id instead.
Maybe you are looking for something like the below:
$.grep(employees, function(n){return n.id==1});
Or this:
$.each(employee, function(){
if(this["id"] == 2){
console.log(this);
}
});
As far as I am aware, in order to achieve that you would have to loop through them
Array.prototype.getObjectById = function(x){
var catcher = false, i = 0;
while(!catcher){
catcher = this[i].id == x ? this[i] : false;
i++;
}
return catcher;
}
This function should help. It will extend the array object so you can use it as myArray.getObjectbyId(id);
By design, this will return the first object that meets the criteria. You could extend it like so:
Array.prototype.getObjectsById = function(x){
var catcher = [], i = 0;
for(var i = 0; i < this.length; i++){
if(this[i].id == value){
catcher.push(this[i]);
}
i++;
}
return catcher.length == 1 ? catcher[0] : catcher;
}
This will return an array of objects if more than one object matches the criteria.
Array.prototype.getObjectsByAttribute = function(x, criteria){
if(!criteria){criteria = 'id';}
var catcher = [], i = 0;
for(var i = 0; i < this.length; i++){
if(this[i].criteria == value){
catcher.push(this[i]);
}
i++;
}
return catcher.length == 1 ? catcher[0] : catcher;
}
This extends it further to look for any criteria.
I know this question is old, but for future reference if anyone else stumbles upon this question ...
Instead of trying to over-engineer a function to parse/examine your JSON, consider changing the structure of your data to suit its purpose.
Consider the example in the question:
data = [ {
"id": 1,
"shift_id": 1,
"days": {
"2012-03-01": 1,
"2012-03-02": 1,
"2012-03-03": 1,
"2012-03-04": 0,
"2012-03-05": 0,
"2012-03-06": 0
}}, { .. }, {...} ]
Structuring the data in this way only gives you sequential access to the objects with no way to lookup an object by a particular index. Array indices are generally meaningless in this context.
data[0] => { id : 1, .. }
data[1] => { id : 2, .. }
What happens if the id is non-sequential or alphanumeric?
Using an array wont help you search any faster, you'll still have to loop...
Instead consider using a hash table/object:
{
'id1' => { id : 1 , data : { ... } },
'id99' => { id : 99, data : { ... } },
'id2' => { id : 2 , data : { ... } },
}
You can use a string value for the key and get direct access to the data by doing something like:
data['id2'] => { id : 2, ... }
Which will give you direct access to the data you want to find (by id). By simply re-organizing the structure of the data we were able to go from a O(n) search to an O(1) search.
Keep in mind that this method may work better for some solutions than others, and there are a number of other considerations to make.
This is just one approach to how you might solve a problem when you want to lookup data by a unique property.
The accepted answer is great - modified a bit for an AngularJS app:
$rootScope.getObjectsByAttribute = function(inarry,infldnm,infldval){
// This will iterate through a fetchAll sql result set and return rows where fldnm==fldval
// If it finds 1 row it returns a single object, if more than that it returns an array of objects
// Usage: result = $rootScope.getObjectsByAttribute(myarray,'myfldnm',myfldval);
if(!infldnm){infldnm = 'id';}
var catcher = [], i = 0;
for(i = 0; i < inarry.length; i++){
if(inarry[i][infldnm] == infldval){
catcher.push(inarry[i]);
}
}
return catcher.length == 1 ? catcher[0] : catcher;
}