Lodash - Merge array of nested objects based on parent key - javascript

I have the following array of array nested objects (however, there could be more objects in it!):
[
{
foo: { something: [{}] },
bar: { something: [{}] },
etc: { something: [{}] },
...
},
{
foo: { something: [{}] },
bar: { something: [{}] },
etc: { something: [{}] },
...
},
];
I want to merge them by the key of each object with Lodash, so "foo" goes with "foo" and so on. What is inside something should be merged too.
The result should be:
{
foo: { something: [{}, {}] },
bar: { something: [{}, {}] },
etc: { something: [{}, {}] },
},
There can be duplicates inside something, so I'm guessing uniqBy does not work here. Because the keys are nested inside the array, I feel a bit unsure of whether this needs merge or assign or another method, but since it should be done by dynamic keys, I haven't been able to make this work. I have looked at several similar questions, but no case has fit my scenario.
I have attempted
// does not do anything other than give "undefined" to the key of each object
groupBy(array, (group, key) => group[key])
// I don't know what to use for the iteratee argument, so this just returns undefined
groupBy(map(array, (group, key) => group[key]))

This approach takes the objects within the something arrays, sorts them into consistent key order, and uses a Map() to deduplicate the objects by the JSON representation of the consistent-key-order objects.
const data = [
{
foo: { something: [{a:1}] },
bar: { something: [{b:1, c:9}] },
etc: { something: [{}] },
},
{
foo: { something: [{a:2}] },
bar: { something: [{c:9, b:1}] },
etc: { something: [{}] },
}
];
function dedup(arr) {
return [...new Map(
arr.map(i=>[JSON.stringify(Object.entries(i)
.sort(([a],[b])=>a.localeCompare(b))),i])
).values()]
}
console.log(Object.fromEntries(Object.keys(data[0])
.map(k=>[k, dedup(data.flatMap(i=>i[k].something))])))

Related

How do I properly implment this JavaScript (TypeScript) Tree Recursion function?

