Find and update in nested json object - javascript

I used this code to find the required portion from the json object from sJhonny's Question
Data Sample
TestObj = {
"Categories": [{
"Products": [{
"id": "a01",
"name": "Pine",
"description": "Short description of pine."
},
{
"id": "a02",
"name": "Birch",
"description": "Short description of birch."
},
{
"id": "a03",
"name": "Poplar",
"description": "Short description of poplar."
}],
"id": "A",
"title": "Cheap",
"description": "Short description of category A."
},
{
"Product": [{
"id": "b01",
"name": "Maple",
"description": "Short description of maple."
},
{
"id": "b02",
"name": "Oak",
"description": "Short description of oak."
},
{
"id": "b03",
"name": "Bamboo",
"description": "Short description of bamboo."
}],
"id": "B",
"title": "Moderate",
"description": "Short description of category B."
}]
};
Function to find
function getObjects(obj, key, val) {
var objects = [];
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] == 'object') {
objects = objects.concat(getObjects(obj[i], key, val));
} else if (i == key && obj[key] == val) {
objects.push(obj);
}
}
return objects;
}
Use like so:
getObjects(TestObj, 'id', 'A'); // Returns an array of matching objects
This code is to select matching piece from the source. But what I want is to update the source object with new value and retrieve the updated source object.
I want something like
getObjects(TestObj, 'id', 'A', 'B'); // Returns source with updated value. (ie id:'A' updated to id:'B' in the returned object)
My code
function getObjects(obj, key, val, newVal) {
var newValue = newVal;
var objects = [];
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] == 'object') {
objects = objects.concat(getObjects(obj[i], key, val));
} else if (i == key && obj[key] == val) {
obj[key] = 'qwe';
}
}
return obj;
}
This works if i give obj[key] = 'qwe'; but if i change the code into obj[key] = newValue; its updated as undefined.
Why is that so?

You forgot to pass newValue in the nested call
function getObjects(obj, key, val, newVal) {
var newValue = newVal;
var objects = [];
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] == 'object') {
objects = objects.concat(getObjects(obj[i], key, val, newValue));
} else if (i == key && obj[key] == val) {
obj[key] = 'qwe';
}
}
return obj;
}

This ?
function update(obj, key, newVal) {
for(var i in obj) {
if(typeof obj[i] == 'object') {
update(obj[i], key, newVal));
} else if(i === key) {
obj[i] = newVal;
}
}
return obj;
}

function getObjects(obj, key, val, newVal) {
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (i == key && obj[key] == val) {
obj[key] = newVal;
}
}
return obj
}
This will do the inplace update of a found value with the newValue (newVal)

you can try my solution
const InsertOrUpdate = (dest, src) => {
GetValue(dest, src, [])
}
const GetValue = (dest, src, keys) => {
for (let key in src) {
let srcValue = src[key]
// Don't use push here
// The concat() method does not change the existing arrays, but returns a new array, containing the values of the joined arrays
let parentKeys = keys.concat(key)
if (typeof (srcValue) === 'object') {
GetValue(dest, srcValue, parentKeys)
} else {
SetValue(dest, parentKeys, srcValue)
}
}
}
const SetValue = (dest, keys, value) => {
if (!keys.length || keys.length === 0) {
return
}
let key = keys[0]
let childKeys = keys.slice(1)
// update
// note: null is object
if (dest[key] && typeof (dest[key]) === 'object') {
SetValue(dest[key], childKeys, value)
} else {
// insert
if (childKeys.length === 0) {
dest[key] = value
} else {
// insert parent key & continue update
dest[key] = {}
SetValue(dest[key], childKeys, value)
}
}
}

I tried using the selected solution above, but it would update every row with the same value. So I added a way to define what record you want to update, and also a way to keep track of the current record ID once you've already looped past it.
function getObjects(obj, rowId, key, val, newVal, rId) {
var objects = [];
for (var i in obj) {
if(obj[i].id !== undefined) rId = obj[i].id;
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] == 'object') {
objects = objects.concat(this.updateObject(obj[i], rowId, key, val, newVal, rId));
} else if (i == key && obj[key] == val) {
if(rId == rowId) obj[key] = newVal;
}
}
return obj;
}

