How to merge nested objects with the same key - javascript

I'm having a hard time merging nested objects with potentially the same key using Object.assign();
See code as an example
// Initial structure
let state = {
pageIndex: 1,
allResults: {
queries: {}
}
}
Code
const assign = (query, page) => {
const obj = {
[page]: {
res: 'hi'
}
}
state.allResults.queries = Object.assign(
{},
state.allResults.queries,
state.allResults.queries[query] || {[query]: {}},
obj
)
}
assign('hi', state.pageIndex);
assign('hi', (state.pageIndex + 1));
assign('hello', (state.pageIndex + 1));
console.log(state)
What I'm getting
state = {
pageindex: 1,
allResults: {
queries: {
1: {
res: 'hi'
},
2: {
res: 'hi'
},
hello: {},
hi: {}
}
}
}
What I expect
let state = {
pageIndex: 1,
allResults: {
queries: {
hi: {
1: {
res: 'h'
},
2: {
res: 'h'
}
},
hello: {
2: {
res: 'h'
}
}
}
}
}
So, this the way how I'm doing it doesn't really work, and I can't figure out how to get the expected result.
Thanks in advance

This will assign the desired sub key of the queries key which you send to the assign function (hi or hello) to their previous value, combined with the new value.
state.allResults.queries[query] = Object.assign(
{},
state.allResults.queries[query] || {},
obj
)

I think this could work for you:
const assign = (query, page) => {
const obj = {
[page]: {
res: 'hi'
}
}
let _obj = Object.assign(
{},
state.allResults.queries[query] || {},
obj
);
state.allResults.queries = Object.assign(
{},
state.allResults.queries,
{ [query]: _obj }
)
}
First I created the plain object that will be assigned to the subQuery object. Then I merge it into a existing (if not, {} an empty) object.
After that I just merge that into the query object.
Hope that it helps you.

You could use a nested Object.assign.
const assign = (query, page) => {
const obj = { [page]: { res: 'hi' } }
state.allResults.queries = Object.assign(
{},
state.allResults.queries,
{ [query]: Object.assign(state.allResults.queries[query] || {}, obj) }
);
}
let state = { pageIndex: 1, allResults: { queries: {} } };
assign('hi', state.pageIndex);
assign('hi', (state.pageIndex + 1));
assign('hello', (state.pageIndex + 1));
console.log(state)
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

Compare nested objects and list out differences in JavaScript

I want the difference in such a way that the I don't return the entire nested object if any of the values is different.
I have seen solutions online and they all return the entire nested objects and it doesn't work if only 1 key-value pair is changed. i don't want to show the difference as a complete nested object. it should be easier for any user to read.
for eg:
const A = {
position: 2,
attributes: [{
code: 123,
name: "xyz",
params: {
label: "hehe",
units: "currency"
}
}],
code: 1
}
const B = {
position: 3,
attributes: [{
code: 123,
name: "xyzr",
params: {
label: "heh",
units: "currency"
}
}],
code: 1
}
I want the output to be like this:
difference: {
position: {
current: 2,
previous: 3
},
attributes: {
current : [{ name: "xyz", params: { label: "hehe" } }],
previous: [{ name: "xyzr", params: {label: "heh"}}]
}
}
The code that I tried:
const compareEditedChanges = (A: any, B: any) => {
const allKeys = _.union(_.keys(A), _.keys(B));
try {
setDifference(
_.reduce(
allKeys,
(result: any, key) => {
if (!_.isEqual(A?.[key], B?.[key])) {
result[key] = {
current: A[key],
previous: B[key]
};
}
return result;
},
{}
)
);
} catch (err) {
console.error(err);
}
return difference;
};
After giving it a lot of thought to the code, I came with my own solution for a deeply nested objects comparison and listing out the differences in an object with keys as current and previous.
I didn't use any inbuilt libraries and wrote the code with simple for loop, recursion and map
const compareEditedChanges = (
previousState,
currentState
) => {
const result = [];
for (const key in currentState) {
// if value is string or number or boolean
if (
typeof currentState[key] === 'string' ||
typeof currentState[key] === 'number' ||
typeof currentState[key] === 'boolean'
) {
if (String(currentState[key]) !== String(previousState[key])) {
result.push({
[key]: {
current: currentState[key],
previous: previousState[key]
}
});
}
}
// if an array
if (
Array.isArray(currentState[key]) ||
Array.isArray(previousState[key])
) {
console.log(currentState[key])
if (currentState[key].length > 0 || previousState[key].length > 0) {
currentState[key].map((value, index) => {
// check for array of string or number or boolean
if (
typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'boolean'
) {
if (
JSON.stringify(currentState[key]) !==
JSON.stringify(previousState[key])
) {
result.push({
[key]: {
current: currentState[key],
previous: previousState[key]
}
});
}
}
// check for array of objects
if (typeof value === 'object') {
const ans = compare(
value,
previousState[key][index]
);
result.push(ans);
}
});
}
}
}
return result;
};
You first need a object:
const [object, setObject] = useState({
number: 0,
text: "foo"
});
You need to check when the object changed with useEffect, but you also need to see the previos object, for that we will be using a helper function.
const prevObject = usePrevious(object);
const [result, setResult] = useState("");
useEffect(() => {
if (prevObject) {
if (object.number != prevObject.number) {
setResult("number changed");
}
if (object.text != prevObject.text) {
setResult("text changed");
}
}
}, [object]);
//Helper function to get previos
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
Here is the Codesandbox

