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
Related
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)
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);
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),
[]
);
}
I have the following sample arr:
const fetchedArr = [
{ id: "3cc74658-a984-4227-98b0-8c28daf7d3d4", type: a },
{ id: "9b96e055-dc2a-418c-9f96-ef449e34db60", type: a },
{ id: "9b96e055-dc2a-418c-9f96-ef449e34db60", type: b }
]
i need the following output :
const arr = [
{ id: "3cc74658-a984-4227-98b0-8c28daf7d3d4", type: a, checked: true },
{ id: "9b96e055-dc2a-418c-9f96-ef449e34db60", type: a, checked: true, hasPair: true }
]
I have the following snippet which works
const newLegendItems = fetchedArr
.reduce((acc, curr, idx, arr) => {
const singleComponentLines = arr.filter((g) => g.id === curr.id);
const exists = !!acc.find((x) => x.id === curr.id);
if (!exists) {
if (singleComponentLines.length === 2 && singleComponentLines.includes(curr)) {
acc[idx] = {...curr, hasPair: true};
} else {
acc[idx] = curr;
}
}
return acc;
}, [])
.map((l) => ({ ...l, checked: true }));
, but i was thinking if there's simpler way to achieve this?
I should clarify that in the fetchedArr, the type does not matter, and that there won't be more than two same Id's, hence my idea for singleComponentLines.length === 2.
Like this?
const fetchedArr = [
{ id: "3cc74658-a984-4227-98b0-8c28daf7d3d4", type: "a" },
{ id: "9b96e055-dc2a-418c-9f96-ef449e34db60", type: "a" },
{ id: "9b96e055-dc2a-418c-9f96-ef449e34db60", type: "b" }
];
let result = fetchedArr.reduce((acc,v) => {
//first i need to check if i already have an element with the same ID in my accumulator. i either get -1 for not found or the index where the element is.
let i = acc.findIndex(el => el.id === v.id);
if(i !== -1) {
//if there is an element then access the element in the array with a[i] and add a new property to the object with ["hasPair"] and set it to true
acc[i]["hasPair"] = true;
return acc;
}
//in case i = -1 what means not found
return [...acc, {...v, checked: true}];
},[])
console.log(result);
I don't fully understand your question but it should help:
const result = [{
id: "3cc74658-a984-4227-98b0-8c28daf7d3d4",
type: 'a'
},
{
id: "9b96e055-dc2a-418c-9f96-ef449e34db60",
type: 'a'
},
{
id: "9b96e055-dc2a-418c-9f96-ef449e34db60",
type: 'b'
}
].reduce((acc, el) => {
const idx = acc.findIndex(it => it.id === el.id);
if (idx > -1) {
acc[idx] = { ...acc[idx],
hasPair: true
}
} else {
acc.push({ ...el,
checked: true
});
}
return acc;
}, []);
console.log(result)
I rather use a Map for this kind of things since it brings more readability IMO.
Start by checking if we already have it
Update our component and add it to the Map
The only "tricky" thing is that we need to iterate over .values() to grab our updated components, but thanks to spread operator it's quite easy.
const components = [
{ id: "3cc74658-a984-4227-98b0-8c28daf7d3d4", type: 'a' },
{ id: "9b96e055-dc2a-418c-9f96-ef449e34db60", type: 'a' },
{ id: "9b96e055-dc2a-418c-9f96-ef449e34db60", type: 'b' },
];
const newLegendItems = components
.reduce((acc, component) => {
if (acc.has(component.id)) {
acc.get(component.id)['hasPair'] = true;
} else {
acc.set(component.id, { ...component, checked: true });
}
return acc;
}, new Map());
console.log([...newLegendItems.values()]);
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
};