Deep Search JSON-Object - javascript

I'm currently having a problem with a deep search in a json object and even though i thought this issue must have been covered alot, I wasn't able to find anything that was really helpful so far (and I actually found alot, also this thread. Maybe I've been looking at code for too long today but it didn't really help me)
Basically what i want is pretty simple. I have a JSON-Object thats pretty deep filled with objects. All i want is a function that returns an array with all objects that contain a given Key-Value-Pair. I made this function to return the first found object which works just fine
deepSearch: function(Obj, Key, Value){
var returned = [];
var result = false;
var searchObj = function(_Obj, _Key, _Value){
if(_Obj[_Key]===_Value){
return _Obj;
} else {
return false;
}
}
result = searchObj(Obj, Key, Value);
$.each(Obj, function(key, value){
if(typeof(Obj[key]) === 'object' && Obj[key]!== null && !result)
result = customGeneralFunctions.objects.deepSearch(Obj[key], Key, Value);
if(result) return result;
});
return result;
}
Now I want to change it to return an array contianing all Objects with that pair. I've been trying for a while now and I think it wouldnt be a change too hard but I just can't wrap my head around it. Maybesomeone has an idea that helps me. Thanks in advance and
Greetings Chris

A safe deep object search?
Can't let this pass 3 answers with examples, all flawed. And all illustrate some classic Javascript coding got-ya's
null is an Object
UPDATE an answer has been changed.
As the code is no longer visible I will just leave the warning when iterating an object's properties and you use typeof to check if you have an object be careful to check for null as it is also of type "object"
getObject returns to early and fails to find additional objects nested inside objects that meet the condition. Though easily fixed by removing the return it will still throw a TypeError: Cannot read property 'find' of null if the object being searched contains an array with null in it.
for in the indiscriminate iterator
UPDATE an answer has been removed.
I have added the removed code as an example in the snippet below function deepSearch is fatally flawed and will more likely throw a RangeError: Maximum call stack size exceeded error then find the object you are looking for. eg deepSearch({ a:"a"},"id",3);. When using for in you should type check as it will iterate a string as well as an object's properties.
function deepSearch(object, key, value) {
var filtered = [];
for (var p in object)
if (p === key && object[p] === value) filtered.push(object);
else if (object[p]) filtered = filtered.concat(deepSearch(object[p], key, value));
return filtered;
}
Dont trust the callback.
Alex K search passed most tests (within reasonable scope of the question) but only if the code in the form of the comment // tip: here is a good idea to check for hasOwnProperty would have been included.
But that said the function has a flaw (and inefficiency) as it will call predicate on all properties of an object, and I can think of plenty of scenarios in which the function can return many references to the same object eg the reciprocal search for objects with property key NOT with value predicate = (key,val)=>{return key === "id" && val !== 3}.
The search should only add one entry per object thus we should test the object not the properties. We can never trust the callback to do what we expect.
And as it is the accepted answer I should point out that Array.concat should really not be used as it is in this situation. Using closure is much more efficient and allows you to not have to pass the current state to each recursion.
Circular reference.
The flaw to floor them all.
I am not to sure if it is relevant as the question does state that the data is from the form JSON and hence would be free of any circular reference (JSON can not reference).
But I will address the problem and several solutions.
A circular reference is simply an object referencing itself. For example.
var me = {};
me.me = me;
That will crash all the other answers if passed as an argument. Circular references are very common.
Some solutions.
First solution is to only accept data in the form of a JSON string and equally return the data as a JSON string (so balance is maintained and the universe does not explode). Thus eliminating any chance of a circular reference.
Track recursion depth and set a limit. Though this will stop a callstack overflow
it will not prevent the result being flawed as a shallow circular reference can create duplicate object references.
The quick down and dirty solution is a simple try catch around a JSON.stringify and throw TypeError("Object can not be searched"); for those on that side of the data bus..
The best solution is to decycle the object. Which in this case is very amenable to the actual algorithm we are using. For each unique object that is encountered we place it in an array. If we encounter an object that is in that array we ignore it and move on.
A possible solution.
Thus the general purpose solution, that is safe (I hope) and flexible. Though it is written for ES6 so legacy support will have to be provided in the form of babel or the like. Though it does come with a BUT!
// Log function
function log(data){console.log(data)}
// The test data
var a = {
a : "a",
one : {
two : {
find : "me",
data : "and my data in one.two"
},
twoA : {
four : 4,
find : "me",
data : "and my data in one.twoA"
}
},
two : {
one : {
one : 1,
find : "not me",
},
two : {
one : 1,
two : 1,
find : "me",
data : "and my data in two.two"
},
},
anArray : [
null,0,undefined,/./,new Date(),function(){return hi},
{
item : "one",
find : "Not me",
},{
item : "two",
find : "Not me",
extra : {
find : "me",
data : "I am a property of anArray item 1",
more : {
find : "me",
data : "hiding inside me"
},
}
},{
item : "three",
find : "me",
data : "and I am in an array"
},{
item : "four",
find : "me",
data : "and I am in an array"
},
],
three : {
one : {
one : 1,
},
two : {
one : 1,
two : 1,
},
three : {
one : 1,
two : {
one : {
find : "me",
data : "and my data in three.three.two.one"
}
}
}
},
}
// Add cyclic referance
a.extra = {
find : "me",
data : "I am cyclic in nature.",
}
a.extra.cycle = a.extra;
a.extraOne = {
test : [a],
self : a,
findme : a.extra,
};
if(! Object.allWith){
/* Non writeable enumerable configurable property of Object.prototype
as a function in the form
Object.allWith(predicate)
Arguments
predicate Function used to test the child property takes the argument
obj the current object to test
and will return true if the condition is meet
Return
An array of all objects that satisfy the predicate
Example
var test = {a : { key : 10, data: 100}, b : { key : 11, data: 100} };
var res = test.allWith((obj)=>obj.key === 10);
// res contains test.a
*/
Object.defineProperty(Object.prototype, 'allWith', {
writable : false,
enumerable : false,
configurable : false,
value : function (predicate) {
var uObjects = [];
var objects = [];
if (typeof predicate !== "function") {throw new TypeError("predicate is not a function")}
(function find (obj) {
var key;
if (predicate(obj) === true) {objects.push(obj)}
for (key of Object.keys(obj)) {
let o = obj[key];
if (o && typeof o === "object") {
if (! uObjects.find(obj => obj === o)) {
uObjects.push(o);
find(o);
}
}
}
} (this));
return objects;
}
});
}else{
console.warn("Warn!! Object.allWith already defined.");
}
var res = a.allWith(obj => obj.find === "me");
res.forEach((a,i)=>(log("Item : " + i + " ------------"),log(a)))
Why are you searching through unknown data structures?
It works for all the test cases I could come up with, but that is not at all the definitive test. I added it to the Object.prototype because you should not do that!!! nor use such a function or derivative thereof.
This is the first time I have written such a function, and the reason is that I have never had to write something like that before, I know what the data looks like and I dont have to create dangerous recursive iterators to find what is needed.. If you are writing code and you are not sure of the data you are using there is something wrong in the design of the whole project.