Getting last appearance of a deeply nested value by specified property

I've got this object as example:
const obj = {
group: {
data: {
data: [
{
id: null,
value: 'someValue',
data: 'someData'
}
]
}
}
};
My goal is to get a value by propery name.
In this case I would like to get
the value of the data propery, but the last appearance of it.
Meaning the expected output is:
someData
However, I'm using this recursive function to retreive it:
const findVal = (obj, propertyToExtract) => {
if (obj && obj[propertyToExtract]) return obj[propertyToExtract];
for (let key in obj) {
if (typeof obj[key] === 'object') {
const value = findVal(obj[key], propertyToExtract);
if (value) return value;
}
}
return false;
};
Which gives me the first appearance of data, meaning:
data: [
{
value: 'someValue',
data: 'someData'
}
]
How can I get the wanted result?
one way can be to recursively flat the object and just get the wanted index (here data)
const obj = {
group: {
data: {
data: [
{
id: null,
value: 'someValue',
data: 'someData'
}
]
}
}
};
function deepFlat(obj) {
let flatObj = {};
flat(obj,flatObj);
console.log(flatObj);
return flatObj;
}
function flat(toFlat, flatObj) {
Object.entries(toFlat).forEach(elem => {
if (elem[1] && typeof elem[1] === 'object') {
flat(elem[1], flatObj);
} else {
flatObj[elem[0]] = elem[1];
}
});
}
let result = deepFlat(obj);
console.log(result['data']);

restructure json based on parent

