Search a value inside complex object - javascript

I have a complex object with nested values. Values could be strings, arrays, arrays of objects or null objects. Something like this:
{
foo:'a',
bar:'b',
otherthings : [{yep:'0',yuk:'yoyo0'},{yep:'1',yuk:'yoyo1'}],
foobar : 'yup',
value: null
}
What is the fastest way to check if a value (e.g. yoyo1) exists somewhere in the object? Is there a Javascript built-in function?

Some short iteration:
var data = {
foo: 'a',
bar: 'b',
otherthings: [{ yep: '0', yuk: 'yoyo0' }, { yep: '1', yuk: 'yoyo1' }],
foobar: 'yup',
value: null
};
function findInObject(o, f) {
return Object.keys(o).some(function (a) {
if (Array.isArray(o[a]) || typeof o[a] === 'object' && o[a] !== null) {
return findInObject(o[a], f);
}
return o[a] === f;
});
}
document.write(findInObject(data, 'yoyo1') + '<br>');
document.write(findInObject(data, 'yoyo2') + '<br>');
document.write(findInObject(data, null) + '<br>');

Regex Solution
var myObject = {
foo: 'a',
bar: 'b',
otherthings: [{
yep: '0',
yuk: 'yoyo0'
}, {
yep: '1',
yuk: 'yoyo1'
}],
foobar: 'yup',
value: null
};
var myStringObject = JSON.stringify(myObject);
var matches = myStringObject.match('yep'); //replace yep with your search query
demo

var jsonStr = JSON.stringify(myJSON);
return jsonStr.indexOf(':"yolo1"') !== -1;
This only works if you want to know if it exists (not where it is). It may also not be the most performant.

Another solution:
function findInObject(obj, comparedObj){
return Object.keys(obj).some(function(key){
var value = obj[key];
if (!value) {
return false;
}
if (typeof value === "string") {
return value === comparedObj;
}
if (value instanceof Array) {
return value.some(function(e){
return (typeof value === "string" && e === comparedObj) ||
findInObject(e, comparedObj);
});
}
return findInObject(value, comparedObj);
});
}

Related

Searching deeply nested object array for a single key

I am trying to search through a deeply nested array and find if a key exists anywhere inside. I have written up a piece of code which does the traversal, but because it is not recursive (only self calling) it cannot return whether or not it has found anything. It just returns undefined since it reaches the end of the function on one of passes.
I was wondering if there was a way I could do this which would allow me to return true on the first occurrence of a specific key.
Here is a JS bin of what I have been working with so far:
https://jsbin.com/qaxuwajuso/edit?js,console
And here is a direct paste of the code from the above example:
function traverse(item, key) {
if (typeof item === 'object' && !Array.isArray(item) && item !== null) {
// Object
for (let itemKey in item) {
if (itemKey === key) {
// Is it possible to return true and break out of the function here?
console.log('found the key: ' + itemKey + ' With value: ' + item[itemKey]);
}
traverse(item[itemKey], key);
}
} else if (Array.isArray(item)) {
// Array
for (let i = 0; i < item.length; ++i) {
traverse(item[i], key);
}
}
}
Any help would be greatly appreciated. Thank you for your time!
Sure you just need to return a flag of some kind to trigger the loops to stop
/*
* I am trying to search the following json array for any occurance of the key "statePath".
* In a perfect world I would be able to find the first occurance, and return true from the
* function.
*
* The following data is not real, I was just trying to write as much nested stuff as possible
* to test that it traverses as far as needed.
*/
const data = [
{
id: '2144d998-4c33-4b03-93d2-f6c675b24508',
element: 'div',
props: {
className: 'testing',
name: [
{
first: 'John',
last: {
statePath: 'lastName',
anArray: [
{
anObject: {
anotherArray: [
{
doesItWork: {
statePath: 'hello',
},
},
],
},
},
],
},
},
{
first: 'Jane',
last: {
statePath: 'lastName',
},
},
],
},
children: 'hi',
},
];
function traverse(item, key) {
if (typeof item === 'object' && !Array.isArray(item) && item !== null) {
// Object
for (let itemKey in item) {
if (itemKey === key) {
console.log('found the key: ' + itemKey + ' With value: ' + item[itemKey]);
// not sure what you want the end "return" of the func to be, I'm returning the value. You could return true here instead, you could return a reference to the parent object, lots of possibilities
return item[itemKey];
}
var found = traverse(item[itemKey], key);
if (found !== undefined) return found;
// otherwise keep looking
}
} else if (Array.isArray(item)) {
// Array
for (let i = 0; i < item.length; ++i) {
var found = traverse(item[i], key);
if (found !== undefined) return found;
}
}
}
var value = traverse(data, 'statePath');
console.log("value is " + value);
You could use for...in and store result in one var and then check that var before you call function again and break loop if value is found.
const data = [{"id":"2144d998-4c33-4b03-93d2-f6c675b24508","statePath":"div","props":{"className":"testing","name":[{"first":"John","last":{"statePath":"lastName","anArray":[{"anObject":{"anotherArray":[{"doesItWork":{"statePath":"hello"}}]}}]}},{"first":"Jane","last":{"statePath":"lastName"}}]},"children":"hi"}]
function traverse(item, key) {
let result = false;
for (var i in item) {
if (i == key) {
result = true;
break;
}
if (typeof item[i] == 'object' && !result) {
result = traverse(item[i], key)
}
}
return result
}
console.log(traverse(data, 'statePath'))
My variant:
const data = [{id: '2144d998-4c33-4b03-93d2-f6c675b24508', element: 'div', props: {className: 'testing', name: [{first: 'John', last: {statePath3: 'lastName', anArray: [{anObject: {anotherArray: [{doesItWork: {statePath1: 'hello',},},],},},],},}, {first: 'Jane', last: {statePath: 'lastName',},},],}, children: 'hi',},];
function traverse(data, find) {
for (let k in data) {
let deepHaveKey = typeof data[k] === 'object' && traverse(data[k], find)
if (find === k || deepHaveKey)
return true
}
return false
}
console.log(traverse(data, 'statePath')); // true
console.log(traverse(data, 'state')); // false