Hopefully this will help you to solve your task.
Lets use recursion to search deep into object.
Also lets make it more generic.
// search function takes object as a first param and
// a predicate Function as second predicate(key, value) => boolean
function search(obj, predicate) {
let result = [];
for(let p in obj) { // iterate on every property
// tip: here is a good idea to check for hasOwnProperty
if (typeof(obj[p]) == 'object') { // if its object - lets search inside it
result = result.concat(search(obj[p], predicate));
} else if (predicate(p, obj[p]))
result.push(
obj
); // check condition
}
return result;
}
Lets test it!
var obj = {
id: 1,
title: 'hello world',
child: {
id: 2,
title: 'foobar',
child: {
id: 3,
title: 'i should be in results array '
}
},
anotherInnerObj: {
id: 3,
title: 'i should be in results array too!'
}
};
var result = search(obj, function(key, value) { // im looking for this key value pair
return key === 'id' && value === 3;
});
Output:
result.forEach(r => console.log(r))
// Object {id: 3, title: "i should be in results array "}
// Object {id: 3, title: "i should be in results array too!"}

You've created a returned array. First, push the result of searchObj() into it. Then in your loop, if you get a result, concat() it to returned. Finally, return returned at the end of the function. That should do it...

You could use a simplified version and
check if object not truthy or object is not an object, then return
check if given key and value match, then add the actual object to the result set,
get the keys and iterate over the properties and call the function again.
At last, the array with the collected objects is returned.
function getObjects(object, key, value) {
function iter(o) {
if (!o || typeof o !== 'object') {
return;
}
if (o[key] === value){
result.push(o);
}
Object.keys(o).forEach(function (k) {
iter(o[k]);
});
}
var result = [];
iter(object);
return result;
}
var object = { id: 1, title: 'hello world', child: { id: null, title: 'foobar', child: { id: null, title: 'i should be in results array ' } }, foo: { id: null, title: 'i should be in results array too!' }, deep: [{ id: null, value: 'yo' }, { id: null, value: 'yo2' }] };
console.log(getObjects(object, 'id', null));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