I am studying the use of reduce in javascript, and I am trying to restructure an Array of Objects in a generic way - need to be dynamic.
flowchart - i get totaly lost
I started with this through.
Every ID becomes a Key.
Every PARENT identifies which Key it belongs to.
i have this:
const in = [
{
"id": "Ball",
"parent": "Futebol"
},
{
"id": "Nike",
"parent": "Ball"
},
{
"id": "Volley",
"parent": null
}
]
i want this
out = {
"Futebol": {
"Ball": {
"Nike": {}
}
},
"Volley": {}
}
i try it - and i had miserably failed.
const tree = require('./mock10.json')
// Every ID becomes a Key.
// Every PARENT identifies which Key it belongs to.
const parsedTree = {}
tree.reduce((acc, item) => {
if (parsedTree.hasOwnProperty(item.parent)){
if (parsedTree[`${item.parent}`].length > 0) {
parsedTree[`${item.parent}`][`${item.id}`] = {}
} else {
parsedTree[`${item.parent}`] = { [`${item.id}`]: {} }
}
} else {
// i get lost in logic
}
}, parsedTree)
console.log(parsedTree)
Got a working code for you, feel free to ask me about the implementation
Hope it helps :)
const arrSample = [
{
"id": "Ball",
"parent": "Futebol"
},
{
"id": "Nike",
"parent": "Ball"
},
{
"id": "Volley",
"parent": null
}
]
const buildTree = (arr) => {
return arr.reduce(([tree, treeMap], { id, parent }) => {
const val = {}
treeMap.set(id, val)
if (!parent) {
tree[id] = val
return [tree, treeMap]
}
if (!treeMap.has(parent)) {
const parentVal = { [id]: val }
treeMap.set(parent, parentVal)
tree[parent] = parentVal
return [tree, treeMap]
}
const newParentValue = treeMap.get(parent)
newParentValue[id] = val
treeMap.set(parent, newParentValue)
return [tree, treeMap]
}, [{}, new Map()])
}
const [result] = buildTree(arrSample)
console.log(JSON.stringify(result, 0, 2))
You could use reduce method for this and store each id on the first level of the object. This solution will work if the objects in the array are in the correct order as in the tree structure.
const data = [{"id":"Futebol","parent":null},{"id":"Ball","parent":"Futebol"},{"id":"Nike","parent":"Ball"},{"id":"Volley","parent":null}]
const result = data.reduce((r, { id, parent }) => {
if (!parent) {
r[id] = {}
r.tree[id] = r[id]
} else if (r[parent]) {
r[parent][id] = {}
r[id] = r[parent][id]
}
return r
}, {tree: {}}).tree
console.log(result)
If reduce solution is just an option, you can try this way:
var input = [
{
"id": "Ball",
"parent": "Futebol"
},
{
"id": "Nike",
"parent": "Ball"
},
{
"id": "Volley",
"parent": null
}
];
var output = {};
input.forEach(item => {
var temp = input.find(x => x.id === item.parent);
if (temp) {
temp[item.id] = {};
}
});
input = input.filter(item => !input.find(x => x.hasOwnProperty(item.id)));
input.forEach(item => {
if (!item.parent) {
output[item.id] = {};
} else {
for (var [id, value] of Object.entries(item)) {
if (typeof value === 'object') {
output[item.parent] = { [item.id]: { id: {} } };
}
}
}
})
console.log(output);
I have tried many things, but none works if we use an Array.prototype.reduce
As there are missing parents, and the elements are out of order, plus the fact that there can be an infinity of levels, I really do not believe that this question can be resolved with a simple reduce
This code should work whatever the cases :
- if all parents are not declared
- if there are infinitely many levels
- if they are in disorder
const origin =
[ { id: 'Ball', parent: 'Futebol' }
, { id: 'Nike', parent: 'Ball' }
, { id: 'Volley', parent: null }
, { id: 'lastOne', parent: 'level4' } // added
, { id: 'level4', parent: 'Nike' } // added
, { id: 'bis', parent: 'Nike' } // added
];
const Result = {} // guess who ?
, Parents = [] // tempory array to keep parents elements address by key names
;
let nbTodo = origin.length // need this one to verify number of elements to track
;
// set all the first levels, add a todo flags
origin.forEach(({id,parent},i,ori)=>
{
ori[i].todo = true // adding todo flag
if (parent===null)
{
Result[id] = {} // new first level element
ori[i].todo = false // one less :)
nbTodo--
Parents.push(({ref:id,path:Result[id]}) ) // I know who you are!
}
else if (origin.filter(el=>el.id===parent).length===0) // if he has no parent...
{
Result[parent] = {} // we create it one
Parents.push({ref:parent,path:Result[parent]} )
}
})
// to put the children back in their parents' arms
while(nbTodo>0) // while there are still some
{
origin.forEach(({id,parent,todo},i,ori)=> // little by little we find them all
{
if(todo) // got one !
{
let pos = Parents.find(p=>p.ref===parent) // have parent already been placed?
if(pos)
{
ori[i].todo = false // to be sure not to repeat yourself unnecessarily
nbTodo-- // one less :)
pos.path[id] = {} // and voila, parentage is done
Parents.push(({ref:id,path:pos.path[id]}) ) // he can now take on the role of parent
}
}
})
}
for (let i=origin.length;i--;) { delete origin[i].todo } // remove todo flags
console.log( JSON.stringify(Result, 0, 2) )
.as-console-wrapper { max-height: 100% !important; top: 0; }
I finaly made this one, based on this previous on, and done with a first step by a reduce...
to by pass the Array of Parents, I made a recursive function for searching each parent elements thru the levels of parsedTree result.
here is the code:
const Tree =
[ { id: 'Ball', parent: 'Futebol' }
, { id: 'Nike', parent: 'Ball' }
, { id: 'Volley', parent: null }
, { id: 'lastOne', parent: 'level4' } // added
, { id: 'level4', parent: 'Nike' } // added
, { id: 'bis', parent: 'Nike' } // added
];
const parsedTree = Tree.reduce((parTree, {id,parent},i ) => {
Tree[i].todo = false
if (parent===null)
{ parTree[id] = {} }
else if (Tree.filter(el=>el.id===parent).length===0) // if he has no parent...
{ parTree[parent] = { [id]: {} } }
else
{ Tree[i].todo = true }
return parTree
}, {})
function parsedTreeSearch(id, part) {
let rep = null
for(let kId in part) {
if (kId===id)
{ rep = part[kId] }
else if (Object.keys(part[kId]).length)
{ rep = parsedTreeSearch(id, part[kId]) }
if (rep) break
}
return rep
}
while (Boolean(Tree.find(t=>t.todo))) {
Tree.forEach(({id,parent,todo},i)=>{ // little by little we find them all
if (todo) {
let Pelm = parsedTreeSearch(parent, parsedTree)
if (Boolean(Pelm)) {
Pelm[id] = {}
Tree[i].todo = false
} } }) }
for (let i=Tree.length;i--;) { delete Tree[i].todo } // remove todo flags
console.log( JSON.stringify( parsedTree ,0,2))
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to pick data from array and create a single Object from it?

