Multiple level attribute retrieval using array notation from a JSON object - javascript

I've got a mentally taxing problem here, where I've got a JSON object retrieved using a collection in Backbone. This is what the object looks like:
{
"MatchID": "00000001",
"Date": "1970-01-01T00:00:00.000Z",
"OriginalID": "",
"Stage": {
"StageNumber": "0",
"StageType": "Stage Type"
},
"Round": {
"RoundNumber": "0",
"Name": "Round Name"
},
"Leg": "1",
"HomeTeam": {
"TeamID": "0",
"Name": "Home Team Name"
},
"AwayTeam": {
"TeamID": "0",
"Name": "Away Team Name"
},
"Venue": {
"VenueID": "0",
"Name": "Venu Name"
},
"Referee": null,
}
What I want to do with this data, is filter it based on a particular attribute, such as the Venue.Name or Date attributes (which are different depths into the object, and can be deeper than two levels for some of the other data). I've got the following code inside a Backbone collection to filter and return a new collection with the contents filtered appropriately:
findWhere: function (Attribute, Value)
{
return new Project.Collections.Fixtures(this.filter(function (fixture)
{
return eval('fixture.attributes.' + Attribute) == Value;
}));
}
This allows me to specify in an attribute which attribute I want to filter by, and what I want it to be equal to, for any depth of object. The problem is, I really don't want to use "eval" to do this, but obviously I can't use "[Attribute]" for something like "AwayTeam.TeamID", as it won't work.
Does anyone know of a method I can use to achieve this functionality without using eval?

Something like this would let you traverse the hierarchy of objects to find a value:
var x = {
y: {
z: 1
}
};
function findprop(obj, path) {
var args = path.split('.'), i, l;
for (i=0, l=args.length; i<l; i++) {
if (!obj.hasOwnProperty(args[i]))
return;
obj = obj[args[i]];
}
return obj;
}
findprop(x, 'y.z');
You could add this as a method to your Fixtureobject:
Fixture = Backbone.Model.extend({
findprop: function(path) {
var obj = this.attributes,
args = path.split('.'),
i, l;
for (i=0, l=args.length; i<l; i++) {
if (!obj.hasOwnProperty(args[i]))
return;
obj = obj[ args[i] ];
}
return obj;
}
});
and use it to extract the value
var f = new Fixture();
f.findprop("HomeTeam.TeamID");
The findWhere method could then be rewritten as
findWhere: function (Attribute, Value)
{
return new Project.Collections.Fixtures(this.filter(function (fixture){
return fixture.findprop(Attribute) === Value;
}));
}
And a Fiddle to play with http://jsfiddle.net/nikoshr/wjWVJ/3/

Attributes in JavaScript objects can be accessed by square-bracket, string identifiers as well as the standard dot-notation.
In other words, this:
fixture.attributes.something
is the same as this:
fixture.attributes["something"]
You can also pass variable names in to the square brackets, the value of the variable is used as the key to retrieve.
So you can change your code to this:
findWhere: function (Attribute, Value)
{
return new Project.Collections.Fixtures(this.filter(function (fixture)
{
return fixture.attributes[Attribute] === Value;
}));
}
As you pointed out in the comments, this only handles one level objects and attributes. To get the nested attributes, you'll need to split the "Attribute" variable and loop through the parts. I like #nikoshr's solution for that.

What about using eval() like this:
var myObject = {
first: 'Some',
last: 'Person',
address: {
city: 'Melbourne',
country: 'Australia'
}
}
var propPath = 'address.city';
var city = eval("myObject."+propPath);
console.log(city); // = Melbourne

I took nikoshr's answer and added some recursive flair to it:
var findprop = function (obj, path) {
var args = (typeof path === 'string') ? path.split('.') : path,
thisProperty = obj[args[0]];
if (thisProperty === undefined) { return; } //not found
args.splice(0, 1); //pop this off the array
if (args.length > 0) { return findprop(thisProperty, args); } //recurse
else {return thisProperty; }
};
I'm not sure if there is much benefit to the recursion cpu cycle-wise, but I like recursive functions when they are appropriate

Related

Loop object with an object [duplicate]