Take a look at object-scan. We use it for a lot of data processing now. For us it makes the code much more maintainable, just takes a moment to wrap your head around it. Here is how you could answer your question
// const objectScan = require('object-scan');
const update = (data, needle, from, to) => objectScan([needle], {
abort: true,
rtn: 'bool',
filterFn: ({ value, parent, property }) => {
if (value === from) {
parent[property] = to;
return true;
}
return false;
}
})(data);
// -------------------------------
const TestObj = { Categories: [{ Products: [{ id: 'a01', name: 'Pine', description: 'Short description of pine.' }, { id: 'a02', name: 'Birch', description: 'Short description of birch.' }, { id: 'a03', name: 'Poplar', description: 'Short description of poplar.' }], id: 'A', title: 'Cheap', description: 'Short description of category A.' }, { Product: [{ id: 'b01', name: 'Maple', description: 'Short description of maple.' }, { id: 'b02', name: 'Oak', description: 'Short description of oak.' }, { id: 'b03', name: 'Bamboo', description: 'Short description of bamboo.' }], id: 'B', title: 'Moderate', description: 'Short description of category B.' }] };
console.log(update(TestObj, '**.id', 'A', 'B'));
// => true
console.log(TestObj);
// => { Categories: [ { Products: [ { id: 'a01', name: 'Pine', description: 'Short description of pine.' }, { id: 'a02', name: 'Birch', description: 'Short description of birch.' }, { id: 'a03', name: 'Poplar', description: 'Short description of poplar.' } ], id: 'B', title: 'Cheap', description: 'Short description of category A.' }, { Product: [ { id: 'b01', name: 'Maple', description: 'Short description of maple.' }, { id: 'b02', name: 'Oak', description: 'Short description of oak.' }, { id: 'b03', name: 'Bamboo', description: 'Short description of bamboo.' } ], id: 'B', title: 'Moderate', description: 'Short description of category B.' } ] }
.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

Related

ES5 JavaScript: Find specific property in nested objects and return its value

I need to return the value of a specific property of nested objects with ES5 syntax. Every object can have its own structure, so the needed property can be on different levels/places. E.g. -> I have three different objects and the value of property "source" is needed:
first_data has that property in list.details.source
second_data has that property in list.details.products[0]._new.source
third_data does not have this property, therefore it should return false
So how can I return the value of the specific property with consideration, that it can be on any position in object?
var first_data = {
area: "South",
code: "S265",
info: {
category: "enhanced",
label: "AB | 27AS53",
variable: "VR"
},
list: {
area: "Mid",
details: [
{
source: "Spain",
totals: 12,
products: [
{
name: "ABC",
brand: "Nobrand",
id: "111",
category: "Men",
}
]
}
]
},
time: 1654775446138
};
var second_data = {
area: "South",
code: "S265",
info: {
category: "enhanced",
label: "AB | 27AS53",
variable: "VR"
},
list: {
area: "Mid",
details: [
{
products: [
{
name: "ABC",
brand: "Nobrand",
id: "111",
category: "Men",
_new: {
source: "Spain",
totals: 12
}
}
]
}
]
},
time: 1654775446138
};
var third_data = {
area: "South",
code: "S265",
info: {
category: "enhanced",
label: "AB | 27AS53",
variable: "VR"
},
list: {
area: "Mid",
details: [
{
products: [
{
name: "ABC",
brand: "Nobrand",
id: "111",
category: "Men"
}
]
}
]
},
time: 1654775446138
};
I first tried to solve it with ES6, so that I can rewrite it in a second step into ES5. Here is what I have so far. The first problem is that here I am getting a false, but the property exists.
var propertyExists = function (obj, key) {
if(obj === null || obj === undefined) {
return false;
}
for(const k of Object.keys(obj)) {
if(k === key) {
return obj[k]
}
else {
const val = obj[k];
if(typeof val === 'object') {
if(propertyExists(val, key) === true) {
return true;
}
}
}
}
return false;
}
propertyExists(first_data, 'source')
Your propertyExists function didn't work because it returned the value of source but it later checked if the value is equal to true (as described by Felix Kling in a comment above).
Here's my implementation (originally in ES6 but I used a typescript compiler with target set to ES5):
var findProp = function (obj, prop) {
if (typeof obj != "object") {
return false;
}
if (obj.hasOwnProperty(prop)) {
return obj[prop];
}
for (var _i = 0, _a = Object.keys(obj); _i < _a.length; _i++) {
var p = _a[_i];
if (typeof obj[p] === "object") {
var t = findProp(obj[p], prop);
if (t) {
return t;
}
}
}
return false;
};
Note: It might be faster to detect which object structure it is and retrieve the value because you would then know where it is.

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);