Check if object already exists in object

I want to check if an object already exists in a given object by only having the object.
For instance:
const information = {
...
city: {
Streetname: ''
}
}
Now, I get the city object and want to check if it is already in the information object (without knowing the property name). The city could be n deep in the information object.
To get the property name of an object you can use Object.keys(). The first problem solved.
Now we need to iterate through the whole object including nested objects. This is the second problem.
And compare it to a query object. This is the third problem.
I assume that we have an object that only contains "simple" though nested objects with primitive values (I do not consider objects with functions or arrays)
// let's assume we have this object
const information = {
city: {
Streetname: 'streetname1'
},
house: {
color: "blue",
height: 100,
city: {
findMe: { Streetname: '' } // we want to get the path to this property 'findMe'
}
},
findMeToo: {
Streetname: '' // we also want to get the path to this proeprty 'findMeToo'
},
willNotFindMe: {
streetname: '' // case sensetive
}
}
// this is our object we want to use to find the property name with
const queryObject = {
Streetname : ''
}
If you use === to compare Objects you will always compare by reference. In our case, we are interested to compare the values. There is a rather extensive checking involved if you want to do it for more complex objects (read this SO comment for details), we will use a simplistic version:
// Note that this only evaluates to true if EVERYTHING is equal.
// This includes the order of the properties, since we are eventually comparing strings here.
JSON.stringify(obj1) === JSON.stringify(obj2)
Before we start to implement our property pathfinder I will introduce a simple function to check if a given value is an Object or a primitive value.
function isObject(obj) {
return obj === Object(obj); // if you pass a string it will create an object and compare it to a string and thus result to false
}
We use this function to know when to stop diving deeper since we reached a primitive value which does not contain any further objects. We loop through the whole object and dive deeper every time we find a nested object.
function findPropertyPath(obj, currentPropertyPath) {
const keys = isObject(obj) ? Object.keys(obj) : []; // if it is not an Object we want to assign an empty array or Object.keys() will implicitly cast a String to an array object
const previousPath = currentPropertyPath; // set to the parent node
keys.forEach(key => {
const currentObj = obj[key];
currentPropertyPath = `${previousPath}.${key}`;
if (JSON.stringify(currentObj) === JSON.stringify(queryObject)) console.log(currentPropertyPath); // this is what we are looking for
findPropertyPath(currentObj, currentPropertyPath); // since we are using recursion this is not suited for deeply nested objects
})
}
findPropertyPath(information, "information"); // call the function with the root key
This will find all "property paths" that contain an object that is equal to your query object (compared by value) using recursion.
information.house.city.findMe
information.findMeToo
const contains = (item, data) => item === data || Object.getOwnPropertyNames(data).some(prop => contains(item, data[prop]));
const information = {
city: {
Streetname: ''
}
}
console.log(contains(information.city, information));
console.log(contains({}, information));