I'm trying to code a recursive function but I'm struggling and I was hoping someone could help push me in the right direction. I believe that this would be considered "Tree Recursion".
This is a trivial example to illustrate the data and what I'm trying to do. Obviously, the real data is more complex...
Basically, I start with an array containing a single array of objects like below where prop2 in any of the objects can either be a valid string or an empty string...
[
[
{ prop1: "abc", prop2: "" },
{ prop1: "def", prop2: "one" },
{ prop1: "ghi", prop2: "" }
]
]
My algorithm needs to look at the array above and iterate over the objects. As soon as it encounters an object with an empty string in prop2, it needs to clone the array three times and replace the empty string in that object (and only that object) with three different values (one/two/three) like this...
[
[
{ prop1: "abc", prop2: "one" },
{ prop1: "def", prop2: "one" },
{ prop1: "ghi", prop2: "" }
],
[
{ prop1: "abc", prop2: "two" },
{ prop1: "def", prop2: "one" },
{ prop1: "ghi", prop2: "" }
],
[
{ prop1: "abc", prop2: "three" },
{ prop1: "def", prop2: "one" },
{ prop1: "ghi", prop2: "" }
]
]
Then the algorithm starts again, except that the the input is this new array containing three arrays.
So on the second iteration, each of the three arrays will get cloned three times and the empty string will get replaced in the same way.
The end result for this simple example would be an array of nine arrays.
If the array had more objects with empty prop2 values, there would be more iterations.
Basically, I'm taking an array of objects where some of the props are empty strings and "expanding" that particular prop value to every permutation of "one"/"two"/"three"
I know that this is an ideal problem for recursion but I'm having trouble figuring out the code.
I think that the "base case" would probably be where I have an array of objects and none of the objects have properties with empty strings. That case would return that array.
I don't know what the other case would look like other than calling the same function three times with the three newly created variants. I also don't know what this case should be returning.
I'm having trouble finding reference examples online that are similar to what I'm trying to do.
EDIT: Looking at the recursive responses, even though they all do work, it's obvious that a recursive solution wasn't as straightforward as I thought it would be. The non-recursive answer is actually the best answer.
I suggest this non-reclusive solution it serves your end-result requirements:
const myTree = [
[
{ prop1: "abc", prop2: "" },
{ prop1: "def", prop2: "one" },
{ prop1: "ghi", prop2: "" }
]
];
let nodesWithEmptyStrings = myTree.filter(n=> !!n.find(l=> l.prop2===""));
while(nodesWithEmptyStrings.length > 0) {
nodesWithEmptyStrings.forEach(n => {
const firstEmptyStringLeaveIndex = n.findIndex(l=> l.prop2==="");
n[firstEmptyStringLeaveIndex].prop2 = "one";
const copy1 = JSON.parse(JSON.stringify(n));
copy1[firstEmptyStringLeaveIndex].prop2 = "two";
myTree.push(copy1);
const copy2 = JSON.parse(JSON.stringify(n));
copy2[firstEmptyStringLeaveIndex].prop2 = "three";
myTree.push(copy2);
});
nodesWithEmptyStrings = myTree.filter(n=> !!n.find(l=> l.prop2===""));
}
document.getElementById('result').innerText = JSON.stringify(myTree, null, 2);
<pre id="result"></pre>
Yes, you can do this using recursion.
The basic principle is to modify the array and then check if it needs to be modified some more, if that is the case, return the result of calling the function with the new array.
Here is an example:
const fillers = ['one', 'two', 'three'];
const propToCheck = 'prop2';
function recursion(arr) {
const mod = arr.reduce((a, c) => {
const found = c.find(v => !v[propToCheck]);
if (found) {
const tmp = c.filter(v => v !== found);
return [...a, ...fillers.map(filler => [...tmp, { ...found, [propToCheck]: filler }])];
}
return [...a, c];
}, []);
const notDone = mod.some(v => v.some(o => !o[propToCheck]))
if (notDone) {
return recursion(mod);
}
return mod;
}
const result = recursion([
[
{ prop1: "abc", prop2: "" },
{ prop1: "def", prop2: "one" },
{ prop1: "ghi", prop2: "" }
]
]);
console.log(result);
I don't know if this is a problem that intuitively I would want to solve with recursion, but this general function that takes both substitutions and what key to check for the empty string as arguments would work (using es6+ syntax and functionality with a lot of destructuring):
const substitute = (data, index, keyToCheck, substitutes) => {
const indexOfObjectWithEmptyKeyToCheck = data[index].findIndex(obj => obj[keyToCheck] === "")
if(indexOfObjectWithEmptyKeyToCheck === -1) {
if(index === data.length - 1)
return data
else
return substitute(data, index + 1, keyToCheck, substitutes)
}
else {
return substitute(
[
...data.slice(0, index),
...(substitutes.map(
substitute => [
...data[index].slice(0, indexOfObjectWithEmptyKeyToCheck),
{ ...data[index][indexOfObjectWithEmptyKeyToCheck], [keyToCheck]: substitute },
...data[index].slice(indexOfObjectWithEmptyKeyToCheck + 1)
]
)),
...data.slice(index + 1)
],
index,
keyToCheck,
substitutes
)
}
}
const SUBSTITUTES = ["one", "two", "three"];
const result = substitute(
[
[
{ prop1: "abc", prop2: "" },
{ prop1: "def", prop2: "one" },
{ prop1: "ghi", prop2: "" }
]
],
0,
"prop2",
SUBSTITUTES
)
console.log(result)
console.log("Size of result: " + result.length)
Basically, we iterate through the array, only incrementing the index if the current array has no object where the key to check is the empty string, otherwise we do replacements as necessary and recurse on the same index. The base case is when the key to check is not the empty string and the index is the last index of the input array.
The Typescript part I left to you as an exercise as I don't believe typing the input data is the big problem here.

Merging two Javascript objects with similar keys