I want to create a single object from an array of objects. Please refer the code provided.
Here's the input array
let queryArr = [
{
query: {
filter: {
term: {
search: 'complete',
}
}
}
},
{
query: {
notFilter: {
term: {
search: 'failed',
}
}
}
},
{
query: {
bool: {
term: {
search: 'complete',
}
}
}
}
]
The expected output
let oneQuery = {query: {
bool: { ... },
filter: { ... },
notFilter: { ... } // data from respective array object key
}};
The function I wrote
function createQuery(arr){
for(let i = 0; i < arr.length; i++){
if(Object.keys(arr[i].query === 'bool')){
oneQuery.query.bool = arr[i].query.bool;
}
if(Object.keys(arr[i].query === 'filter')){
oneQuery.query.filter = arr[i].query.filter;
}
if(Object.keys(arr[i].query === 'notFilter')){
oneQuery.query.notFilter = arr[i].query.notFilter;
}
}
return oneQuery;
}
createQuery(queryArr);
The output I'm getting:
query: {
bool: { ... },
filter: undefined,
notFilter: undefined
}
I don't get what I'm doing wrong here. A solution using reduce or map will be preferred.
Use Array.map() to get an array with the contents of each query property, then spread into Object.assign() to combine to a single object:
const queryArr = [{"query":{"filter":{"term":{"search":"complete"}}}},{"query":{"notFilter":{"term":{"search":"failed"}}}},{"query":{"bool":{"term":{"search":"complete"}}}}];
const createQuery = (arr) => ({
query: Object.assign({}, ...queryArr.map(({ query }) => query))
});
console.log(createQuery(queryArr));
To fix your code, initialize the query item, and get the 1st key from each item in the array - arr[i].query)[0]:
const queryArr = [{"query":{"filter":{"term":{"search":"complete"}}}},{"query":{"notFilter":{"term":{"search":"failed"}}}},{"query":{"bool":{"term":{"search":"complete"}}}}]
function createQuery(arr){
const oneQuery = { query: {} };
for(let i = 0; i < arr.length; i++){
if(Object.keys(arr[i].query)[0] === 'bool'){
oneQuery.query.bool = arr[i].query.bool;
}
if(Object.keys(arr[i].query)[0] === 'filter'){
oneQuery.query.filter = arr[i].query.filter;
}
if(Object.keys(arr[i].query)[0] === 'notFilter'){
oneQuery.query.notFilter = arr[i].query.notFilter;
}
}
return oneQuery;
}
console.log(createQuery(queryArr));
You problem seems to be this line
Object.keys(arr[i].query === 'filter')
This evaluates to Object.keys(true) or Object.keys(false)
Use reduce
queryArr.reduce( (acc, c) => (
acc[ Object.keys(c.query)[0] ] = Object.values(c.query)[0], //set the first key and value to accumulator
acc ), //return the accumulator
{}); //initialize accumulator to {}

shallow copy not updating in nested JSON object javascript