Delete item from object by selector string

I am trying to delete an item from an object by passing a key to the method. For example I want to delete a1, and to do so I pass a.a1 to the method. It then should delete a1 from the object leaving the rest of the object alone.
This is the structure of the object:
this.record = {
id: '',
expiration: 0,
data: {
a: {
a1: 'Cat'
}
}
}
I then call this method:
delete(key) {
let path = key.split('.')
let data = path.reduce((obj, key) => typeof obj == 'object' ? obj[key] : null, this.record.data)
if(data) delete data
}
Like this:
let inst = new MyClass()
inst.delete('a.a1')
This however gives me the following error:
delete data;
^^^^
SyntaxError: Delete of an unqualified identifier in strict mode.
I assume that data is a reference still at this point, or is it not?
Maybe reduce isn't the right method to use here. How can I delete the item from the object?
Using your example, the value of data at the point where it is checked for truthiness is Cat, the value of the property you're trying to delete. At this point, data is just a regular variable that's referencing a string and it's no longer in the context of inst.
Here's a solution I managed to get to work using the one from your OP as the basis:
let path = key.split('.')
let owningObject = path.slice(0, path.length - 1)
.reduce((obj, key) => typeof obj == 'object' ? obj[key] : null, this.record.data)
if (owningObject) delete owningObject[path[path.length - 1]]
The main difference between this and what you had is that reduce operates on a slice of the path segments, which does not include the final identifier: This ends up with owningObject being a reference to the a object. The reduce is really just navigating along the path up until the penultimate segment, which itself is used as the property name that gets deleted.
For an invalid path, it bails out either because of the if (owningObject) or because using delete on an unknown property is a no-op anyway.
The solution I came up with which I am not super fond of but works, is looping over the items which will allow me to do long keys like this
a.a1
a.a1.a1-1
a.a1.a1-1.sub
The function then looks like this
let record = {
data: {
a: {
a1: 'Cat',
a2: {
val: 'Dog'
}
}
}
}
function remove(key) {
let path = key.split('.')
let obj = record.data
for (let i = 0; i < path.length; i++) {
if (i + 1 == path.length && obj && obj[path[i]]) delete obj[path[i]]
else if(obj && obj[path[i]]) obj = obj[path[i]]
else obj = null
}
}
// Removes `a.a1`
remove('a.a1')
console.log(JSON.stringify(record))
// Removes `a.a2.val`
remove('a.a2.val')
console.log(JSON.stringify(record))
// Removes nothing since the path is invalid
remove('a.a2.val.asdf.fsdf')
console.log(JSON.stringify(record))
You can delete keys using [] references.
var foo = {
a: 1,
b: 2
};
var selector = "a";
delete foo[selector];
console.log(foo);
I'm not sure if this helps you but it might help someone googling to this question.
Here's another method which is very similar to the OP's own solution but uses Array.prototype.forEach to iterate over the path parts. I came to this result independently in my attempt to wrap this up as elegantly as possible.
function TestRecord(id, data) {
let record = {
id : id,
data : data
};
function removeDataProperty(key) {
let parent = record.data;
let parts = key.split('.');
let l = parts.length - 1;
parts.forEach((p, i) => {
if (i < l && parent[p]) parent = parent[p];
else if (i == l && parent[p]) delete parent[p];
else throw new Error('invalid key');
});
}
return {
record : record,
remove : function(key) {
try {
removeDataProperty(key);
} catch (e) {
console.warn(`key ${key} not found`);
}
}
}
}
let test = new TestRecord('TESTA', {
a : { a1 : '1', a2 : '2' },
b : { c : { d : '3' } }
});
test.remove('a'); // root level properties are supported
test.remove('b.c.d'); // deep nested properties are supported
test.remove('a.b.x'); // early exit loop and warn that property not found
console.log(test.record.data);
The usage of throw in this example is for the purpose of breaking out of the loop early if any part of the path is invalid since forEach does not support the break statement.
By the way, there is evidence that forEach is slower than a simple for loop but if the dataset is small enough or the readability vs efficiency tradeoff is acceptable for your use case then this may be a good alternative.
https://hackernoon.com/javascript-performance-test-for-vs-for-each-vs-map-reduce-filter-find-32c1113f19d7
This may not be the most elegant solution but you could achieve the desired result very quickly and easily by using eval().
function TestRecord(id) {
let record = {
id : id,
data : {
a : {
a1 : 'z',
a2 : 'y'
}
}
};
return {
record : record,
remove : function (key) {
if (!key.match(/^(?!.*\.$)(?:[a-z][a-z\d]*\.?)+$/i)) {
console.warn('invalid path');
return;
} else {
let cmd = 'delete this.record.data.' + key;
eval(cmd);
}
}
};
}
let t = new TestRecord('TESTA');
t.remove('a.a1');
console.log(t.record.data);
I have included a regular expression from another answer that validates the user input against the namespace format to prevent abuse/misuse.
By the way, I also used the method name remove instead of delete since delete is a reserved keyword in javascript.
Also, before the anti-eval downvotes start pouring in. From: https://humanwhocodes.com/blog/2013/06/25/eval-isnt-evil-just-misunderstood/ :
...you shouldn’t be afraid to use it when you have a case where eval()
makes sense. Try not using it first, but don’t let anyone scare you
into thinking your code is more fragile or less secure when eval() is
used appropriately.
I'm not promoting eval as the best way to manipulate objects (obviously a well defined object with a good interface would be the proper solution) but for the specific use-case of deleting a nested key from an object by passing a namespaced string as input, I don't think any amount of looping or parsing would be more efficient or succinct.