I'd like to traverse a JSON object tree, but cannot find any library for that. It doesn't seem difficult but it feels like reinventing the wheel.
In XML there are so many tutorials showing how to traverse an XML tree with DOM :(
If you think jQuery is kind of overkill for such a primitive task, you could do something like that:
//your object
var o = {
foo:"bar",
arr:[1,2,3],
subo: {
foo2:"bar2"
}
};
//called with every property and its value
function process(key,value) {
console.log(key + " : "+value);
}
function traverse(o,func) {
for (var i in o) {
func.apply(this,[i,o[i]]);
if (o[i] !== null && typeof(o[i])=="object") {
//going one step down in the object tree!!
traverse(o[i],func);
}
}
}
//that's all... no magic, no bloated framework
traverse(o,process);
A JSON object is simply a Javascript object. That's actually what JSON stands for: JavaScript Object Notation. So you'd traverse a JSON object however you'd choose to "traverse" a Javascript object in general.
In ES2017 you would do:
Object.entries(jsonObj).forEach(([key, value]) => {
// do something with key and val
});
You can always write a function to recursively descend into the object:
function traverse(jsonObj) {
if( jsonObj !== null && typeof jsonObj == "object" ) {
Object.entries(jsonObj).forEach(([key, value]) => {
// key is either an array index or object key
traverse(value);
});
}
else {
// jsonObj is a number or string
}
}
This should be a good starting point. I highly recommend using modern javascript methods for such things, since they make writing such code much easier.
function traverse(o) {
for (var i in o) {
if (!!o[i] && typeof(o[i])=="object") {
console.log(i, o[i]);
traverse(o[i]);
} else {
console.log(i, o[i]);
}
}
}
There's a new library for traversing JSON data with JavaScript that supports many different use cases.
https://npmjs.org/package/traverse
https://github.com/substack/js-traverse
It works with all kinds of JavaScript objects. It even detects cycles.
It provides the path of each node, too.
Original Simplified Answer
For a newer way to do it if you don't mind dropping IE and mainly supporting more current browsers (check kangax's es6 table for compatibility). You can use es2015 generators for this. I've updated #TheHippo's answer accordingly. Of course if you really want IE support you can use the babel JavaScript transpiler.
// Implementation of Traverse
function* traverse(o, path=[]) {
for (var i in o) {
const itemPath = path.concat(i);
yield [i,o[i],itemPath,o];
if (o[i] !== null && typeof(o[i])=="object") {
//going one step down in the object tree!!
yield* traverse(o[i], itemPath);
}
}
}
// Traverse usage:
//that's all... no magic, no bloated framework
for(var [key, value, path, parent] of traverse({
foo:"bar",
arr:[1,2,3],
subo: {
foo2:"bar2"
}
})) {
// do something here with each key and value
console.log(key, value, path, parent);
}
If you want only own enumerable properties (basically non-prototype chain properties) you can change it to iterate using Object.keys and a for...of loop instead:
function* traverse(o,path=[]) {
for (var i of Object.keys(o)) {
const itemPath = path.concat(i);
yield [i,o[i],itemPath,o];
if (o[i] !== null && typeof(o[i])=="object") {
//going one step down in the object tree!!
yield* traverse(o[i],itemPath);
}
}
}
//that's all... no magic, no bloated framework
for(var [key, value, path, parent] of traverse({
foo:"bar",
arr:[1,2,3],
subo: {
foo2:"bar2"
}
})) {
// do something here with each key and value
console.log(key, value, path, parent);
}
EDIT: This edited answer solves infinite looping traversals.
Stopping Pesky Infinite Object Traversals
This edited answer still provides one of the added benefits of my original answer which allows you to use the provided generator function in order to use a cleaner and simple iterable interface (think using for of loops as in for(var a of b) where b is an iterable and a is an element of the iterable). By using the generator function along with being a simpler api it also helps with code reuse by making it so you don't have to repeat the iteration logic everywhere you want to iterate deeply on an object's properties and it also makes it possible to break out of the loop if you would like to stop iteration earlier.
One thing that I notice that has not been addressed and that isn't in my original answer is that you should be careful traversing arbitrary (i.e. any "random" set of) objects, because JavaScript objects can be self referencing. This creates the opportunity to have infinite looping traversals. Unmodified JSON data however cannot be self referencing, so if you are using this particular subset of JS objects you don't have to worry about infinite looping traversals and you can refer to my original answer or other answers. Here is an example of a non-ending traversal (note it is not a runnable piece of code, because otherwise it would crash your browser tab).
Also in the generator object in my edited example I opted to use Object.keys instead of for in which iterates only non-prototype keys on the object. You can swap this out yourself if you want the prototype keys included. See my original answer section below for both implementations with Object.keys and for in.
Worse - This will infinite loop on self-referential objects:
function* traverse(o, path=[]) {
for (var i of Object.keys(o)) {
const itemPath = path.concat(i);
yield [i,o[i],itemPath, o];
if (o[i] !== null && typeof(o[i])=="object") {
//going one step down in the object tree!!
yield* traverse(o[i], itemPath);
}
}
}
//your object
var o = {
foo:"bar",
arr:[1,2,3],
subo: {
foo2:"bar2"
}
};
// this self-referential property assignment is the only real logical difference
// from the above original example which ends up making this naive traversal
// non-terminating (i.e. it makes it infinite loop)
o.o = o;
//that's all... no magic, no bloated framework
for(var [key, value, path, parent] of traverse(o)) {
// do something here with each key and value
console.log(key, value, path, parent);
}
To save yourself from this you can add a set within a closure, so that when the function is first called it starts to build a memory of the objects it has seen and does not continue iteration once it comes across an already seen object. The below code snippet does that and thus handles infinite looping cases.
Better - This will not infinite loop on self-referential objects:
function* traverse(o) {
const memory = new Set();
function * innerTraversal (o, path=[]) {
if(memory.has(o)) {
// we've seen this object before don't iterate it
return;
}
// add the new object to our memory.
memory.add(o);
for (var i of Object.keys(o)) {
const itemPath = path.concat(i);
yield [i,o[i],itemPath, o];
if (o[i] !== null && typeof(o[i])=="object") {
//going one step down in the object tree!!
yield* innerTraversal(o[i], itemPath);
}
}
}
yield* innerTraversal(o);
}
//your object
var o = {
foo:"bar",
arr:[1,2,3],
subo: {
foo2:"bar2"
}
};
/// this self-referential property assignment is the only real logical difference
// from the above original example which makes more naive traversals
// non-terminating (i.e. it makes it infinite loop)
o.o = o;
console.log(o);
//that's all... no magic, no bloated framework
for(var [key, value, path, parent] of traverse(o)) {
// do something here with each key and value
console.log(key, value, path, parent);
}
EDIT: All above examples in this answer have been edited to include a new path variable yielded from the iterator as per #supersan's request. The path variable is an array of strings where each string in the array represents each key that was accessed to get to the resulting iterated value from the original source object. The path variable can be fed into lodash's get function/method. Or you could write your own version of lodash's get which handles only arrays like so:
function get (object, path) {
return path.reduce((obj, pathItem) => obj ? obj[pathItem] : undefined, object);
}
const example = {a: [1,2,3], b: 4, c: { d: ["foo"] }};
// these paths exist on the object
console.log(get(example, ["a", "0"]));
console.log(get(example, ["c", "d", "0"]));
console.log(get(example, ["b"]));
// these paths do not exist on the object
console.log(get(example, ["e", "f", "g"]));
console.log(get(example, ["b", "f", "g"]));
You could also make a set function like so:
function set (object, path, value) {
const obj = path.slice(0,-1).reduce((obj, pathItem) => obj ? obj[pathItem] : undefined, object)
if(obj && obj[path[path.length - 1]]) {
obj[path[path.length - 1]] = value;
}
return object;
}
const example = {a: [1,2,3], b: 4, c: { d: ["foo"] }};
// these paths exist on the object
console.log(set(example, ["a", "0"], 2));
console.log(set(example, ["c", "d", "0"], "qux"));
console.log(set(example, ["b"], 12));
// these paths do not exist on the object
console.log(set(example, ["e", "f", "g"], false));
console.log(set(example, ["b", "f", "g"], null));
EDIT Sep. 2020: I added a parent for quicker access of the previous object. This could allow you to more quickly build a reverse traverser. Also you could always modify the traversal algorithm to do breadth first search instead of depth first which is actually probably more predictable in fact here's a TypeScript version with Breadth First Search. Since this is a JavaScript question I'll put the JS version here:
var TraverseFilter;
(function (TraverseFilter) {
/** prevents the children from being iterated. */
TraverseFilter["reject"] = "reject";
})(TraverseFilter || (TraverseFilter = {}));
function* traverse(o) {
const memory = new Set();
function* innerTraversal(root) {
const queue = [];
queue.push([root, []]);
while (queue.length > 0) {
const [o, path] = queue.shift();
if (memory.has(o)) {
// we've seen this object before don't iterate it
continue;
}
// add the new object to our memory.
memory.add(o);
for (var i of Object.keys(o)) {
const item = o[i];
const itemPath = path.concat([i]);
const filter = yield [i, item, itemPath, o];
if (filter === TraverseFilter.reject)
continue;
if (item !== null && typeof item === "object") {
//going one step down in the object tree!!
queue.push([item, itemPath]);
}
}
}
}
yield* innerTraversal(o);
}
//your object
var o = {
foo: "bar",
arr: [1, 2, 3],
subo: {
foo2: "bar2"
}
};
/// this self-referential property assignment is the only real logical difference
// from the above original example which makes more naive traversals
// non-terminating (i.e. it makes it infinite loop)
o.o = o;
//that's all... no magic, no bloated framework
for (const [key, value, path, parent] of traverse(o)) {
// do something here with each key and value
console.log(key, value, path, parent);
}
Depends on what you want to do. Here's an example of traversing a JavaScript object tree, printing keys and values as it goes:
function js_traverse(o) {
var type = typeof o
if (type == "object") {
for (var key in o) {
print("key: ", key)
js_traverse(o[key])
}
} else {
print(o)
}
}
js> foobar = {foo: "bar", baz: "quux", zot: [1, 2, 3, {some: "hash"}]}
[object Object]
js> js_traverse(foobar)
key: foo
bar
key: baz
quux
key: zot
key: 0
1
key: 1
2
key: 2
3
key: 3
key: some
hash
If you're traversing an actual JSON string then you can use a reviver function.
function traverse (json, callback) {
JSON.parse(json, function (key, value) {
if (key !== '') {
callback.call(this, key, value)
}
return value
})
}
traverse('{"a":{"b":{"c":{"d":1}},"e":{"f":2}}}', function (key, value) {
console.log(arguments)
})
When traversing an object:
function traverse (obj, callback, trail) {
trail = trail || []
Object.keys(obj).forEach(function (key) {
var value = obj[key]
if (Object.getPrototypeOf(value) === Object.prototype) {
traverse(value, callback, trail.concat(key))
} else {
callback.call(obj, key, value, trail)
}
})
}
traverse({a: {b: {c: {d: 1}}, e: {f: 2}}}, function (key, value, trail) {
console.log(arguments)
})
I wanted to use the perfect solution of #TheHippo in an anonymous function, without use of process and trigger functions. The following worked for me, sharing for novice programmers like myself.
(function traverse(o) {
for (var i in o) {
console.log('key : ' + i + ', value: ' + o[i]);
if (o[i] !== null && typeof(o[i])=="object") {
//going on step down in the object tree!!
traverse(o[i]);
}
}
})
(json);
Most Javascript engines do not optimize tail recursion (this might not be an issue if your JSON isn't deeply nested), but I usually err on the side of caution and do iteration instead, e.g.
function traverse(o, fn) {
const stack = [o]
while (stack.length) {
const obj = stack.shift()
Object.keys(obj).forEach((key) => {
fn(key, obj[key], obj)
if (obj[key] instanceof Object) {
stack.unshift(obj[key])
return
}
})
}
}
const o = {
name: 'Max',
legal: false,
other: {
name: 'Maxwell',
nested: {
legal: true
}
}
}
const fx = (key, value, obj) => console.log(key, value)
traverse(o, fx)
My Script:
op_needed = [];
callback_func = function(val) {
var i, j, len;
results = [];
for (j = 0, len = val.length; j < len; j++) {
i = val[j];
if (i['children'].length !== 0) {
call_func(i['children']);
} else {
op_needed.push(i['rel_path']);
}
}
return op_needed;
};
Input JSON:
[
{
"id": null,
"name": "output",
"asset_type_assoc": [],
"rel_path": "output",
"children": [
{
"id": null,
"name": "output",
"asset_type_assoc": [],
"rel_path": "output/f1",
"children": [
{
"id": null,
"name": "v#",
"asset_type_assoc": [],
"rel_path": "output/f1/ver",
"children": []
}
]
}
]
}
]
Function Call:
callback_func(inp_json);
Output as per my Need:
["output/f1/ver"]
var test = {
depth00: {
depth10: 'string'
, depth11: 11
, depth12: {
depth20:'string'
, depth21:21
}
, depth13: [
{
depth22:'2201'
, depth23:'2301'
}
, {
depth22:'2202'
, depth23:'2302'
}
]
}
,depth01: {
depth10: 'string'
, depth11: 11
, depth12: {
depth20:'string'
, depth21:21
}
, depth13: [
{
depth22:'2201'
, depth23:'2301'
}
, {
depth22:'2202'
, depth23:'2302'
}
]
}
, depth02: 'string'
, dpeth03: 3
};
function traverse(result, obj, preKey) {
if(!obj) return [];
if (typeof obj == 'object') {
for(var key in obj) {
traverse(result, obj[key], (preKey || '') + (preKey ? '[' + key + ']' : key))
}
} else {
result.push({
key: (preKey || '')
, val: obj
});
}
return result;
}
document.getElementById('textarea').value = JSON.stringify(traverse([], test), null, 2);
<textarea style="width:100%;height:600px;" id="textarea"></textarea>
I've created library to traverse and edit deep nested JS objects. Check out API here: https://github.com/dominik791
You can also play with the library interactively using demo app:
https://dominik791.github.io/obj-traverse-demo/
Examples of usage:
You should always have root object which is the first parameter of each method:
var rootObj = {
name: 'rootObject',
children: [
{
'name': 'child1',
children: [ ... ]
},
{
'name': 'child2',
children: [ ... ]
}
]
};
The second parameter is always the name of property that holds nested objects. In above case it would be 'children'.
The third parameter is an object that you use to find object/objects that you want to find/modify/delete. For example if you're looking for object with id equal to 1, then you will pass { id: 1} as the third parameter.
And you can:
findFirst(rootObj, 'children', { id: 1 }) to find first object
with id === 1
findAll(rootObj, 'children', { id: 1 }) to find all objects
with id === 1
findAndDeleteFirst(rootObj, 'children', { id: 1 }) to delete first matching object
findAndDeleteAll(rootObj, 'children', { id: 1 }) to delete all matching objects
replacementObj is used as the last parameter in two last methods:
findAndModifyFirst(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'}) to change first found object with id === 1 to the { id: 2, name: 'newObj'}
findAndModifyAll(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'}) to change all objects with id === 1 to the { id: 2, name: 'newObj'}
We use object-scan for many data processing tasks. It's powerful once you wrap your head around it. Here is how you could do basic traversal
// const objectScan = require('object-scan');
const obj = { foo: 'bar', arr: [1, 2, 3], subo: { foo2: 'bar2' } };
objectScan(['**'], {
reverse: false,
filterFn: ({ key, value }) => {
console.log(key, value);
}
})(obj);
// => [ 'foo' ] bar
// => [ 'arr', 0 ] 1
// => [ 'arr', 1 ] 2
// => [ 'arr', 2 ] 3
// => [ 'arr' ] [ 1, 2, 3 ]
// => [ 'subo', 'foo2' ] bar2
// => [ 'subo' ] { foo2: 'bar2' }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan
This Will read All Nodes to a map.
function readJsonFile() {
let jsonString = getValueById("testDataContent");
let jsonObj = JSON.parse(jsonString);
let jsonElements = [];
jsonElements = traverse(jsonObj, jsonElements);
console.log(jsonElements)
}
function traverse(jsonObj, jsonElements) {
if (jsonObj !== null && typeof jsonObj == "object") {
Object.entries(jsonObj).forEach(([key, value]) => {
if (typeof value == "object") {
var obj = [];
let map = new Map();
map.set(key, traverse(value, obj))
jsonElements.push(map);
} else {
var obj = [];
obj.key = key;
obj.value = value;
jsonElements.push(obj);
}
});
} else {
}
return jsonElements;
}
You can get all keys / values and preserve the hierarchy with this
// get keys of an object or array
function getkeys(z){
var out=[];
for(var i in z){out.push(i)};
return out;
}
// print all inside an object
function allInternalObjs(data, name) {
name = name || 'data';
return getkeys(data).reduce(function(olist, k){
var v = data[k];
if(typeof v === 'object') { olist.push.apply(olist, allInternalObjs(v, name + '.' + k)); }
else { olist.push(name + '.' + k + ' = ' + v); }
return olist;
}, []);
}
// run with this
allInternalObjs({'a':[{'b':'c'},{'d':{'e':5}}],'f':{'g':'h'}}, 'ob')
This is a modification on (https://stackoverflow.com/a/25063574/1484447)
var localdata = [{''}]// Your json array
for (var j = 0; j < localdata.length; j++)
{$(localdata).each(function(index,item)
{
$('#tbl').append('<tr><td>' + item.FirstName +'</td></tr>);
}
The best solution for me was the following:
simple and without using any framework
var doSomethingForAll = function (arg) {
if (arg != undefined && arg.length > 0) {
arg.map(function (item) {
// do something for item
doSomethingForAll (item.subitem)
});
}
}

Update Javascript Object, nested Data - insert only if not updateable

Let's say i have the following data
var obj = {
test: 'somedata',
scores: [
{
"points":99,
"id":"x12"
},
{
"points":21,
"id":"x13"
}
],
sites: [
{
"exercises":21,
"sid":"s12"
},
{
"exercises":23,
"sid":"s11"
}
],
history: {
key: 'value',
commits: [
{
id: 1,
value: 'thank you'
}
 ]
}
}
Notice that scores and sites contain arrays with unique elements based on id in scores and based on sid in sites. I want a function that does the following magic:
//will **update** obj.test to 'newdata' and return {test:'newdata'}
magicUpdate(obj, {test:'newdata'})
//will **insert** obj.newkey with with value 'value' and return {newkey: 'value'}
magicUpdate(obj, {newkey: 'value'})
//will do nothing and return {}
magicUpdate(obj, {scores: []})
//will **update** scores[0] and return {scores:[{points:3, id: "x12"}]}, as id "x12" is already in the array at index 0
magicUpdate(obj, {scores:[{points:3, id: "x12"}])
//will **insert** {points:3, id: "x14"} into obj.scores and return {scores:[{points:3, id: "x14"}]}
magicUpdate(obj, {scores:[{points:3, id: "x14"}]})
//will **update** sites[0] and return {sites:[{exercises:22, sid: "s12"}]}, as id "s12" is already in the array at index 0
magicUpdate(obj, {sites:[{exercises:22, sid: "s12"}])
//will **insert** {exercises:10, sid: "s14"} into obj.sites and return {sites:[{exercises:10, sid: "s14"}]}
magicUpdate(obj, {sites:[{exercises:10, sid: "s14"}]})
//and also be recursive ...
//will **update** obj.history.commits[0]
magicUpdate(obj, {'history.commits': [{id:1, value: 'changed'}]});
I have seen .update doing the recursion, but only if one is passing the path which should be determined automatically. Then there is .merge which internally uses _.baseMerge and comes really close to what i need though I do not understand the signature of the function.
_.merge(
{scores:[{id: 12, points:10}, {id: 13, points:10}]},
{scores:[{id: 14, points:10}, {id: 15, points:10}]}
)
// returns {scores:[{id: 14, points:10}, {id: 15, points:10}]} not the fully merged array
Can someone point me to a good direction or has achieved similar things with lodash?
The magicUpdate function you mention in your post could be achieved using lodash functions indeed.
For this implementation, I've used mostly _ .get, _ .set and _ .unionWith though I'm sure it could have been achieved using some others:
// src will be mutated. For simplicity's sake, obj is an object with only one property that represents the changes to make to src
function magicUpdate(src, obj) {
var key = _.first(_.keys(obj)),
value = _.get(obj, key),
srcValue = _.get(src, key),
comparator = function(a, b) {
var idKey = _.isUndefined(a.id) ? 'sid' : 'id';
return a[idKey] === b[idKey];
}
if (_.isArray(srcValue)) {
value = _.unionWith(value, srcValue, comparator);
}
return _.set(src, key, value);
}
As you may have noticed looking at the code, the return type is the mutated object and not what you're asking. I wasn't really sure what you wanted as a return value.
Anyway, Lodash doesn't have a built-in object difference function so it'd be necessary to develop something like that in case you wanted the difference between the old object and the modified one (you'd also have to _ .clone the object first to have a copy and be able to compare).
The idea of the function I present is to try to get the key of obj (it's the key we want to modify in src) and check if it exists and is an array. If so, we just add the two arrays, updating those in src that have the same id in obj. Due to the fact that sites, scores and history had id and sid I had to add some more logic to the comparator of the _.unionWith function.
If key doesn't exist or isn't an array, we just set it in src.
Here you have the fiddle in case you want to play with it. Hope it helps.
UPDATE
My first solution was intended for one property updated at a time. However, it seems that is possible to update more than one at the same time.
One quick solution could be to iterate over the object with the updates and update one property at a time.
function updateProperty(src, obj) {
var key = _.first(_.keys(obj)),
value = _.get(obj, key),
srcValue = _.get(src, key),
comparator = function(a, b) {
var idKey = _.isUndefined(a.id) ? 'sid' : 'id';
return a[idKey] === b[idKey];
}
if (_.isArray(srcValue)) {
value = _.unionWith(value, srcValue, comparator);
}
return _.set(src, key, value);
}
function magicUpdate(obj, src) {
_.forEach(src, function(value, key) {
updateProperty(obj, _.pick(src, key));
});
return obj;
}
Fiddle
I wrote a solution which is recursive and quite performant. See this fiddle.
function mergeRecursive(obj1, obj2) {
if (obj1.constructor == Array) {
for (var i = 0; i < obj1.length; i++) {
if (obj1[i].id == obj2.id) {
obj1[i] = obj2;
return obj1;
}
}
obj1.push(obj2);
return obj1;
}
for (var p in obj2) {
// Property in destination object set; update its value.
if (obj2[p].constructor == Array) {
obj2[p].forEach(function(arrayElement) {
obj1[p] = MergeRecursive(obj1[p], arrayElement);
});
} else if (obj2[p].constructor == Object) {
obj1[p] = MergeRecursive(obj1[p], obj2[p]);
} else {
obj1[p] = obj2[p];
}
}
return obj1;
}

Get array of key-values from array of objects without knowing format of array of objects (Javascript)?

Imagine I'm given a reference to an array of similar objects, e.g. array will be the name of that array. Now I'm asked to create an array of all the values of some property that is found inside each object of that array, e.g. "user.id".
The problem is that I won't know the format of each object and where that property will reside/be nested.So "user.id" might reside in array[#].someKey (array[#].someKey["user.id"])or in array[#].someKey.someOtherKey (array[#].someKey.someOtherKey["user.id"])
Is there a function (jQuery, underscore..etc) that could create such an array ?
e.g. var arrayOfUserIds = returnArray(array, "user.id");
For example, imagine that the following is an example of such an array :
var array = [
{
"age": "13",
"type": "publish_action",
"tag": null,
"timestamp": 1398931707000,
"content": {
"action": "publish",
"user.id": "860",
"user.email": "alex#somemail.com",
"property.id": "2149",
"iteration_id": "15427",
"test_id": "6063",
"property.name" : "bloop"
}, {
....
}, {
....
}];
Based on the above, I could obviously do :
var arrayOfUserIds = [];
for (var i=0; i<array.length; i++)
{
arrayOfUserIds.push(array[i]["content"]["user.id"]);
}
But like I said, in my case I won't know about the format of the object so I can't create such a for-loop for example.
Any ideas will be much appreciated!
Thank you!
If I understand correctly, each object in someArray either contains a property user.id or an object that contains user.id...or, recursively, some object that contains someArray. You want to create an array containing only the user.id properties.
An easy way to do this would be to do a recursive examination of each object in the array until user.id is located:
// get `user.id` property from an object, or sub-object
// it is assumed that there will only be one such property;
// if there are more than one, only the first one will be returned
function getUserId(o){
if(o===null || o===undefined) return;
if(o['user.id']) return o['user.id'];
for(var p in o){
if(!o.hasOwnProperty(p)) continue;
if(typeof o[p] !== 'object') continue;
if(o[p] === null || o[p] === undefined) continue;
var id = getUserId(o[p]);
if(id) return id;
}
}
function getUserIds(arr){
return arr.map(function(e){
return getUserId(e);
});
}
If you want something a little more generic, you could write a "find" method that will find all instances of a named property in an object tree:
var find = (function(){
function find(matches, o, prop, checkPrototypeChain){
if(typeof o[prop] !== 'undefined') matches.push(o[prop]);
for(var p in o){
if(checkPrototypeChain || !o.hasOwnProperty(p)) continue;
if(typeof o[p] !== 'object') continue;
if(o[p] === null || o[p] === undefined) continue;
find(matches, o[p], prop, checkPrototypeChain);
}
}
return function(o, prop, checkPrototypeChain){
var matches = [];
find(matches, o, prop, checkPrototypeChain);
return matches;
}
})();
Then you could just map your array based on that:
var userIds = someArray.map(function(e){ return find(e, 'user.id'); });
Note that I'm glossing over properties that may be in the prototype chain, but in the find function, I added the ability to additionally search for properties in the prototype chain.
I went with the assumption that you're only working with primitives and object/array literals. In which case, the following method (using underscore) seems to do the trick.
var testSubject = {
mykey: 9,
firstArray: [
{something: 9, another: {x: 'hello', mykey: 'dude'}, mykey: 'whatever'},
{something: 9, another: {x: 'hello', mykey: 'dude2'}, mykey: 'whatever2'},
{
someArray: [
{seven: 7, mykey: 'another'},
{hasNo: 'mykey', atAll: 'mykey'}
]
}
],
anObject: {beef: 'jerky', mykey: 19}
};
function getValuesForKey(subject, searchKey) {
return _.reduce(subject, function(memo, value, key) {
if (_.isObject(value)) {
memo = memo.concat(getValuesForKey(value, searchKey));
} else if (key === searchKey) {
memo.push(value);
}
return memo;
}, []);
}
console.log(getValuesForKey(testSubject, 'mykey'));
// -> [9, "dude", "whatever", "dude2", "whatever2", "another", 19]
It only returns the list of values because they will all share the same key (i.e. the one specified). Additionally, I do believe any matching keys will be ignored if their values are not primitive (e.g. mykey: {…} or mykey: […] should be ignored). I hope it helps.

How to aggregate objects properties?

If I have an object like this (or similar):
sales = {
obs1:{
Sales1:{
Region:"North", Value: 200},
Sales2:{
Region:"South", Value:100}},
obs2:{
Sales1:{
Region:"North", Value: 50},
Sales2:{
Region:"South", Value:20}
}
}
How could I aggregate the sum of the property Value by Region? Answers could be in pure JavaScript or a library.
The end result should be something similar to this:
totals = {North: 250, South:120}
As others pointed out, there's no built-in JavaScript functions to do that (there are a few high-order functions like map, but not enough for the task). However, some libraries such as Underscore.js provide many utilities to simplify this kind of task.
var totals = _
.chain(sales) // Wraps up the object in an "underscore object",
// so methods can be chained
// First: "flatten" the sales
.map(function(v) {
return _
.chain(v)
.map(function(v2) {
return v2;
})
.value();
})
.flatten()
// Second: group the sales by region
.groupBy('Region')
// Third: sum the groups and create the object with the totals
.map(function(g, key) {
return {
type: key,
val: _(g).reduce(function(m, x) {
return m + x.Value;
}, 0)
};
})
.value(); // Unwraps the "underscore object" back to a plain JS object
Source: this answer at SOpt
This answer assumes the structure of your data is known - contrary to the other answers, which focus on generalizing the structure. Though the code above can be generalized itself, by removing the hardcoded Region and Value and varying the nesting level to something other than two and the aggregation function to something other than sum - as long as the leaves contain both a property you want to group by, and a value you want to aggregate.
function aggregate(object, toGroup, toAggregate, fn, val0) {
function deepFlatten(x) {
if ( x[toGroup] !== undefined ) // Leaf
return x;
return _.chain(x)
.map(function(v) { return deepFlatten(v); })
.flatten()
.value();
}
return _.chain(deepFlatten(object))
.groupBy(toGroup)
.map(function(g, key) {
return {
type: key,
val: _(g).reduce(function(m, x) {
return fn(m, x[toAggregate]);
}, val0 || 0)
};
})
.value();
}
It's called like this:
function add(a,b) { return a + b; }
var totals = aggregate(sales, "Region", "Value", add);
Another example (finds minimum value by region):
function min(a,b) { return a < b ? a : b; }
var mins = aggregate(sales, "Region", "Value", min, 999999);
Are you looking for something like this (updated based on #Barmar's suggestion):
var totals = {};
function ExtractSales(obj) {
if (obj.Region && obj.Value) {
if (totals[obj.Region]) {
totals[obj.Region] += obj.Value;
} else {
totals[obj.Region] = obj.Value;
}
} else {
for (var p in obj) {
ExtractSales(obj[p]);
}
}
}
ExtractSales(sales);
console.log(totals);
http://jsfiddle.net/punF8/3/
What this will do, for a given root object, is walk down it's properties and try and find something with a Region and a Value property. If it finds them, it populates an object with your totals.
With this approach, you don't need to know anything about the nesting of objects. The only thing you need to know is that the objects you are looking for have Region and Value properties.
This can be optimized further and include some error checking (hasOwnProperty, undefined, circular references, etc), but should give you a basic idea.
Here's a function that will sum and group all all the properties in an object (recursively) http://jsfiddle.net/tjX8p/2/ This is almost the same as #MattBurland, except that it's fully generalized, that is, you can use any property as what to group-by or sum.
/**
* #param {object} obj Arbitrarily nested object that must contain the
* given propName and groupPropName at some level
* #param {string} propName The property to be summed up
* #param {string} groupPropName The property to group by
* #param {object} This is used internally for recursion, feel free to pass
* in an object with existing totals, but a default one is provided
* #return {object} An object keyed by by the groupPropName where the value
* is the sum of all properties with the propName for that group
*/
function sumUp(obj, propName, groupPropName, totals) {
var totals = totals || {};
for (var prop in obj) {
if (prop === propName) {
if (!totals[obj[groupPropName]]) {
totals[obj[groupPropName]] = 0
}
totals[obj[groupPropName]] += obj[propName]
} else if (typeof obj[prop] == 'object'){
sumUp(obj[prop], propName, groupPropName, totals);
}
}
return totals;
}
This function will work with the data you posted, or with something like
var sales2 = {
a: {
obs1:{
Sales1:{
Region:"North", Value: 100, OtherValue: 12},
Sales2:{
Region:"South", Value:50, OtherValue: 15}}},
b: {
obs2:{
Sales1:{
Region:"North", Value: 50, OtherValue: 18},
Sales2:{
Region:"South", Value:100, OtherValue: 20}}
}
};
console.log (sumUp(sales2, 'Value', 'Region'));
// Object {North: 150, South: 150}
console.log (sumUp(sales2, 'OtherValue', 'Value'));
// Object {50: 33, 100: 32}
I've stayed away from error checking to keep to code clear.
Searching for querying options in JS and looking at #mgibsonbr answer, it seems that another good solution for problems like this would be using something like jFunk to query (even though jFunk is still a prototype) and Underscore to group and reduce.
totals= _.chain(jF("*[Region]", sales).get()).groupBy('Region').map(function(g, key) {
return {
type: key,
val: _(g).reduce(function(m, x) {
return m + x.Value;
}, 0)
};
})
.value();
There are a lot of ways to approach this scenario -- most of which have been addressed already. So I decided to go the extra mile here and create something that would both be a viable solution for the OP's question, and vague enough in its definition to be uses with any data object.
So here's what I was able to throw together...
aggPropByAssoc() or Aggregate Property By Association is used to gather certain data from an object, based of the data's property name, by an associated property key/value identifier. Currently, this function assumes that the associated property resides in the same object level as the property being requested.
The function does not make assumptions about on which level in the object, that the requested property can be found. As such, the function will recurse through the object until the property (and the associated property) have been found.
Syntax
aggPropByAssoc (obj, ops [, cb])
obj the object to parse
ops an object containing...
assocKey : the key name of the associated property
assocVal : a string or array of assocKey value
property : the property to aggregate
cb a callback function [optional]
Examples
Using the OP's example:
// I've removed the object definition for brevity.
var sales = { ... };
aggPropByAssoc( /* obj, ops, cb */
sales,
{
assocKey: 'Region',
assocVal: ['North', 'South'],
property: 'Value'
},
function (e) {
// As the function only returns the data found, and does not
// automatically manipulate it, we use the callback function
// to return the sum of each region, as requested.
return {
North: e.North.reduce(function (p, c) { return p + c}, 0),
South: e.South.reduce(function (p, c) { return p + c}, 0)
}
}
)
// var regionSums = aggPropByAssoc( ... );
// returns --> Object {North: 250, South: 120}
Source
function aggPropByAssoc(obj, ops, cb) {
if (typeof obj !== "object" || typeof ops !== "object") { return; }
if (!(ops.assocKey && ops.assocVal && ops.property)) { return; }
function recurseObj(robj) {
for (var k in robj) {
if (! (robj[k][ops.assocKey] && robj[k][ops.property])) { recurseObj(robj[k]); continue; }
if (robj[k][ops.assocKey] === ops.assocVal) { aggArr.push(robj[k][ops.property]); }
}
}
var assocVObj = ops.assocVal, aggArr = [], aggObj = {};
if (typeof ops.assocVal !== "object" ) {
recurseObj(obj), aggObj = aggArr;
} else {
for (var op in assocVObj) {
ops.assocVal = assocVObj[op];
recurseObj(obj);
aggObj[ops.assocVal] = aggArr, aggArr = [];
}
}
if (typeof cb === "function") { return cb(aggObj); }
return aggObj;
}
This problem can be solved by an aggregation. But in order to use aggregation we need to convert it from an object to an array first. This can be achieved by using Object.values(obj). Because the source object has two levels of nesting, we need to apply it twice and flatten the result:
intermediate = Object.values(sales)
.map(x => Object.values(x))
.flat()
This gives us
[
{
"Region": "North",
"Value": 200
},
{
"Region": "South",
"Value": 100
},
{
"Region": "North",
"Value": 50
},
{
"Region": "South",
"Value": 20
}
]
And now we can use aggregation
totals = intermediate.reduce((r,v) => {
r[v.Region] = (r[v.Region] || 0) + v.Value;
return r;
}, {});

Add new attribute (element) to JSON object using JavaScript

How do I add new attribute (element) to JSON object using JavaScript?
JSON stands for JavaScript Object Notation. A JSON object is really a string that has yet to be turned into the object it represents.
To add a property to an existing object in JS you could do the following.
object["property"] = value;
or
object.property = value;
If you provide some extra info like exactly what you need to do in context you might get a more tailored answer.
var jsonObj = {
members:
{
host: "hostName",
viewers:
{
user1: "value1",
user2: "value2",
user3: "value3"
}
}
}
var i;
for(i=4; i<=8; i++){
var newUser = "user" + i;
var newValue = "value" + i;
jsonObj.members.viewers[newUser] = newValue ;
}
console.log(jsonObj);
A JSON object is simply a javascript object, so with Javascript being a prototype based language, all you have to do is address it using the dot notation.
mything.NewField = 'foo';
With ECMAScript since 2015 you can use Spread Syntax ( …three dots):
let people = { id: 4 ,firstName: 'John'};
people = { ...people, secondName: 'Fogerty'};
It's allow you to add sub objects:
people = { ...people, city: { state: 'California' }};
the result would be:
{
"id": 4,
"firstName": "John",
"secondName": "Forget",
"city": {
"state": "California"
}
}
You also can merge objects:
var mergedObj = { ...obj1, ...obj2 };
thanks for this post. I want to add something that can be useful.
For IE, it is good to use
object["property"] = value;
syntax because some special words in IE can give you an error.
An example:
object.class = 'value';
this fails in IE, because "class" is a special word. I spent several hours with this.
You can also use Object.assign from ECMAScript 2015. It also allows you to add nested attributes at once. E.g.:
const myObject = {};
Object.assign(myObject, {
firstNewAttribute: {
nestedAttribute: 'woohoo!'
}
});
Ps: This will not override the existing object with the assigned attributes. Instead they'll be added. However if you assign a value to an existing attribute then it would be overridden.
extend: function(){
if(arguments.length === 0){ return; }
var x = arguments.length === 1 ? this : arguments[0];
var y;
for(var i = 1, len = arguments.length; i < len; i++) {
y = arguments[i];
for(var key in y){
if(!(y[key] instanceof Function)){
x[key] = y[key];
}
}
};
return x;
}
Extends multiple json objects (ignores functions):
extend({obj: 'hej'}, {obj2: 'helo'}, {obj3: {objinside: 'yes'}});
Will result in a single json object
You can also dynamically add attributes with variables directly in an object literal.
const amountAttribute = 'amount';
const foo = {
[amountAttribute]: 1
};
foo[amountAttribute + "__more"] = 2;
Results in:
{
amount: 1,
amount__more: 2
}
You can also add new json objects into your json, using the extend function,
var newJson = $.extend({}, {my:"json"}, {other:"json"});
// result -> {my: "json", other: "json"}
A very good option for the extend function is the recursive merge. Just add the true value as the first parameter (read the documentation for more options). Example,
var newJson = $.extend(true, {}, {
my:"json",
nestedJson: {a1:1, a2:2}
}, {
other:"json",
nestedJson: {b1:1, b2:2}
});
// result -> {my: "json", other: "json", nestedJson: {a1:1, a2:2, b1:1, b2:2}}
Uses $.extend() of jquery, like this:
token = {_token:window.Laravel.csrfToken};
data = {v1:'asdass',v2:'sdfsdf'}
dat = $.extend(token,data);
I hope you serve them.
Following worked for me for add a new field named 'id'.
Angular Slickgrid usually needs such id
addId() {
this.apiData.forEach((item, index) => {
item.id = index+1;
});

Categories

Resources