Optimize function for finding a nested property in an object? - javascript

I made the following function which traverses an object recursively and tries to find if a property exists in that object. If it does, it returns that property and null if it is not found:
export const findNestedPropertyInObject = (sourceObject: any, targetKey: string) => {
let results: any;
const maxIterations = 100;
let currentIteration = 0;
const searchInObject = (obj: any, targetKey: string) => {
const keys = obj.length ? [] : Object.keys(obj);
if (keys && keys.length) {
if (keys.includes(targetKey)) {
results = obj[targetKey];
return;
} else {
keys.forEach((key: string) => {
if (currentIteration >= maxIterations) {
return;
}
currentIteration++;
searchInObject(obj[key], targetKey);
});
}
}
};
searchInObject(sourceObject, targetKey);
return results;
};
this is the sample object:
const aggregations = {
most_used_skills: {
doc_count: 388,
skill: {
doc_count_error_upper_bound: 10,
sum_other_doc_count: 325,
buckets: [
{
key: 'C++ Programming Language',
doc_count: 15,
},
{
key: 'Javascript',
doc_count: 14,
},
{
key: 'ExtJS',
doc_count: 12,
},
{
key: 'Amazon Web Services',
doc_count: 11,
},
{
key: 'Android',
doc_count: 11,
},
],
},
},
};
this is how i am invoking it:
console.log(findNestedPropertyInObject(aggregations, 'buckets'));
My question is that, how can i optimize this method? What safety checks can i add to this to make it more fault tolerant and robust?

export const findNestedPropertyInObject = (sourceObject: any, targetKey: string) => {
let results: any;
const maxIterations = 100;
let currentIteration = 0;
const searchInObject = (obj: any, targetKey: string) => {
const keys = (obj.length)? obj : Object.keys(obj); // if obj is object get keys as an array
if ( currentIteration < maxIterations && typeof keys === 'object') // checking if it actually an Array (js treat array as object type)
{
if (keys.includes(targetKey))
{
results = obj[targetKey];
currentIteration = maxIterations; // to stop recursive calling
return; //optional
}
else
{
keys.forEach((key: any) => {
currentIteration++;
const innerkeys = (obj[key])? obj[key] : key; // to check if 'key' is key of object or object itself
searchInObject(innerkeys, targetKey);
});
}
}
};
searchInObject(sourceObject, targetKey);
return results
};

Related

Modifying a specific value in a nested object (JavaScript)

I got this type of object:
const obj = {
group: {
data: {
data: [
{
id: null,
value: 'someValue',
data: 'someData'
}
]
}
}
};
I need to edit this object so whenever null is in the property value,
it would be replaced with some string.
Meaning if the replacement string will be 'someId',
the expected outcome is:
const obj = {
group: {
data: {
data: [
{
id: 'someId',
value: 'someValue',
data: 'someData'
}
]
}
}
};
Closest I found were this and this but didn't manage to manipulate the solutions there to what i need.
How should I do it?
Probably running into issues with the array values. Pass in the index of the array to modify. In this case [0]
obj.group.data.data[0].id = "someId"
EDIT
This will update all null values of id inside the data array:
obj.group.data.data.forEach(o => {
if (o.id === null) {
o.id = "someId"
}
})
Another EDIT
Here is an algorithm to recursively check all deeply nested values in an object. It will compile an array of object paths where null values live. There is an included helper method to find and update the value of the object at the given path in the array. There is a demonstration of the program in the console.
const object = {
group: {
data: {
data: [
{
id: null,
value: "foo",
data: [null, "bar", [null, { stuff: null }]]
},
{
id: null,
value: null,
data: {
bar: [null]
}
},
{
id: null,
value: "foo",
data: null
},
{
id: 4,
value: "foo",
data: "bar"
},
{
id: 4,
value: "stuff",
data: null
}
]
},
attributes: null,
errors: ["stuff", null]
}
}
const inspectProperty = (key, obj, path = "") => {
if (typeof obj[key] === "object") {
if (obj[key] instanceof Array) {
return analyzeArray(obj[key], `${path ? path + "." : ""}${key}`);
}
return analyzeObj(obj[key], `${path ? path + "." : ""}${key}`);
}
return [];
};
const analyzeKey = (obj, key, path = "") => {
if (obj[key] === null) return [`${path ? path + "." : ""}${key}`];
return inspectProperty(key, obj, path).reduce((a, k) => [...a, ...k], []);
};
const analyzeObj = (obj, path = "") => {
return Object.keys(obj).map((item) => analyzeKey(obj, item, path));
};
const analyzeArray = (array, path) => {
return array.map((item, i) => analyzeKey(array, i, path));
};
const updateNullValue = (path, value) => {
let p = path.split(".");
p.reduce((accum, iter, i) => {
if (i === p.length - 1) {
accum[iter] = value;
return object;
}
return accum[iter];
}, object);
};
let nullValues = analyzeObj(object)[0]
console.log(nullValues)
nullValues.forEach((nullVal, i) => {
updateNullValue(nullVal, "hello-" + i)
})
console.log(object)