javaScript: Parsing a stringified object with JSON.parse removes references

I am trying to deep-clone an object, say "a" with k = JSON.parse(JSON.stringify(a)). It is important that I use the stringify way, since I am trying to save the object into a file and then load from it.
I stumbled upon a problem with references on the cloned object which is illustrated below:
var obj={};
obj.importantProperty={s:2};
obj.c=obj.importantProperty;
obj.d=obj.importantProperty;
console.log( obj.c === obj.d ); // Returns true
var cloned = JSON.parse(JSON.stringify(obj));
console.log( cloned.c === cloned.d ); // Returns false
I need the references to be kept when using JSON.parse, in the above example they are not. In my project the object is much more complicated, but in the end it comes down to the example above.
Thanks in advance to anyone who helps me with this :)
The proper way to do something like this would be to store the common referenced object(s) separately and reference it by an ID.
For instance, you can hold your importantProperty objects in an array and use the index as the ID:
var importantProperties = [
{ s: 1 },
{ s: 2 },
{ s: 3 }
];
var obj = {};
obj.importantProperty = importantProperties[1];
obj.c = obj.importantProperty;
obj.d = obj.importantProperty;
Then when you stringify the object you replace the referenced object with its index:
var stringified = JSON.stringify(obj, function(key, value) {
if (key) {
return importantProperties.indexOf(value);
}
return value;
});
console.log(stringified);
// prints {"importantProperty":1,"c":1,"d":1}
And then when you parse you simply reverse the process to revive the references:
var parsed = JSON.parse(stringified, function(key, value) {
if (key) {
return importantProperties[value];
}
return value;
});
console.log(parsed.c === parsed.d && parsed.d === parsed.importantProperty);
// prints true
Now, the example above works for your example code under the assumption that all properties in obj is an object from the importantProperties array. If that's not the case and it's only certain properties that is an importantProperties object, you need to check for that when replacing/reviving.
Assuming only the "importantProperty", "c" and "d" properties are such objects:
if (['importantProperty', 'c', 'd'].includes(key)) instead of just if (key)
If this isn't good enough and you don't want the property name to have anything to do with whether or not the value is an importantProperties object, you'll need to indicate this in the value together with the identifier. Here's an example of how this can be done:
// Replacing
JSON.stringify(obj, function(k, value) {
if (importantProperties.includes(value)) {
return 'ImportantProperty['
+ importantProperties.indexOf(value)
+ ']';
}
return value;
});
// Reviving
JSON.parse(stringified, function(k, value) {
if (/^ImportantProperty\[\d+\]$/.test(value)) {
var index = Number( value.match(/\d+/)[0] );
return importantProperties[index];
}
return value;
});
It is impossible to achieve your desired result using JSON because JSON format can contain only a limited ammount of data types (http://json.org/) and when you stringify an object to JSON some information gets lost.
Probably there is some other kind of serialization technique, but I would recommend you to look for another approach to store data.

In Javascript what would be the simplest function to get the namespace of a multi depth object [duplicate]

This question already has answers here:
Javascript reflection: Get nested objects path
(3 answers)
Closed 6 years ago.
Sorry if the title is a bit miss leading, I'll give an example of what I wish to do.
If I have a object say:
{
testable: {
some: {
id: 10
},
another: {
some: {
id: 20
}
},
an : {
arrayExample: ['test']
}
}
}
and I want an easy way to get an array of all the keys for non object values like so:
['testable.some.id','testable.another.some.id', 'testable.an.arrayExample.0']
How would I do it? I assume I'd need a recursive function to build such an array as I'd have to do a for loop through each object and grab the key and add it to the previous namespace before pushing it into an array.
Also is this a bit to costly as a function? Especially if I'm going to use the array afterwards to grab the values.
You could use an iterative an recursive approach to get all keys of the object and array.
For all nested keys, you nedt to iterate over all items. There is no short circuit or other way around to skipt that task.
If you have some fancy names like 'item.0' you may get the wrong path to it.
Edit
Added a function getValue for getting a value from a path to a property/item. This works for falsy values as well.
function getPath(object) {
function iter(o, p) {
if (Array.isArray(o) ){
o.forEach(function (a, i) {
iter(a, p.concat(i));
});
return;
}
if (typeof o === 'object') {
Object.keys(o).forEach(function (k) {
iter(o[k], p.concat(k));
});
return;
}
path.push(p.join('.'));
}
var path = [];
iter(object, []);
return path;
}
function getValue(object, path) {
return path.split('.').reduce(function (r, a) {
return (Array.isArray(r) || typeof r === 'object') ? r[a] : undefined;
}, object);
}
var obj = { testable: { some: { id: 10, number: 0 }, another: { some: { id: 20 } }, an: { arrayExample: ['test'] } } },
path = getPath(obj);
document.write('<pre>' + JSON.stringify(getPath(obj), 0, 4) + '</pre>');
path.forEach(function (a) {
document.write(getValue(obj, a) + '<br>');
});
//console.log(getPath(obj));
//path.forEach(function (a) {
// console.log(getValue(obj, a));
//});
That would simply be done by the invention of a "reusable" Object method. I call it Object.prototype.getNestedValue() It dynamically fetches the value resides at deeply nested properties. You have to provide the properties as arguments in an ordered fashion. So for example if you would like to get the value of myObj.testable.some.id you should invoke the function as var myId = myObj.getNestedValue("testable","some","id") // returns 10 So lets see how it works.
Object.prototype.getNestedValue = function(...a) {
return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};
var myObj = {
testable: {
some: {
id: 10
},
another: {
some: {
id: 20
}
},
an : {
arrayExample: ['test']
}
}
},
myId = myObj.getNestedValue("testable","some","id");
console.log(myId);
//or if you receive the properties to query in an array can also invoke it like
myId = myObj.getNestedValue(...["testable","some","id"]);
console.log(myId);
So far so good. Let's come to your problem. Once we have this nice tool in our hand what you want to do becomes a breeze. This time i insert your each query in an array and the spread operator is our friend. Check this out.
Object.prototype.getNestedValue = function(...a) {
return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};
var myObj = {
testable: {
some: {
id: 10
},
another: {
some: {
id: 20
}
},
an : {
arrayExample: ['test']
}
}
},
idCollection = [["testable","some","id"],["testable","another","some","id"],["testable","an","arrayExample",0]],
ids = idCollection.reduce((p,c) => p.concat(myObj.getNestedValue(...c)),[]);
console.log(ids);
Of course the the argument we use for querying can be dynamic like
var a = "testable",
b = "another",
c = "some",
d = "id",
myId = myObj.getNestedValue(a,b,c,d);
and you can reuse this Object method everywhere including arrays since in JS arrays are objects and the have perfect access to the Object.prototype. All you need is to pass the index of the array instead of the property of an object.

More elegant way to get nested object properties by name?

I have this working code, which retrieves the names of object properties from a JS object which (unfortunately!) is out of my scope. So I cannot change how this object is built. But I want to (and do) extract the names of the properties, that are marked as true, as an array, to be able to handle this object easier.
Object:
{
group1: {
foo: true,
itemFoo: "Name of foo", // This is what I want, because foo is true
bar: false,
itemBar: "Name of bar", // I dont want this, bar is false
// ...
},
group2: {
baz: true,
itemBaz: "Name of baz", // I want this too
// ...
},
uselessProp1: "not an object",
// ...
}
Working Code:
var items = [];
for (var m in obj) {
if (typeof obj[m] == 'object') {
for (var n in obj[m]) {
if (obj[m][n] === true) {
items.push(obj[m]['item' + (n.charAt(0).toUpperCase() + n.slice(1))]);
}
}
}
}
My question is: does someone know a more elegant way of achieving this traversal with underscore.js or plain node.js or any other library? I did experiments with _.filter, but did not come up with a solution.
Something like this?
var result = [];
_.chain(obj).filter(_.isObject).each(function(t) {
_(t).each(function(val, key) {
if(val === true)
result.push(t['item' + key.charAt(0).toUpperCase() + key.substr(1)])
})
})
This is the solution I've come so far:
http://jsfiddle.net/kradmiy/28NZP/
var process = function (obj) {
var items = [];
var objectProperties = _(obj).each(function (rootProperty) {
// exit from function in case if property is not an object
if (!_(rootProperty).isObject()) return;
_(rootProperty).each(function (value, key) {
// proceed only if property is exactly true
if (value !== true) return;
var searchedKey = 'item' + (key.charAt(0).toUpperCase() + key.slice(1));
// check that parent has this property...
if (rootProperty.hasOwnProperty(searchedKey)) {
// ...and push that to array
items.push(rootProperty[searchedKey]);
}
});
});
return items;
};
I would like to point out something :
Micha’s Golden Rule
Micha Gorelick, a data scientist in NYC, coined the following rule:
Do not store data in the keys of a JSON blob.
Your JSON should use :
{//group1
groupname:"group1",
items :[
{//item1
itemcheck:true,
itemname:'itemBar'
},
...
]
},
...
If you store itemname in key. You will have problem when traversing the JSON, because your 'itemFoo' would be using 'foo'(indirectly) to get its value. Your data structure, is the problem here. Searching your JSON is tricky. Once you follow the rule, your code will be elegant automatically.

Categories