Determining if all attributes on a javascript object are null or an empty string

What is the most elegant way to determine if all attributes in a javascript object are either null or the empty string? It should work for an arbitrary number of attributes.
{'a':null, 'b':''} //should return true for this object
{'a':1, 'b':''} //should return false for this object
{'a':0, 'b':1} //should return false
{'a':'', 'b':''} //should return true
Check all values with Object.values. It returns an array with the values, which you can check with Array.prototype.every or Array.prototype.some:
const isEmpty = Object.values(object).every(x => x === null || x === '');
const isEmpty = !Object.values(object).some(x => x !== null && x !== '');
Create a function to loop and check:
function checkProperties(obj) {
for (var key in obj) {
if (obj[key] !== null && obj[key] != "")
return false;
}
return true;
}
var obj = {
x: null,
y: "",
z: 1
}
checkProperties(obj) //returns false
Here's my version, specifically checking for null and empty strings (would be easier to just check for falsy)
function isEmptyObject(o) {
return Object.keys(o).every(function(x) {
return o[x]===''||o[x]===null; // or just "return o[x];" for falsy values
});
}
let obj = { x: null, y: "hello", z: 1 };
let obj1 = { x: null, y: "", z: 0 };
!Object.values(obj).some(v => v);
// false
!Object.values(obj1).some(v => v);
// true
Using Array.some() and check if the values are not null and not empty is more efficient than using Array.every and check it the other way around.
const isEmpty = !Object.values(object).some(x => (x !== null && x !== ''));
This answer should just make the excellent comment of user abd995 more visible.
Quick and simple solution:
Object.values(object).every(value => !!value);
You can use the Array.reduce prototype on your object's keys.
Assuming that the object is structured as follows:
var obj = {
x: null,
y: "",
z: 1
}
you can use the following instruction to discover if all of it's properties are unset or set to empty string using just one line:
Object.keys(obj).reduce((res, k) => res && !(!!obj[k] || obj[k] === false || !isNaN(parseInt(obj[k]))), true) // returns false
If you want to discover if all of it's properties are set instead you have to remove the negation before the conditions and set the initial result value to true only if the object has keys:
Object.keys(obj).reduce((res, k) => res && (!!obj[k] || obj[k] === false || !isNaN(parseInt(obj[k]))), Object.keys(obj).length > 0) // returns false as well
Based on adeneo's answer, I created a single line condition. Hope it will be helpful to someone.
var test = {
"email": "test#test.com",
"phone": "1234567890",
"name": "Test",
"mobile": "9876543210",
"address": {
"street": "",
"city": "",
"state": "",
"country": "",
"postalcode": "r"
},
"website": "www.test.com"
};
if (Object.keys(test.address).every(function(x) { return test.address[x]===''||test.address[x]===null;}) === false) {
console.log('has something');
} else {
console.log('nothing');
}
You can test it https://jsfiddle.net/4uyue8tk/2/
Just complementing the past answers: they'll work if your object doesn't contain arrays or objects. If it does, you'll need to do a 'deep check'.
So I came up with this solution. It'll evaluate the object as empty if all its values (and values inside values) are undefined, {} or [].
function deepCheckEmptyObject(obj) {
return Object.values(obj).every( value => {
if (value === undefined) return true;
else if ((value instanceof Array || value instanceof Object) && _.isEmpty(value) ) return true;
else if (value instanceof Array && !_.isEmpty(value)) return deepCheckEmptyArray(value);
else if (value instanceof Object && !_.isEmpty(value)) return deepCheckEmptyObject(value);
else return false;
});
}
function deepCheckEmptyArray(array) {
return array.every( value => {
if (value === undefined) return true;
else if ((value instanceof Array || value instanceof Object) && _.isEmpty(value)) return true;
else if (value instanceof Array && !_.isEmpty(value)) return deepCheckEmptyArray(value);
else if (value instanceof Object && !_.isEmpty(value)) return deepCheckEmptyObject(value);
else return false;
});
}
Note it uses Lodash's .isEmpty() to do the heavy work after we 'isolated' a value. Here, Lodash is imported as '_'.
Hope it helps!
Also if you are searching for only values are empty within the object,
Object.values({ key: 0, key2: null, key3: undefined, key4: '' }).some(e => Boolean(e))
// false
Object.values({ key: 0, key2: null, key3: undefined, key4: "hello" }).some(e => Boolean(e))
// true
Object.values({ key: 1, key2: "hello" }).some(e => Boolean(e))
// true
Based on tymeJv's answer =)
function checkProperties(obj) {
var state = true;
for (var key in obj) {
if ( !( obj[key] === null || obj[key] === "" ) ) {
state = false;
break;
}
}
return state;
}
var obj = {
x: null,
y: "",
z: 1
}
checkProperties(obj) //returns false
Hope it helps =)
This will give you all the keys from the object which is empty, undefined and null
Object.keys(obj).filter((k)=> {
if (obj[k] === "" || obj[k]===undefined || obj[k]===null) {
return k;
}
});
Building on top of other answers I would use lodash to check isEmpty on the object, as well as its properties.
const isEmpty = (object) => return _.isEmpty(object) || !Object.values(object).some(x => !_.isEmpty(x))
This skip the function attribute
function checkIsNull(obj){
let isNull=true;
for(let key in obj){
if (obj[key] && typeof obj[key] !== 'function') {
isNull = false;
}
}
return isNull;
}
var objectWithFunctionEmpty={
"name":undefined,
"surname":null,
"fun": function (){ alert('ciao'); }
}
var objectWithFunctionFull={
"name":undefined,
"surname":"bla bla",
"fun": function (){ alert('ciao'); }
}
checkIsNull(objectWithFunctionEmpty); //true
checkIsNull(objectWithFunctionFull); //false
This works with me perfectly:
checkProperties(obj) {
let arr = [];
for (let key in obj) {
arr.push(obj[key] !== undefined && obj[key] !== null && obj[key] !== "");
}
return arr.includes(false);
}
This will return true or false if there is at-least one value is empty or something like that.
You can use Object.values() method to get all the object's values (as an array of object's values) and then check if this array of values contains null or "" values, with the help of _.includes method prvided by lodash library.
const checkObjectProperties = obj => {
const objValues = Object.keys(obj);
if (_.includes(objValues, "") || _.includes(objValues, null)) {
return false;
} else {
return true
}
const incorrectObjProps = { one: null, two: "", three: 78 }
const correctObjProps = { one: "some string" }
checkObjectProperties(incorrectObjProps) // return false
checkObjectProperties(correctObjProps) // return true
}
I'll add my two sense:
Object.values(object).every(value => Boolean(value));
Solution:
function checkValues(obj) {
var objValues = Object.values(obj);
if (objValues.length < 1) return false;
return objValues.every((value) => {
if (value === null) return true;
if (typeof(value) == 'string')
if(!(value || false))
return true;
return false;
});
}
// OR
Object.values( obj ).every(
value => value === null || (typeof(value) == 'string' && !(value || false))
);
Testing:
checkValues({ a: null, b: '' });
// OR
Object.values({ a: null, b: '' }).every(
value => value === null || (typeof(value) == 'string' && !(value || false))
);
// Output: true
checkValues({ a: '', b: '' });
// OR
Object.values({ a: '', b: '' }).every(
value => value === null || (typeof(value) == 'string' && !(value || false))
);
// Output: true
checkValues({ a: 0, b: '' });
// OR
Object.values({ a: 0, b: '' }).every(
value => value === null || (typeof(value) == 'string' && !(value || false))
)
// Output: false
checkValues({ a: 0, b: 1 });
// OR
Object.values({ a: 0, b: 1 }).every(
value => value === null || (typeof(value) == 'string' && !(value || false))
)
// Output: false
checkValues({ a: 1, b: '' });
// OR
Object.values({ a: 1, b: '' }).every(
value => value === null || (typeof(value) == 'string' && !(value || false))
)
// Output: false
How about this?
!Object.values(yourObject).join('')