Convert object to array of prorperties

I need to convert object:
{
middleName: null,
name: "Test Name",
university: {
country: {
code: "PL"
},
isGraduated: true,
speciality: "Computer Science"
}
}
to array:
[{
key: "name",
propertyValue: "Test Name",
},
{
key: "middleName",
propertyValue: null,
},
{
key: "university.isGraduated",
propertyValue: true,
},
{
key: "university.speciality",
propertyValue: "Computer Science",
},
{
key: "university.country.code",
propertyValue: "PL"
}];
I wrote algorithm, but it's pretty dummy, how can I improve it? Important, if object has nested object than I need to write nested object via dot (e.g university.contry: "value")
let arr = [];
Object.keys(parsedObj).map((key) => {
if (parsedObj[key] instanceof Object) {
Object.keys(parsedObj[key]).map((keyNested) => {
if (parsedObj[key][keyNested] instanceof Object) {
Object.keys(parsedObj[key][keyNested]).map((keyNestedNested) => {
arr.push({ 'key': key + '.' + keyNested + '.' + keyNestedNested, 'propertyValue': parsedObj[key][keyNested][keyNestedNested] })
})
} else {
arr.push({ 'key': key + '.' + keyNested, 'propertyValue': parsedObj[key][keyNested] })
}
})
} else {
arr.push({ 'key': key, 'propertyValue': parsedObj[key] })
}
});
Thanks
A recursive function implementation.
I have considered that your object consist of only string and object as the values. If you have more kind of data types as your values, you may have to develop on top of this function.
const myObj = {
middleName: null,
name: "Test Name",
university: {
country: {
code: "PL"
},
isGraduated: true,
speciality: "Computer Science"
}
}
const myArr = [];
function convertObjectToArray(obj, keyPrepender) {
Object.entries(obj).forEach(([key, propertyValue]) => {
if (typeof propertyValue === "object" && propertyValue) {
const updatedKey = keyPrepender ? `${keyPrepender}.${key}` : key;
convertObjectToArray(propertyValue, updatedKey)
} else {
myArr.push({
key: keyPrepender ? `${keyPrepender}.${key}` : key,
propertyValue
})
}
})
}
convertObjectToArray(myObj);
console.log(myArr);
I'd probably take a recursive approach, and I'd probably avoid unnecessary intermediary arrays (though unless the original object is massive, it doesn't matter). For instance (see comments):
function convert(obj, target = [], prefix = "") {
// Loop through the object keys
for (const key in obj) {
// Only handle "own" properties
if (Object.hasOwn(obj, key)) {
const value = obj[key];
// Get the full key for this property, including prefix
const fullKey = prefix ? prefix + "." + key : key;
if (value && typeof value === "object") {
// It's an object...
if (Array.isArray(value)) {
throw new Error(`Arrays are not valid`);
} else {
// ...recurse, providing the key as the prefix
convert(value, target, fullKey);
}
} else {
// Not an object, push it to the array
target.push({key: fullKey, propertyValue: value});
}
}
}
// Return the result
return target;
}
Live Example:
const original = {
middleName: null,
name: "Test Name",
university: {
country: {
code: "PL"
},
isGraduated: true,
speciality: "Computer Science"
}
};
function convert(obj, target = [], prefix = "") {
// Loop through the object keys
for (const key in obj) {
// Only handle "own" properties
if (Object.hasOwn(obj, key)) {
const value = obj[key];
// Get the full key for this property, including prefix
const fullKey = prefix ? prefix + "." + key : key;
if (value && typeof value === "object") {
// It's an object...
if (Array.isArray(value)) {
throw new Error(`Arrays are not valid`);
} else {
// ...recurse, providing the key as the prefix
convert(value, target, fullKey);
}
} else {
// Not an object, push it to the array
target.push({key: fullKey, propertyValue: value});
}
}
}
// Return the result
return target;
}
const result = convert(original, []);
console.log(result);
.as-console-wrapper {
max-height: 100% !important;
}
Note that I've assumed the order of the array entries isn't significant. The output you said you wanted is at odds with the standard order of JavaScript object properties (yes, they have an order now; no, it's not something I suggest relying on 😀). I've gone ahead and not worried about the order within an object.
This is the most bulletproof I could do :D, without needing a global variable, you just take it, and us it wherever you want!
const test = {
middleName: null,
name: "Test Name",
university: {
country: {
code: "PL"
},
isGraduated: true,
speciality: "Computer Science"
}
};
function toPropertiesByPath(inputObj) {
let arr = [];
let initialObj = {};
const getKeys = (obj, parentK='') => {
initialObj = arr.length === 0 ? obj: initialObj;
const entries = Object.entries(obj);
for(let i=0; i<entries.length; i++) {
const key = entries[i][0];
const val = entries[i][1];
const isRootElement = initialObj.hasOwnProperty(key);
parentK = isRootElement ? key: parentK+'.'+key;
if(typeof val === 'object' && val!==null && !Array.isArray(val)){
getKeys(val, parentK);
} else {
arr.push({ key: parentK, property: val });
}
}
};
getKeys(inputObj);
return arr;
}
console.log(toPropertiesByPath(test));
I wrote a small version using recursive function and another for validation is an object.
let values = {
middleName: null,
name: "Test Name",
university: {
country: {
code: "PL"
},
isGraduated: true,
speciality: "Computer Science"
}
}
function isObject(obj) {
return obj != null && obj.constructor.name === "Object"
}
function getValues(values) {
let arrValues = Object.keys(values).map(
v => {
return { key: v, value: isObject(values[v]) ? getValues(values[v]) : values[v] };
});
console.log(arrValues);
}
getValues(values);

Transforming data into paths

I have a data like this:
[
{
user_id: 356793,
bets: [
{
key: 'total',
team: {
away: 1959,
home: 1418
},
value: 1.76,
match_game: {
score: {
away: 17,
home: 18
}
}
}
]
}
]
And I want to transform it into paths or just log the paths, like this:
data[i].user_id
data[i].bets
data[i].bets[i]
data[i].bets[i].key
data[i].bets[i].team
data[i].bets[i].team.away
data[i].bets[i].team.home
data[i].bets[i].value
data[i].bets[i].match_game
data[i].bets[i].match_game.score
data[i].bets[i].match_game.score.away
data[i].bets[i].match_game.score.home
I think recursion does the trick here but it throws a RangeError: Maximum call stack size exceeded. And I don't know where it fails. It works on the first array but on the second array, it fails and causes an infinite loop.
Here's my code:
const data = [
{
user_id: 356793,
bets: [
{
key: 'total',
team: {
away: 1959,
home: 1418
},
value: 1.76,
match_game: {
score: {
away: 17,
home: 18
}
}
}
]
}
]
const isArray = hit => Array.isArray(hit)
const isObject = hit => !isArray(hit) && typeof hit === 'object'
const isNumber = hit => typeof hit === 'number'
const isString = hit => typeof hit === 'string'
const isNonIterable = hit => !isArray(hit) && !isObject(hit)
const toPaths = (payload, prefix) => {
if (isArray(payload)) {
for (let i = 0; i < data.length; i++) {
const currData = data[i]
toPaths(currData, prefix + '[i]')
}
return
}
if (isObject(payload)) {
for (const key in payload) {
const currData = payload[key]
if (isNonIterable(currData)) {
console.log(`${prefix}.${key}`)
continue
}
if (isArray(currData)) {
for (let i = 0; i < data.length; i++) {
const currData = data[i]
toPaths(currData, prefix + '[i]')
}
continue
}
// console.log(`${prefix}.key`)
}
return console.log(prefix)
}
}
toPaths(data, 'bets')
In two places, you iterate over data while you were supposed to use payload:
for (let i = 0; i < data.length; i++) {

Get the difference object from two object in typescript/javascript angular

I am trying to get the change object from two objects using typescript in angular.
For example
this.productPreviousCommand = {
"id": "60f910d7d03dbd2ca3b3dfd5",
"active": true,
"title": "ss",
"description": "<p>ss</p>",
"category": {
"id": "60cec05df64bde4ab9cf7460"
},
"subCategory": {
"id": "60cec18c56d3d958c4791117"
},
"vendor": {
"id": "60ced45b56d3d958c479111c"
},
"type": "load_product_success"
}
model = {
"active": true,
"title": "ss",
"description": "<p>ss sss</p>",
"category": "60cec05df64bde4ab9cf7460",
"subCategory": "60cec18c56d3d958c4791117",
"vendor": "60ced45b56d3d958c479111c",
"tags": []
}
Now the difference between two objects are description: "<p>hello hello 1</p>". So I want to return {description: "<p>hello hello 1</p>"}
I used lodash https://github.com/lodash/lodash
import { transform, isEqual, isObject, isArray} from 'lodash';
function difference(origObj, newObj) {
function changes(newObj, origObj) {
let arrayIndexCounter = 0
return transform(newObj, function (result, value, key) {
if (!isEqual(value, origObj[key])) {
let resultKey = isArray(origObj) ? arrayIndexCounter++ : key
result[resultKey] = (isObject(value) && isObject(origObj[key])) ? changes(value, origObj[key]) : value
}
})
}
return changes(newObj, origObj)
}
This library is not working for me, it returns the whole object using this code const differenc = difference(this.productPreviousCommand, model);
The output of above code is
{
active: true
description: "<p>hello hello 1</p>"
id: "60f8f29dd03dbd2ca3b3dfd1"
title: "hello"
}
Try this function
differenceInObj(firstObj: any, secondObj: any): any {
let differenceObj: any = {};
for (const key in firstObj) {
if (Object.prototype.hasOwnProperty.call(firstObj, key)) {
if(firstObj[key] !== secondObj[key]) {
differenceObj[key] = firstObj[key];
}
}
}
return differenceObj;
}
You can check loop through each key of the first object and compare it with the second object.
function getPropertyDifferences(obj1, obj2) {
return Object.entries(obj1).reduce((diff, [key, value]) => {
// Check if the property exists in obj2.
if (obj2.hasOwnProperty(key)) {
const val = obj2[key];
// Check if obj1's property's value is different from obj2's.
if (val !== value) {
return {
...diff,
[key]: val,
};
}
}
// Otherwise, just return the previous diff object.
return diff;
}, {});
}
const a = {
active: true,
description: '<p>hello</p>',
id: '60f8f29dd03dbd2ca3b3dfd1',
title: 'hello',
};
const b = {
active: true,
description: '<p>hello hello 1</p>',
id: '60f8f29dd03dbd2ca3b3dfd1',
title: 'hello',
};
const c = {
active: true,
description: '<p>hello hello 2</p>',
id: '60f8f29dd03dbd2ca3b3dfd1',
title: 'world',
};
console.log(getPropertyDifferences(a, b));
console.log(getPropertyDifferences(b, c));
function difference(origObj, newObj) {
const origObjKeyList = Object.keys(origObj),
newObjKeyList = Object.keys(newObj);
// if objects length is not same
if (origObjKeyList?.length !== newObjKeyList?.length) {
return;
}
// if object keys some difference in keys
if (Object.keys(origObj).filter((val) => !Object.keys(newObj).includes(val))?.length) {
return;
}
return Object.entries(origObj).reduce(
(acc, [key, value]) => (newObj[key] !== value ? { ...acc, ...{ [key]: newObj[key] } } : acc),
[]
);
}
const a = {
active: true,
description: '<p>hello</p>',
id: '60f8f29dd03dbd2ca3b3dfd1',
title: 'hello',
};
const b = {
active: true,
description: '<p>hello hello 1</p>',
id: '60f8f29dd03dbd2ca3b3dfd1',
title: 'hello',
};
console.log(difference(a, b));
You can try this code.
function difference(origObj, newObj) {
const origObjKeyList = Object.keys(origObj),
newObjKeyList = Object.keys(newObj);
// if objects length is not same
if (origObjKeyList?.length !== newObjKeyList?.length) {
return;
}
// if object keys is not same
if (Object.keys(origObj).filter((val) => !Object.keys(newObj).includes(val))?.length) {
return;
}
return Object.entries(origObj).reduce(
(acc, [key, value]) => (newObj[key] !== value ? { ...acc, ...{ [key]: newObj[key] } } : acc),
[]
);
}

Find path of string in object or array

Given an object or array, I want to be able to determine if the path exists or not.
Given - Example 1
const spath = "data/message";
const body = {
data: {
school: 'yaba',
age: 'tolu',
message: 'true'
},
time: 'UTC',
class: 'Finals'
}
it should return true because message can be found in body.data.message else return false.
Given - Example 2
const spath = "data/message/details/lastGreeting";
const body = {
data: {
school: 'yaba',
age: 'tolu',
message: {
content: 'now',
details: {
lastGreeting: true
}
}
},
time: 'UTC',
class: 'Finals'
}
it should return true because lastGreeting can be found in body.data.message.details.lastGreeting else return false.
The other condition is when the body consists of an array
Given - Example 3
const spath = "data/area/NY";
const body = {
data: {
school: 'yaba',
age: 'tolu',
names : ['darious'],
area: [{
NY: true,
BG: true
]]
message: {
content: 'now',
details: {
lastGreeting: true
}
}
},
time: 'UTC',
class: 'Finals'
}
it should return true because NY can be found in body.data.area[0].NY else return false.
This is the solution I came up with
const findPathInObject = (data, path, n) => {
console.log('entered')
console.log(data, path)
if(!data){
return false
}
let spath = path.split('/');
for(let i = 0; i<n; i++){
let lastIndex = spath.length - 1;
if(spath[i] in data && spath[i] === spath[lastIndex]){
return true
}
const currentIndex = spath[i];
// spath.splice(currentIndex, 1);
return findPathInObject(data[spath[currentIndex]], spath[i+1], spath.length)
}
return false
}
console.log(findPathInObject(body, spath, 3))
You could take some checks in advance and check if path is an empry string, then exit with true.
By having an array, you could exit early by checking the elements of the array with the actual path by omitting the indices.
For the final check of a key, you could check the existence of it and return the result of the recursove call with the rest path or return false, if the key is not in the object.
const
findPathInObject = (data, path) => {
if (!path) return true;
if (!data || typeof data !== 'object') return false;
if (Array.isArray(data)) return data.some(d => findPathInObject(d, path));
const
spath = path.split('/'),
key = spath.shift();
return key in data
? findPathInObject(data[key], spath.join('/'))
: false;
};
console.log(findPathInObject({ data: { school: 'yaba', age: 'tolu', message: 'true' }, time: 'UTC', class: 'Finals' }, "data/message", 3)); // true
console.log(findPathInObject({ data: { school: 'yaba', age: 'tolu', message: { content: 'now', details: { lastGreeting: true } } }, time: 'UTC', class: 'Finals' }, "data/message/details/lastGreeting", 3)); // true
console.log(findPathInObject({ data: { school: 'yaba', age: 'tolu', names: ['darious'], area: [{ NY: true, BG: true }], message: { content: 'now', details: { lastGreeting: true } } }, time: 'UTC', class: 'Finals' }, "data/area/NY", 3)); // true
find
For this answer, I'm going to provide a tree with varying degrees of nesting of objects and arrays -
const tree =
{ data:
{ school: "yaba", age: "tolu", message: "foo" }
, classes:
[ { name: "math" }, { name: "science" } ]
, deep:
[ { example:
[ { nested: "hello" }
, { nested: "world" }
]
}
]
}
Generators are a fantastic fit for this type of problem. Starting with a generic find which yields all possible results for a particular path -
function find (data, path)
{ function* loop (t, [k, ...more])
{ if (t == null) return
if (k == null) yield t
else switch (t?.constructor)
{ case Object:
yield *loop(t[k], more)
break
case Array:
for (const v of t)
yield *loop(v, [k, ...more])
break
}
}
return loop(data, path.split("/"))
}
Array.from(find(tree, "classes/name"))
Array.from(find(tree, "deep/example/nested"))
Array.from(find(tree, "x/y/z"))
[ "math", "science" ]
[ "hello", "world" ]
[]
find1
If you want a function that returns one (the first) result, we can easily write find1. This is particularly efficient because generators are pauseable/cancellable. After the first result is found, the generator will stop searching for additional results -
function find1 (data, path)
{ for (const result of find(data, path))
return result
}
find1(tree, "data/school")
find1(tree, "classes")
find1(tree, "classes/name")
find1(tree, "deep/example/nested")
find1(tree, "x/y/z")
"yaba"
[ { name: "math" }, { name: "science" } ]
"math"
"hello"
undefined
exists
If you care to check whether a particular path exists, we can write exists as a simple specialisation of find1 -
const exists = (data, path) =>
find1(data, path) !== undefined
exists(tree, "data/school")
exists(tree, "classes")
exists(tree, "deep/example/nested")
exists(tree, "x/y/z")
true
true
true
false
demo
Expand the snippet below to verify the results in your own browser -
function find (data, path)
{ function* loop (t, [k, ...more])
{ if (t == null) return
if (k == null) yield t
else switch (t?.constructor)
{ case Object:
yield *loop(t[k], more)
break
case Array:
for (const v of t)
yield *loop(v, [k, ...more])
break
}
}
return loop(data, path.split("/"))
}
function find1 (data, path)
{ for (const result of find(data, path))
return result
}
const tree =
{ data:
{ school: "yaba", age: "tolu", message: "foo" }
, classes:
[ { name: "math" }, { name: "science" } ]
, deep:
[ { example:
[ { nested: "hello" }
, { nested: "world" }
]
}
]
}
console.log("find1")
console.log(find1(tree, "data/school"))
console.log(find1(tree, "classes"))
console.log(find1(tree, "classes/name"))
console.log(find1(tree, "deep/example/nested"))
console.log(find1(tree, "x/y/z"))
console.log("find")
console.log(Array.from(find(tree, "classes/name")))
console.log(Array.from(find(tree, "deep/example/nested")))
console.log(Array.from(find(tree, "x/y/z")))
Strictly spoken, body.data.area[0].NY is not in the path of body, sorry. body.data.area is in the path. For the object without body.data.area as array here's a solution. If you want to include objects within arrays as part of an objects path, the solution will be more complex
const spath = "data/area/NY";
const spath2 = "data/message/details/lastGreeting";
const notPath = "data/message/details/firstGreeting";
const body = {
data: {
school: 'yaba',
age: 'tolu',
names : ['darious'],
area: {
NY: true,
BG: true
},
message: {
content: 'now',
details: {
lastGreeting: true
}
}
},
time: 'UTC',
class: 'Finals'
};
console.log(`${spath} exists? ${ exists(body, spath) && `yep` || `nope`}`);
console.log(`${spath2} exists? ${ exists(body, spath2) && `yep` || `nope`}`);
console.log(`${notPath} exists? ${ exists(body, notPath) && `yep` || `nope`}`);
function exists(obj, path) {
const pathIterable = path.split("/");
while (pathIterable.length) {
const current = pathIterable.shift();
// no path left and exists: true
if (pathIterable.length < 1 && current in obj) { return true; }
// up to now exists, path continues: recurse
if (current in obj) { return exists(obj[current], pathIterable.join("/")); }
}
// no solution found: false
return false;
}
You can check this solution. Will also check for array of objects.
const body = {
data: {
school: 'yaba',
age: 'tolu',
message: {
content: 'now',
details: {
lastGreeting: true,
},
},
area: [
{
NY: true,
BG: true,
},
],
},
time: 'UTC',
class: 'Finals',
};
const spath1 = 'data/message';
const spath2 = 'data/message/details/lastGreeting';
const spath3 = 'data/area/NY';
const spath4 = 'data/area/NY/Test';
console.log(`${spath1}: `, isPathExists(body, spath1.split('/'), 0));
console.log(`${spath2}: `, isPathExists(body, spath2.split('/'), 0));
console.log(`${spath3}: `, isPathExists(body, spath3.split('/'), 0));
console.log(`${spath4}: `, isPathExists(body, spath4.split('/'), 0));
function isPathExists(data, pathArr, i) {
const key = pathArr[i];
if (Array.isArray(data)) {
for (let value of data) {
if (isObject(value)) return isPathExists(value, pathArr, i);
}
} else if (data.hasOwnProperty(key)) {
if (key === pathArr[pathArr.length - 1]) return true;
return isPathExists(data[key], pathArr, i + 1);
} else return false;
return true;
}
function isObject(a) {
return !!a && a.constructor === Object;
}
With help of a colleague we eventually came up with something simple and easy to comprehend that really suits our needs. The answer with the yield implementation solves the problem but we were looking for something someone can read and easily understand in the codebase. We wanted to be able to check if the path exists in the object as well as get the value.
So we added a third param called returnValue - by default it will always return the value. If we don't want it to do that, we can set the value of the return value to false and the function will check if the path am looking exists, if it does, it will return true else return false
This is what we finally came up with
const find = (path, data) => {
if (Array.isArray(data)) {
data = data[0];
}
for (const item in data) {
if (item === path) {
return data[item];
}
}
return null;
};
const findPath = (fullPath, fullData, returnValue = true) => {
const pathArray = fullPath.split('/');
let findResult = fullData;
for (const pathItem of pathArray) {
findResult = find(pathItem, findResult);
if (!findResult) {
if (!returnValue) return false;
return null;
}
}
if (!returnValue) return true;
return findResult;
};
const body = {
name: 'mike',
email: 1,
data: {
school: [
{
testing: 123
}
]
}
}
console.log(findPath('data/school/testing', body))
Here is a clean, iterative solution using object-scan
.as-console-wrapper {max-height: 100% !important; top: 0}
<script type="module">
import objectScan from 'https://cdn.jsdelivr.net/npm/object-scan#18.1.2/lib/index.min.js';
const data1 = { data: { school: 'yaba', age: 'tolu', message: 'true' }, time: 'UTC', class: 'Finals' };
const data2 = { data: { school: 'yaba', age: 'tolu', message: { content: 'now', details: { lastGreeting: true } } }, time: 'UTC', class: 'Finals' };
const data3 = { data: { school: 'yaba', age: 'tolu', names: ['darious'], area: [{ NY: true, BG: true }], message: { content: 'now', details: { lastGreeting: true } } }, time: 'UTC', class: 'Finals' };
const path1 = 'data/message';
const path2 = 'data/message/details/lastGreeting';
const path3 = 'data/area/NY';
const exists = (data, n) => objectScan([n.replace(/\//g, '.')], {
useArraySelector: false,
rtn: 'bool',
abort: true
})(data);
console.log(exists(data1, path1));
// => true
console.log(exists(data2, path2));
// => true
console.log(exists(data3, path3));
// => true
</script>
Disclaimer: I'm the author of object-scan

Categories