Spread operator with reduce function in js

I trying to generate all possible paths of the given json object. Some how I generated the paths but I want my final array in a flatten manner (no nested arrays inside the final array).
I tried speading the array, but the final array contains some nested arrays. I want to have all the elements in a flatter manner.
Current op:
[
"obj",
"level1.level2.level3.key",
[
"arrayObj.one[0].name",
"arrayObj.one[0].point"
]
]
Expected:
[
"obj",
"level1.level2.level3.key",
"arrayObj.one[0].name",
"arrayObj.one[0].point"
]
Below I have attached the snippet I tried.
const allPaths = (obj, path = "") =>
Object.keys(obj).reduce((res, el) => {
if (Array.isArray(obj[el]) && obj[el].length) {
return [...res, ...obj[el].map((item, index) => {
return [...res, ...allPaths(item, `${path}${el}[${index}].`)];
})];
} else if (typeof obj[el] === "object" && obj[el] !== null) {
return [...res, ...allPaths(obj[el], `${path}${el}.`)];
}
return [...res, path + el];
}, []);
const obj = {
obj: 'sample',
level1: {
level2: {
level3: {
key: 'value'
}
}
},
arrayObj: {
one: [{
name: 'name',
point: 'point'
},
{
name: 'name2',
point: 'point2'
},
{
name: 'name2',
point: 'point2'
}
]
}
}
console.log(allPaths(obj));
UPDATE: I didn't understood the question previously correctly. Now i do. So yes the below code will solve the problem for you.
You want your object to be flattened with dots
If thats the case the below should work
const obj = {
obj: 'sample',
level1: {
level2: {
level3: {
key: 'value'
}
}
},
arrayObj: {
one: [{
name: 'name',
point: 'point'
},
{
name: 'name2',
point: 'point2'
},
{
name: 'name2',
point: 'point2'
}
]
}
}
function flatten(data, prefix) {
let result = {}
for(let d in data) {
if(typeof data[d] == 'object') Object.assign(result, flatten(data[d], prefix + '.' + d))
else result[(prefix + '.' + d).replace(/^\./, '')] = data[d]
}
return result
}
console.log(flatten(obj, ''))

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 a value in an object containing nested objects and array

I have the following object and a value -
{
location:"xyz",
title:"abc",
company: {
address:"address can have spaces",
name:"name"
},
array-key :[
{ skill : "skill1"},
{ skill : "skill2"},
{ skill : "skill3"}
],
description :"brief description"
}
and now I have a value - "spaces", now I want to check if "spaces" is present in the object at any level. If "spaces" is present function should return true.
I tried the recursive way but how should I handle the array?
One useful trick for iterating recursively over an object is to use the replacer parameter to JSON.stringify.
function findString(obj, regexp) {
let found = false;
JSON.stringify(obj, (k, v) => {
if (found || typeof v === 'string' && regexp.test(v)) found = true;
else return v;
});
return found;
}
Just for completeness with iterating all levels recursively and checking the value either strict or as string and with String#indexOf.
function check(object, value) {
return Object.keys(object).some(function (key) {
if (object[key] && typeof object[key] === 'object') {
return check(object[key], value);
}
return object[key] === value || object[key].toString().indexOf(value) + 1;
});
}
var data = { location: "xyz", title: "abc", company: { address: "address can have spaces", name: "name" }, arrayKey: [{ skill: "skill1" }, { skill: "skill2" }, { skill: "skill3" }], description: "brief description" };
console.log(check(data, "spaces"));
console.log(check(data, "foo"));

Categories