I need to merge two objects(obj1, obj2), that happen to share similar keys.
obj1 = {
0:{"Example1": "Example1"},
1:{"Example1": "Example1"},
2:{"Example1": "Example1"}
}
obj2 = {
0:{"Example2": "Example2"},
1:{"Example2": "Example2"},
2:{"Example2": "Example2"}
}
Expected result:
obj3 = {
0:{"Example1": "Example1"},
1:{"Example1": "Example1"},
2:{"Example1": "Example1"},
3:{"Example2": "Example2"},
4:{"Example2": "Example2"},
5:{"Example2": "Example2"},
}
Usual approach when merging two objects:
const obj3 = Object.assign({}, obj1, obj2);
Problem: They do share many keys, as such, in obj3, only the contents of obj2 are going to be found.
My approach:
let obj3= Object.assign({}, obj1);
for(let i=0; i<obj2.length; i++) {
obj3[obj3.length + i] = obj2[i];
}
Question: Is there, another more elegant, pre-defined way of merging two objects with similar keys?
Although I still think obj1 and obj2 should be arrays...
const obj1 = {
0:{"Example1": "Example1"},
1:{"Example1": "Example1"},
2:{"Example1": "Example1"}
};
const obj2 = {
0:{"Example2": "Example2"},
1:{"Example2": "Example2"},
2:{"Example2": "Example2"}
}
const result = Object.fromEntries(
Object.values(obj1) // get the values of the first object
.concat(Object.values(obj2)) // get the values of the second object and add them to the values of the first
.map((value, index) => [ index, value ]) // re-index them
// or if you need actual copies of the "inner" objects
/*
.map((value, index) => [
index,
Object.assign({}, value)
])
*/
);
console.log(result);
Is this "more elegant". Maybe...
The objects in your code are key-value pairs rather than a simple list (array).
From the looks of it there are only two possible scenarios:
Your objects have a meaningful, unique key associated to them (for example, this very question on stackoverflow has key 69316153 – look at the URL bar). In this case, you really can't merge the two as they have conflicting keys. Think if there was another question on this website with the same ID as this one!
The keys are not meaningful and you're happy with the same object being re-assigned a different key. In this case the correct data structure to use is arrays (obj1 = [{"Example": "..."}, {"Example": "..."}]).
If the latter is your situation, this code will work:
const obj3 = Object.values(obj1).concat(Object.values(obj2))
Object.values(obj) returns an array of values, discarding all of the keys.
Let's say we have:
const obj1 = {
1: { Name: "Apple" },
2: { Name: "Watermelon" },
};
const obj2 = {
2: { Name: "Pear" },
5: { Name: "Tomato" }
};
Object.values(obj1) will return [{ Name: "Apple" }, { Name: "Watermelon" }], while Object.values(obj2) will return [{ Name: "Pear" }, { Name: "Tomato" }].
With const obj3 = Object.values(obj1).concat(Object.values(obj2)) you will end up with:
obj3 = [
{ Name: "Apple" },
{ Name: "Watermelon" },
{ Name: "Pear" },
{ Name: "Tomato" }
];
Because you have key-value maps and not arrays, you can't just combine the objects. Your only way would be to iterate through all the keys and add them to the final result.
E.g. something along the lines:
const obj1 = {
0:{"Example1": "Example1"},
1:{"Example1": "Example1"},
2:{"Example1": "Example1"}
};
const obj2 = {
0:{"Example2": "Example2"},
1:{"Example2": "Example2"},
2:{"Example2": "Example2"}
};
function merge(...args) {
return Object.fromEntries( // Create entries
args // From all arguments
.flatMap(o => Object.values(o)) // Get values (discard keys)
.map((element, index) => [ index, element ]) // Remap them
);
}
console.log(merge(obj1, obj2));
// Will output
// {
// 0: { Example1: "Example1" },
// 1: { Example1: "Example1" },
// 2: { Example1: "Example1" },
// 3: { Example2: "Example2" },
// 4: { Example2: "Example2" },
// 5: { Example2: "Example2" }
// }

React: Is there a way to refer to another object in the same component's constructor?

I have 2 objects in this.state of the same component:
Object 1 is a set of key: value pairs.
Object 2 is an array of objects. Each inner object contains again pairs of key: value, but one of the pairs should refer to value stored in the first object:
this.state = {
Object 1: {
key 1: 1,
key 2: 2,
key 3: 3
},
Object 2: [
{
key 1: "...",
key 2: value of e.g. Object 1 - key 3
},
{
...
}
]
};
Is there a way to refer directly to the first object from the second? The only other way that came on my mind was passing the first object as props to another class, but that would be a bit impractical as this is the top element that would ideally contain both objects.
I don't want to store all values in the same object as both objects are rendered in different elements.
As state should be predictable and serializable IMHO is a better solution to hydrate your object at runtime:
this.state = {
Object1: {
key1: 1,
key2: 2,
key3: 3
},
Object2: [
{
key1: "...",
},
]
};
// Hydrate method
getObject2 = () => this.state.Object2.map(
obj => ({...obj, key2: this.state.Object1.key3 })
)
console.log(getObject2());
// Simulating setState()
this.state.Object1.key3 = "Test"
console.log(getObject2());
You might want to try something like this:
this.state = {}
this.state.Object_1 = {
key_1: 1,
key_2: 2,
key_3: "this is it",
}
this.state.Object_2 = [
{
key_1: "...",
key_2: this.state.Object_1.key_3,
},
{
},
];
console.log(this.state);