I have an nested JSON object like this:
var jsonObj =
{ "level1" :
{ "status" : true,
"level2" : {} // and it has the same format and can go to level3, 4, etc
}
}
What I want to do is simple, I want to get to Level2, and add a new Level3 object to it.
Basically I want to do the following code below, but since the levels are dynamic, I need a function that traverse my object.
obj.Level1.Level2.Level3 = { 'status' : true}
Here's my snippet of code:
function updateStatusForLevel(nestedObj, categoryHierarchy){
// categoryHierarchy that is passed = ['Level1', 'Level2', 'Level3'];
var obj = nestedObj;
angular.forEach(categoryHierarchy, function(value, key){
obj = obj[value];
if (key === categoryHierarchy.length - 1 && angular.isUndefined(obj)){
obj[value] = {}; // I want to add 'Level3' = {}
}
});
obj.status = 'true'; // and finally, update the status
console.info("my original obj is " + JSON.stringify(nestedObj));
}
However seems like I'm missing something. If I do this, my original nestedObj is still the same as what I'm passing in (it's not updated, only the obj object is updated. I believe this should be a really simple code that traverse a nested JSON object. Why is the shallow copy not updating the original object?
Maybe like this
function updateStatusForLevel(nestedObj, categoryHierarchy){
// categoryHierarchy that is passed = ['Level1', 'Level2', 'Level3'];
if(categoryHierarchy.length) {
var shifted = categoryHierarchy.shift();
nestedObj[shifted] = {status: true};
return updateStatusForLevel(starter[shifted], categoryHierarchy);
} else {
return nestedObj;
}
}
Then calling updateStatusForLevel(nestedObj, ['level1', 'level2', 'level3']) will modify nestedObj as
level1: Object
level2: Object
level3: Object
status: true
status: true
status: true
note, this answer is not clever, so better have plnkr or something for better asnwer, but for now, try this in browser dev console
Since you only want to add a value from a certain path of nested objects, then how about creating a generic function that can do this, instead of creating a custom one. I have created a factory named helper, which can be a collection of helper functions that you may want to add later on.
DEMO
JAVASCRIPT
.factory('helper', function() {
var helper = {};
helper.set = function(object, path, value) {
// save reference of an object
var reference = object,
// last key n the path
lastKey;
path = angular.isArray(path)? path: // set as an array if it is an array
angular.isString(path)? path.split('.'): // split the path as an array if it is a string
false; // set to false and do nothing if neither of the conditions above satisfies
// check if path is truthy
if(path) {
// get the last key of the path
lastKey = path.pop();
// reduce the references until all the remaining keys
reference = path.reduce(function(reference, key) {
// check if the current object reference is undefined
if(angular.isUndefined(reference[key])) {
// set current object reference as an object if it is undefined
reference[key] = {};
}
// return the current object reference for the next iteration
return reference[key];
}, reference);
// set the last object reference for the value
reference[lastKey] = value;
}
return object;
};
return helper;
})
.run(function(helper) {
var object1 = {},
object2 = {},
object3 = {},
object4 = {
"level1" : {
"status" : true,
"level2" : {}
}
};
helper.set(object1, 'z.k.v.q', { status: false });
// object1 = { z: { k: { v: { q: { status: false } } } } }
console.log(object1);
helper.set(object2, 'a.e.i.o.u', { status: true });
// object2 = { a: { e: { i: { o: { u: { status: true } } } } } }
console.log(object2);
helper.set(object3, ['hello', 'world'], { status: undefined });
// object3 = { hello: { world: { status: undefined } } }
console.log(object3);
helper.set(object4, 'level1.level2.level3', { status: true });
// object4 = { status: true, level1: { level2: { level3: { status: true } } } }
console.log(object4);
});
Alternatively, you can use lodash for this, and you'd be able to do more object, array and collection manipulation. The lodash function you should be looking for would be _.set()
DEMO
JAVASCRIPT
.service('_', function($window) {
// you can add mixins here
// read more about lodash if you
// want to customize data manipulation
return $window._;
})
.run(function(_) {
var object1 = {},
object2 = {},
object3 = {},
object4 = {
"level1" : {
"status" : true,
"level2" : {}
}
};
_.set(object1, 'z.k.v.q', { status: false });
// object1 = { z: { k: { v: { q: { status: false } } } } }
console.log(object1);
_.set(object2, 'a.e.i.o.u', { status: true });
// object2 = { a: { e: { i: { o: { u: { status: true } } } } } }
console.log(object2);
_.set(object3, ['hello', 'world'], { status: undefined });
// object3 = { hello: { world: { status: undefined } } }
console.log(object3);
_.set(object4, 'level1.level2.level3', { status: true });
// object4 = { status: true, level1: { level2: { level3: { status: true } } } }
console.log(object4);
});

Categories