How to get the path from javascript object from key and value

I have a javascript object width depth.
I need to know the exact path from this key within the object ex: "obj1.obj2.data1"
I already know the key is data1, the value is 123.
My javascript object look like this
{
obj1: {
obj2: {
data1: 213,
data2: "1231",
obj3: {
data: "milf"
}
}
},
obj4: {
description: "toto"
}
}
How could I achieve that ?
here is a jsfiddle : http://jsfiddle.net/3hvav8xf/8/
I am trying to implement getPath.
I think recursive function can help to you (Updated version, to check value)
function path(c, name, v, currentPath, t){
var currentPath = currentPath || "root";
for(var i in c){
if(i == name && c[i] == v){
t = currentPath;
}
else if(typeof c[i] == "object"){
return path(c[i], name, v, currentPath + "." + i);
}
}
return t + "." + name;
};
console.log(path({1: 2, s: 5, 2: {3: {2: {s: 1, p: 2}}}}, "s", 1));
The following finds the path in any level of nested objects. Also with arrays.
It returns all the paths found, which is something you want if you have keys with the same name.
I like this approach because it works with lodash methods get and set out-of-the-box.
function findPathsToKey(options) {
let results = [];
(function findKey({
key,
obj,
pathToKey,
}) {
const oldPath = `${pathToKey ? pathToKey + "." : ""}`;
if (obj.hasOwnProperty(key)) {
results.push(`${oldPath}${key}`);
return;
}
if (obj !== null && typeof obj === "object" && !Array.isArray(obj)) {
for (const k in obj) {
if (obj.hasOwnProperty(k)) {
if (Array.isArray(obj[k])) {
for (let j = 0; j < obj[k].length; j++) {
findKey({
obj: obj[k][j],
key,
pathToKey: `${oldPath}${k}[${j}]`,
});
}
}
if (obj[k] !== null && typeof obj[k] === "object") {
findKey({
obj: obj[k],
key,
pathToKey: `${oldPath}${k}`,
});
}
}
}
}
})(options);
return results;
}
findPathsToKey({ obj: objWithDuplicates, key: "d" })
// ["parentKey.arr[0].c.d", "parentKey.arr[1].c.d", "parentKey.arr[2].c.d"]
Try it here - https://jsfiddle.net/spuhb8v7/1/
If you want the result to be a single key (first encountered), you can change the results to be a string and if defined, then return the function with it.
I ended up with the following function, that works with nested objects/arrays :
function findPath (obj, name, val, currentPath) {
currentPath = currentPath || ''
let matchingPath
if (!obj || typeof obj !== 'object') return
if (obj[name] === val) return `${currentPath}['${name}']`
for (const key of Object.keys(obj)) {
if (key === name && obj[key] === val) {
matchingPath = currentPath
} else {
matchingPath = findPath(obj[key], name, val, `${currentPath}['${key}']`)
}
if (matchingPath) break
}
return matchingPath
}
const treeData = [{
id: 1,
children: [{
id: 2
}]
}, {
id: 3,
children: [{
id: 4,
children: [{
id: 5
}]
}]
}]
console.log(findPath (treeData, 'id', 5))
Here you go!
function getPath(obj, value, path) {
if(typeof obj !== 'object') {
return;
}
for(var key in obj) {
if(obj.hasOwnProperty(key)) {
console.log(key);
var t = path;
var v = obj[key];
if(!path) {
path = key;
}
else {
path = path + '.' + key;
}
if(v === value) {
return path;
}
else if(typeof v !== 'object'){
path = t;
}
var res = getPath(v, value, path);
if(res) {
return res;
}
}
}
}
getPath(yourObject, valueYouWantToFindPath);
Rerutns path if found, else returns undefined.
I have only tested it with objects & comparison is very strict(ie: used ===).
Update:
Updated version that takes key as an argument.
function getPath(obj, key, value, path) {
if(typeof obj !== 'object') {
return;
}
for(var k in obj) {
if(obj.hasOwnProperty(k)) {
console.log(k);
var t = path;
var v = obj[k];
if(!path) {
path = k;
}
else {
path = path + '.' + k;
}
if(v === value) {
if(key === k) {
return path;
}
else {
path = t;
}
}
else if(typeof v !== 'object'){
path = t;
}
var res = getPath(v, key, value, path);
if(res) {
return res;
}
}
}
}
getPath(yourObject, key, valueYouWantToFindPath);
JSON Object can be handled in JavaScript as associative array.
So You can cycle through and store indexes of "parents" in some variables.
Assume the whole object to be stored in variable called obj.
for( var p1 in obj )
{
for( var p2 in obj[ p1 ] )
{
for( var p3 in obj[ p1 ][ p2 ] )
{
// obj[ p1 ][ p2 ][ p3 ] is current node
// so for Your example it is obj.obj1.obj2.data1
}
}
}
Hope answer was helpful.
I would do this job as follows;
Object.prototype.paths = function(root = [], result = {}) {
var ok = Object.keys(this);
return ok.reduce((res,key) => { var path = root.concat(key);
typeof this[key] === "object" &&
this[key] !== null ? this[key].paths(path,res)
: res[this[key]] == 0 || res[this[key]] ? res[this[key]].push(path)
: res[this[key]] = [path];
return res;
},result);
};
var myObj = {
obj1: {
obj2: {
data1: 213,
data2: "1231",
obj3: {
data: "milf"
}
}
},
obj4: {
description: "toto",
cougars: "Jodi",
category: "milf"
}
},
value = "milf",
milfPath = myObj.paths()[value]; // the value can be set dynamically and if exists it's path will be listed.
console.log(milfPath);
A few words of warning: We should be cautious when playing with the Object prototype. Our modification should have the descriptor enumerable = false or it will list in the for in loops and for instance jQuery will not work. (this is how silly jQuery is, since apparently they are not making a hasOwnProperty check in their for in loops) Some good reads are here and here So we have to add this Object method with Object.defineProperty() to make it enumerable = false;. But for the sake of simplicity and to stay in the scope of the question i haven't included that part in the code.
Here is a pretty short, and relatively easy to understand function I wrote for retrieving the JSON Path for every property/field on an Object (no matter how deeply nested, or not).
The getPaths(object) function just takes the Object you'd like the JSON Paths for and returns an array of paths. OR, if you would like the initial object to be denoted with a symbol that is different from the standard JSON Path symbol, $, you can call getPaths(object, path), and each JSON Path will begin with the specified path.
For Example: getPaths({prop: "string"}, 'obj'); would return the following JSON Path: obj.prop, rather than $.prop.
See below for a more detailed, in depth example of what getPaths returns, and how it is used.
object = {
"firstName": "John",
"lastName": "doe",
"age": 26,
"fakeData": true,
"address": {
"streetAddress": "fake street",
"city": "fake city",
"postalCode": "12345"
},
"phoneNumbers": [{
"type": "iPhone",
"number": "0123-4567-8888"
}, {
"type": "home",
"number": "0123-4567-8910"
}]
};
function getPaths(object, path = "$") {
return Object.entries(object).flatMap(function(o, i) {
if (typeof o[1] === "object" && !o[1].length) {
return `${getPaths(o[1], path + '.' + o[0])}`.split(',');
} else if (typeof o[1] === "object" && o[1].length) {
return Object.entries(o[1]).flatMap((no, i) => getPaths(no[1], `${path}.${o[0]}[${i}]`));
} else {
return `${path}.${o[0]}`;
}
});
}
console.log(`%o`, getPaths(object));
I really liked Roland Jegorov's answer, but I had a very complex object that I needed to search through and that answer could not account for it.
If you were in a situation like mine you may want to first make sure you have no circular references (or else you'll run into an infinite search). There are a few ways to do this, but I was having to stringify my object to copy it into other windows, so I ended up using this circular replacer: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value
(Update here - I made a small change to the getCircularReplacer function from MDN so it no longer leaves out function references since that is what I was looking for!)
(Update 3 - I also wanted to check on methods of any instances of classes, but I was returning just 'function' too early, so I have adjusted it to include instance methods. I think it finally works as I intended!)
const getCircularReplacer = () => {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === "function") {
if (value?.prototype) {
if (seen.has(value.prototype)) {
return;
}
seen.add(value.prototype)
return value.prototype
}
return "function";
}
if (typeof value === "object" && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
};
const nonCyclicObject = JSON.parse(JSON.stringify(myComplexObject, getCircularReplacer()));
Then I used this modified version of Roland's answer:
(Update 2: I had to make sure not to return after the key was found as it would always simply return after only calling the function once if the first level of the object had that key)
function findPathsToKey(options) {
let count = 0;
let results = [];
(function findKey({
key,
obj,
pathToKey,
}) {
count += 1;
if (obj === null) return;
const oldPath = `${pathToKey ? pathToKey + "." : ""}`;
if (Object.hasOwnProperty.call(obj, key)) {
results.push(`${oldPath}${key}`);
}
if (typeof obj === "object" && !Array.isArray(obj)) {
for (const k in obj) {
if (Object.hasOwnProperty.call(obj, k)) {
if (Array.isArray(obj[k])) {
for (let j = 0; j < obj[k].length; j++) {
findKey({
obj: obj[k][j],
key,
pathToKey: `${oldPath}${k}[${j}]`,
});
}
}
if (typeof obj[k] === "object") {
findKey({
obj: obj[k],
key,
pathToKey: `${oldPath}${k}`,
});
}
}
}
}
})(options);
return { count, results };
};
The count was just to troubleshoot a little bit and make sure it was actually running through the amount of keys I thought it was. Hope this helps any others looking for a solution!
⚠️ This code doesn't answer the question but does related: transforms nested object to query object with dot.divided.path as keys and non-object values; compatible with URlSearchParams & qs. Maybe will be useful for someone.
const isPlainObject = (v) => {
if (Object.prototype.toString.call(v) !== '[object Object]') return false;
const prototype = Object.getPrototypeOf(v);
return prototype === null || prototype === Object.prototype;
};
const objectToQueryObject = (obj, path) => {
return Object.entries(obj).reduce((acc, [key, value]) => {
const newPath = path ? `${path}.${key}` : key;
if (isPlainObject(value)) {
return {
...acc,
...objectToQueryObject(value, newPath)
};
}
acc[newPath] = value;
return acc;
}, {})
};
const queryObjectRaw = {
value: {
field: {
array: {
'[*]': {
field2: {
eq: 'foo',
ne: 'bar',
}
}
},
someOtherProp: { in: [1, 2, 3],
ne: 'baz',
}
},
someOtherField: {
gt: 123
},
},
otherValue: {
eq: 2
},
};
const result = objectToQueryObject(queryObjectRaw);
console.log('result', result);
const queryString = new URLSearchParams(result).toString();
console.log('queryString', queryString);
If you know only the value and not the key, and want to find all paths with this value use this.
It will find all property with that value, and print the complete path for every founded value.
const createArrayOfKeys = (obj, value) => {
const result = []
function iter(o) {
Object.keys(o).forEach(function(k) {
if (o[k] !== null && typeof o[k] === 'object') {
iter(o[k])
return
}
if (o[k]=== value) {
result.push(k)
return
}
})
}
iter(obj)
return result
}
function findPath (obj, name, val, currentPath) {
currentPath = currentPath || ''
let matchingPath
if (!obj || typeof obj !== 'object') return
if (obj[name] === val) return `${currentPath}/${name}/${val}`
for (const key of Object.keys(obj)) {
if (key === name && obj[key] === val) {
matchingPath = currentPath
} else {
matchingPath = findPath(obj[key], name, val, `${currentPath}/${key}`)
}
if (matchingPath) break
}
return matchingPath
}
const searchMultiplePaths = (obj, value) => {
const keys = createArrayOfKeys(obj, value)
console.log(keys);
keys.forEach(key => {
console.log(findPath(obj, key, value))
})
}
var data = { ffs: false, customer: { customer_id: 1544248, z_cx_id: '123456' }, selected_items: { '3600196': [{ id: 4122652, name: 'Essential Large (up to 8\'x10\')', selected: true }] }, service_partner: { id: 3486, name: 'Some String', street: '1234 King St.', hop: '123456' }, subject: 'Project-2810191 - Orange Juice Stain (Rug)', description: 'Product Type: \n\nIssue: (copy/paste service request details here)\n\nAction Required:', yes: '123456' };
searchMultiplePaths(data, '123456')
I know the post is old but the answers don't really satisfy me.
A simple solution is to add the object path to each object in the structure. Then you can easily read the path when you need it.
let myObject = {
name: 'abc',
arrayWithObject: [
{
name: "def"
},
{
name: "ghi",
obj: {
name: "jkl"
}
}
],
array: [15, 'mno'],
arrayArrayObject: [
[
{
name: '...'
}
]
]
}
function addPath(obj, path = [], objectPathKey = '_path') {
if (Array.isArray(obj)) {
obj.map((item, idx) => addPath(item, [...path, idx]))
} else if (typeof obj === "object") {
obj[objectPathKey] = path;
for (const key in obj) {
obj[key] = addPath(obj[key], [...path, key])
}
}
return obj
}
myObject = addPath(myObject);
let changeMe = _.cloneDeep(myObject.arrayWithObject[0])
changeMe.newProp = "NEW"
changeMe.newNested = {name: "new", deeper: {name: "asdasda"}}
changeMe = addPath(changeMe, changeMe._path)
_.set(myObject, changeMe._path, changeMe);
When your updates are done sanitize your object and remove your _path property.
Advantages of this solution:
You do the work once
you keep your code simple
no need for own property checks
no cognitive overload
I can highly suggest you to use lodash for this problem.
In their documentation this should help you out
// using "_.where" callback shorthand
_.find(characters, { 'age': 1 });
// → { 'name': 'pebbles', 'age': 1, 'blocked': false }

Find property by name in a deep object

I have a HUGE collection and I am looking for a property by key someplace inside the collection. What is a reliable way to get a list of references or full paths to all objects containing that key/index? I use jQuery and lodash if it helps and you can forget about infinite pointer recursion, this is a pure JSON response.
fn({ 'a': 1, 'b': 2, 'c': {'d':{'e':7}}}, "d");
// [o.c]
fn({ 'a': 1, 'b': 2, 'c': {'d':{'e':7}}}, "e");
// [o.c.d]
fn({ 'aa': 1, 'bb': 2, 'cc': {'d':{'x':9}}, dd:{'d':{'y':9}}}, 'd');
// [o.cc,o.cc.dd]
fwiw lodash has a _.find function that will find nested objects that are two nests deep, but it seems to fail after that. (e.g. http://codepen.io/anon/pen/bnqyh)
This should do it:
function fn(obj, key) {
if (_.has(obj, key)) // or just (key in obj)
return [obj];
// elegant:
return _.flatten(_.map(obj, function(v) {
return typeof v == "object" ? fn(v, key) : [];
}), true);
// or efficient:
var res = [];
_.forEach(obj, function(v) {
if (typeof v == "object" && (v = fn(v, key)).length)
res.push.apply(res, v);
});
return res;
}
a pure JavaScript solution would look like the following:
function findNested(obj, key, memo) {
var i,
proto = Object.prototype,
ts = proto.toString,
hasOwn = proto.hasOwnProperty.bind(obj);
if ('[object Array]' !== ts.call(memo)) memo = [];
for (i in obj) {
if (hasOwn(i)) {
if (i === key) {
memo.push(obj[i]);
} else if ('[object Array]' === ts.call(obj[i]) || '[object Object]' === ts.call(obj[i])) {
findNested(obj[i], key, memo);
}
}
}
return memo;
}
here's how you'd use this function:
findNested({'aa': 1, 'bb': 2, 'cc': {'d':{'x':9}}, dd:{'d':{'y':9}}}, 'd');
and the result would be:
[{x: 9}, {y: 9}]
this will deep search an array of objects (hay) for a value (needle) then return an array with the results...
search = function(hay, needle, accumulator) {
var accumulator = accumulator || [];
if (typeof hay == 'object') {
for (var i in hay) {
search(hay[i], needle, accumulator) == true ? accumulator.push(hay) : 1;
}
}
return new RegExp(needle).test(hay) || accumulator;
}
If you can write a recursive function in plain JS (or with combination of lodash) that will be the best one (by performance), but if you want skip recursion from your side and want to go for a simple readable code (which may not be best as per performance) then you can use lodash#cloneDeepWith for any purposes where you have to traverse a object recursively.
let findValuesDeepByKey = (obj, key, res = []) => (
_.cloneDeepWith(obj, (v,k) => {k==key && res.push(v)}) && res
)
So, the callback you passes as the 2nd argument of _.cloneDeepWith will recursively traverse all the key/value pairs recursively and all you have to do is the operation you want to do with each. the above code is just a example of your case. Here is a working example:
var object = {
prop1: 'ABC1',
prop2: 'ABC2',
prop3: {
prop4: 'ABC3',
prop5Arr: [{
prop5: 'XYZ'
},
{
prop5: 'ABC4'
},
{
prop6: {
prop6NestedArr: [{
prop1: 'XYZ Nested Arr'
},
{
propFurtherNested: {key100: '100 Value'}
}
]
}
}
]
}
}
let findValuesDeepByKey = (obj, key, res = []) => (
_.cloneDeepWith(obj, (v,k) => {k==key && res.push(v)}) && res
)
console.log(findValuesDeepByKey(object, 'prop1'));
console.log(findValuesDeepByKey(object, 'prop5'));
console.log(findValuesDeepByKey(object, 'key100'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
With Deepdash you can pickDeep and then get paths from it, or indexate (build path->value object)
var obj = { 'aa': 1, 'bb': 2, 'cc': {'d':{'x':9}}, dd:{'d':{'y':9}}}
var cherry = _.pickDeep(obj,"d");
console.log(JSON.stringify(cherry));
// {"cc":{"d":{}},"dd":{"d":{}}}
var paths = _.paths(cherry);
console.log(paths);
// ["cc.d", "dd.d"]
paths = _.paths(cherry,{pathFormat:'array'});
console.log(JSON.stringify(paths));
// [["cc","d"],["dd","d"]]
var index = _.indexate(cherry);
console.log(JSON.stringify(index));
// {"cc.d":{},"dd.d":{}}
Here is a Codepen demo
Something like this would work, converting it to an object and recursing down.
function find(jsonStr, searchkey) {
var jsObj = JSON.parse(jsonStr);
var set = [];
function fn(obj, key, path) {
for (var prop in obj) {
if (prop === key) {
set.push(path + "." + prop);
}
if (obj[prop]) {
fn(obj[prop], key, path + "." + prop);
}
}
return set;
}
fn(jsObj, searchkey, "o");
}
Fiddle: jsfiddle
In case you don't see the updated answer from #eugene, this tweak allows for passing a list of Keys to search for!
// Method that will find any "message" in the Apex errors that come back after insert attempts
// Could be a validation rule, or duplicate record, or pagemessage.. who knows!
// Use in your next error toast from a wire or imperative catch path!
// message: JSON.stringify(this.findNested(error, ['message', 'stackTrace'])),
// Testing multiple keys: this.findNested({thing: 0, list: [{message: 'm'}, {stackTrace: 'st'}], message: 'm2'}, ['message', 'stackTrace'])
findNested(obj, keys, memo) {
let i,
proto = Object.prototype,
ts = proto.toString,
hasOwn = proto.hasOwnProperty.bind(obj);
if ('[object Array]' !== ts.call(memo)) memo = [];
for (i in obj) {
if (hasOwn(i)) {
if (keys.includes(i)) {
memo.push(obj[i]);
} else if ('[object Array]' === ts.call(obj[i]) || '[object Object]' === ts.call(obj[i])) {
this.findNested(obj[i], keys, memo);
}
}
}
return memo.length == 0 ? null : memo;
}
Here's how I did it:
function _find( obj, field, results )
{
var tokens = field.split( '.' );
// if this is an array, recursively call for each row in the array
if( obj instanceof Array )
{
obj.forEach( function( row )
{
_find( row, field, results );
} );
}
else
{
// if obj contains the field
if( obj[ tokens[ 0 ] ] !== undefined )
{
// if we're at the end of the dot path
if( tokens.length === 1 )
{
results.push( obj[ tokens[ 0 ] ] );
}
else
{
// keep going down the dot path
_find( obj[ tokens[ 0 ] ], field.substr( field.indexOf( '.' ) + 1 ), results );
}
}
}
}
Testing it with:
var obj = {
document: {
payload: {
items:[
{field1: 123},
{field1: 456}
]
}
}
};
var results = [];
_find(obj.document,'payload.items.field1', results);
console.log(results);
Outputs
[ 123, 456 ]
We use object-scan for data processing tasks. It's pretty awesome once you've wrapped your head around how to use it.
// const objectScan = require('object-scan');
const haystack = { a: { b: { c: 'd' }, e: { f: 'g' } } };
const r = objectScan(['a.*.*'], { joined: true, rtn: 'entry' })(haystack);
console.log(r);
// => [ [ 'a.e.f', 'g' ], [ 'a.b.c', 'd' ] ]
.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
There are plenty more examples on the website.
The shortest and simplest solution:
Array.prototype.findpath = function(item,path) {
return this.find(function(f){return item==eval('f.'+path)});
}

formatting json data to be camelCased

I get a json response from the server that looks something like this:
{
"Response": {
"FirstName": "John",
"LastName": "Smith",
"NickNames": {
"NameOne": "Johnny",
"NameTwo": "JohnS",
"NameThree": "Smithy"
},
"Success": true,
"Errors": []
}
}
Is there a way I can run this response through a function so that the key of each key value pair would be camelCased?
So the output would look something like:
{
"response": {
"firstName": "John",
"lastName": "Smith",
"nickNames": {
"nameOne": "Johnny",
"nameTwo": "JohnS",
"nameThree": "Smithy"
},
"success": true,
"errors": []
}
}
If someone could point me in the right direction, that'd be great.
Thanks.
You would give JSON.parse a reviver function that assigns values to new properties that are lower-cased.
function toCamelCase(key, value) {
if (value && typeof value === 'object'){
for (var k in value) {
if (/^[A-Z]/.test(k) && Object.hasOwnProperty.call(value, k)) {
value[k.charAt(0).toLowerCase() + k.substring(1)] = value[k];
delete value[k];
}
}
}
return value;
}
var parsed = JSON.parse(myjson, toCamelCase);
More information about how it works in this SO answer.
The approach that user '#I Hate Lazy' suggested - using a 'reviver' function is - the right one. However his function didn't work for me.
Perhaps it is because I'm parsing a JSON array. Also I use Resharper and it complained about a code smell :) ('not all code paths return a value'). So I ended up using a function from another SO issue which did work for me:
function camelCaseReviver(key, value) {
if (value && typeof value === 'object') {
for (var k in value) {
if (/^[A-Z]/.test(k) && Object.hasOwnProperty.call(value, k)) {
value[k.charAt(0).toLowerCase() + k.substring(1)] = value[k];
delete value[k];
}
}
}
return value;
}
Here is a functional recursive (ES6) approach.
function convertKeysToCamelCase(o) {
if (o === null || o === undefined) {
return o;
} else if (Array.isArray(o)) {
return o.map(convertKeysToCamelCase);
}
return typeof o !== 'object' ? o : Object.keys(o).reduce((prev, current) => {
const newKey = `${current[0].toLowerCase()}${current.slice(1)}`;
if (typeof o[current] === 'object') {
prev[newKey] = convertKeysToCamelCase(o[current]);
} else {
prev[newKey] = o[current];
}
return prev;
}, {});
}
// successfully tested input
const o = {
SomeNum: 1,
SomeStr: 'a',
SomeNull: null,
SomeUndefined: undefined,
SomeBoolean: true,
SomeNaN: NaN,
NestedObject: {
SomeSentence: 'A is for apple',
AnotherNested: {
B: 'is for blahblah'
}
},
NumArray: [1, 2, 3, 4],
StringArray: ['a', 'b', 'c'],
BooleanArray: [true, false],
ArrayOfArrays: [[1,2,], ['a','b']],
ObjectArray: [{Foo:'bar'}, {Hello:'world', Nested:{In:'deep'}}],
MixedArray: [1,'a', true, null, undefined, NaN, [{Foo:'bar'}, 'wat']]
}
const output = convertKeysToCamelCase(o);
console.log(output.mixedArray[6][0].foo); // 'bar'
#adamjyee Your solution works except for nested array of integers. A small fix could be:
function convertKeysToCamelCase (o) {
if (o === null) {
return null
} else if (o === undefined) {
return undefined
} else if (typeof o === 'number') {
return o
} else if (Array.isArray(o)) {
return o.map(convertKeysToCamelCase)
}
return Object.keys(o).reduce((prev, current) => {
const newKey = `${current[0].toLowerCase()}${current.slice(1)}`
if (typeof o[current] === 'object') {
prev[newKey] = convertKeysToCamelCase(o[current])
} else {
prev[newKey] = o[current]
}
return prev
}, {})
[Right to comment but lacking comment priviledge :(]
You need to write a recursive function that traverses the tree and returns a new tree where the keys in the objects have been updated. The recursive function would call itself to deal with any sub-objects it encounters.

Categories

Resources