Given an object how can I get the name used to describe it?

I have a JSON object(mainObj) which in turn has objects (say obj1, obj2, obj3). What I am trying to achieve is when I check for a condition iterating through every obj in the mainObj and if it holds true, I want to add only the name of that obj in an array of String. Something like,
for(obj in mainObj){
if(obj holds condition){
add the descriptor of the obj (in string format) to an array (not the entire obj)
}
You can use Object.keys() to iterate over your object keys, then use Array.filter() to filter the keys, here I am checking if the inner objects have a property show and if this property is truthy:
const mainObj = {
obj1: { show: true, a: 1 },
obj2: { show: false, a: 2 },
obj3: { a: 3 },
obj4: { show: true, b: 1 }
};
const result = Object.keys(mainObj).filter(key => mainObj[key].show);
console.log(result);
If you want to use a for-in loop, you have to make sure the property is part of the object and is not inherited from its protype chain using Object.hasOwnProperty():
const mainObj = {
obj1: { show: true, a: 1 },
obj2: { show: false, a: 2 },
obj3: { a: 3 },
obj4: { show: true, b: 1 }
};
const result = [];
for (const prop in mainObj) {
if (mainObj.hasOwnProperty(prop) && mainObj[prop].show) {
result.push(prop);
}
}
console.log(result);

Javascript Nested Literal to string

I am looking for a technique to run over a object of nested properties and wish to join the properties'.
This is the object I'd like to join:
var array = {
prop1: {
foo: function() {
// Your code here
}
},
prop2: {
bar1: 'some value',
bar2: 'some other value'
}
};
The result should look like this:
[
[ 'prop1', 'foo' ],
[ 'prop2', 'bar1' ],
[ 'prop2', 'bar2' ]
]
Then I'd like to join the array to strings formatted like this:
prop1.foo
prop2.bar1
prop2.bar2
Any tips?
EDIT: Forgot to say it should work for deeper arrays too.
Something along these lines? http://jsfiddle.net/X2X2b/
var array = {
prop1: {
foo: function() {
// Your code here
}
},
prop2: {
bar1: 'some value',
bar2: 'some other value'
}
};
var newA = [],
newB = [];
for ( var obj in array ) {
for (var inObj in array[obj]) {
newA.push([obj, inObj]);
newB.push(obj + '.' + inObj);
}
}
console.log(newA);
console.log(newB);
This is quite a different problem now that you have specified that it needs to support arbitrary depths. In order to solve it we need to use recursion and we need to use a second recursive parameter which keeps track of where we are in the nested hierarchy.
function objectPropertiesToArrays(obj, prepend) {
// result will store the final list of arrays
var result = [];
// test to see if this is a valid object (code defensively)
if(obj != null && obj.constructor === Object) {
for (var propertyName in obj) {
var property = obj[propertyName],
// clone prepend instantiate a new array
list = (prepend || []).slice(0);
// add the property name to the list
list.push(propertyName);
// if it isn't a nested object, we're done
if (property.constructor !== Object) {
result.push(list);
// if it is a nested object, recurse
} else {
// recurse and append the resulting arrays to our list
result = result.concat(objectPropertiesToArrays(property, list));
}
}
}
return result;
}
Example:
var obj = {
prop1: {
foo: function() { }
},
prop2: {
bar1: 'some value',
bar2: 'some other value'
},
prop3: {
x: {
y: [],
z: 'test'
},
erg: 'yar'
}
};
objectPropertiesToArrays(obj);
Returns
[
["prop1", "foo"],
["prop2", "bar1"],
["prop2", "bar2"],
["prop3", "x", "y"],
["prop3", "x", "z"],
["prop3", "erg"]
]

Categories

Resources