If two different objects have same key name, merge their value - javascript

let obj1 = { names: ["Zack","Cody"]};
let obj2 = { names: ["John","Jake"] };
Results in: obj1 = { names: ["Zack","Cody","John","Jake"]}
What I have tried:
if (Object.keys(obj1) == Object.keys(obj2)) {
Object.values(obj1) = [...Object.values(obj1), ...Object.values(obj2)];
}

Iterate through the keys in obj1, and for every one that exists in obj2, merge its values into obj1:
let obj1 = { names: ["Zack","Cody"]};
let obj2 = { names: ["John","Jake"] };
Object.keys(obj1).forEach(function(k) {
if ( obj2[k] ) { // obj2 contains this key
obj1[k].push( ...obj2[k] ) // Add the values from obj2's key to obj1's key
}
});

You can just iterate over each objects properties and add them to the new object if that property does not exist. To deal with merges where the same key exists in other objects we can define a merge strategy function that will resolve the conflict.
let obj1 = { names: ["Zack","Cody"]};
let obj2 = { names: ["John","Jake"] };
function mergeObjs(objects, mergeStrategy) {
mergeStrategy = mergeStrategy || ((oldV, newV) => newV);
const result = {};
for (let ob of objects) {
for (let [k, v] of Object.entries(ob)) {
const oldV = result[k];
result[k] = (oldV == undefined) ? v: mergeStrategy(oldV, v);
}
}
return result;
}
console.log(mergeObjs([obj1, obj2], (oldV, newV) => oldV.concat(newV)));
the function only allows one strategy per merge, which might be a bit limiting for more complex cases but for simple ones like this its ok.

You could concat the value by wrapping into array element and flatten (or concat as #ggorlen suggested in the comment) it
let obj1 = { names: ["Zack", "Cody"] }
let obj2 = { names: ["John", "Jake"] }
const mergeObj = (obj1Raw = {}, obj2Raw = {}) => {
// shallow clone for demo only
let obj1 = { ...obj1Raw }
let obj2 = { ...obj2Raw }
for (const prop in obj2) {
if (obj1.hasOwnProperty(prop)) {
obj1[prop] = [obj1[prop], obj2[prop]].flat()
} else {
obj1[prop] = obj2[prop]
}
}
return obj1
}
console.log(mergeObj(obj1, obj2))

You can try lodash.merge, powerful function to merge multiple objects
var object = {
'a': [{ 'b': 2 }, { 'd': 4 }]
};
var other = {
'a': [{ 'c': 3 }, { 'e': 5 }]
};
_.merge(object, other);
// => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }

Here's one-liner using Object.entries, Object.fromEntries and Array#map, assuming the properties share the same key name are always arrays:
let obj1 = { names: ["Zack","Cody"] };
let obj2 = { names: ["John","Jake"] };
const result = {
...obj1,
...Object.fromEntries(Object.entries(obj2).map(([k, v]) => [
k, obj1[k] ? [...obj1[k], ...v] : v
]))
};
console.log(result);

In your code, you were comparing two arrays - Object.keys(obj1) and Object.keys(obj2). This condition would never be true because no two arrays are equal because they have reference to different memory location.
To deal with this, you can compare their contents. This is what I am doing in the below snippet -
let obj1 = { names: ["Zack","Cody"]};
let obj2 = { names: ["John","Jake"]};
if (Object.keys(obj1)[0] == Object.keys(obj2)[0]) {
Object.values(obj1)[0].push(...Object.values(obj2)[0]);
console.log(obj1.names);
}
I am only comparing the first keys in both objects in the above snippet. If you like to do this for every key in both objects, then you can use a loop with indexes replacing the [0].
Note - If you are going to use the above approach, then the order of keys in both objects matters.

Related

Javascript - Access a nested property on an object from an array of strings

I have an object like this
{
metadata: {
correlationId: 'b24e9f21-6977-4553-abc7-416f8ed2da2d',
createdDateTime: '2021-06-15T16:46:24.247Z'
}
}
and I have an array of the properties I wanna access
[metadata, correlationId]
how can I dynamically access the property on the object?
like
keys.forEach((key) => {
object[key][key2] ???
})
it needs to be dynamic since I don't know how deep we need to access the object
Here is a solution without recursion:
const myObj = {
a: {
b: {
c: "I'm the target"
}
}
}
const keys = ['a', 'b', 'c'];
let result = myObj;
for (const key of keys) {
result = result[key];
}
console.log(result);
Or with recursion:
const finder = (obj, keys, index = 0) => {
const result = obj[keys[index++]];
if (!result) {
return obj;
}
return finder(result, keys, index);
}
console.log(finder(myObj, keys));
This is pretty similar to Accessing nested JavaScript objects and arrays by string path, except with one fewer step - you already have the keys you need in the form of an array. .reduce and access the next nested value in each iteration.
const obj = {
metadata: {
correlationId: 'b24e9f21-6977-4553-abc7-416f8ed2da2d',
createdDateTime: '2021-06-15T16:46:24.247Z'
}
};
const keys = ['metadata', 'correlationId'];
const result = keys.reduce((a, key) => a[key], obj);
console.log(result);
This is my idea to solve your problem. Tell me, if is ok for you.
let x = {
metadata: {
correlationId: 'b24e9f21-6977-4553-abc7-416f8ed2da2d',
createdDateTime: '2021-06-15T16:46:24.247Z'
}
}
let fun = x => typeof x === 'string' ? console.log(x) : Object.keys(x).map( y => fun(x[y]));
fun(x);

Get equals values from two nested objects

Suppose I have two objects like these ones:
const obj1 = {
ch1: {
as: ['a', 'b'],
ns: ['1']
}
ch2: {
ss: ['#', '*', '+'],
ts: ['2', '3']
}
}
const obj2 = {
ch1: {
as: ['a', 'g'],
}
ch2: {
ss: ['#', '-', '+'],
ts: ['1', '5']
}
}
const result = findSimilarities(obj1, obj2)
should returns:
[
'a' // because obj1.ch1.as and obj2.ch1.as contain 'a'
'#' // because obj1.ch2.ss and obj2.ch2.ss contain '#'
'+' // because obj1.ch2.ss and obj2.ch2.ss contain '+'
]
I can define Level 1 as ch1 or ch2, Level 2 as as, ns, ss or ts and Level 3 the strings inside the arrays.
So what I'm interested in are che value at level 3 that the two objects have in common.
Honestly I didn't know how to start..
What I would do is start with a function, function getMatches(obj1, obj2) { ....
Iterate over the first-level keys of obj1 with Object.keys(obj1).foreach( key2 => .... (2 to represent what you call level 2)
Then, iterate over THOSE keys, Object.keys(obj1[key2]).foreach(key3 > ...
So now you have an array that you can access via obj1[key2][key3], and you can access the array to compare against via obj2[key2][key3]. So now you just have to iterate every value in obj1[key2][key3] and check if it's in obj2[key2][key3]. If a key is in both, smash it in an array, and then return the array at the end of the function
[edit] you may have to use some sort of check, or the optional chaining operator ?., to access the keys on obj2, in case there's a key on obj1 that isn't on obj2
Here is what I would recommend:
Create a function lets call it function compare(obj1, obj2), then create two Object.values variables followed by an empty array var equivalent = [];
After that have a forEach function that searches both objects, and the final product should look something like:
function compare(obj1, obj2) {
var values1 = Object.values(obj1);
var values2 = Object.values(obj2);
var equivalent = [];
var keys = Object.keys(obj1);
keys.forEach(k => {
if (obj1.hasOwnProperty(k) && obj2.hasOwnProperty(k)) {
if (obj1[k] === obj2[k]) {
equivalent.push(obj1[k]);
}
}
});
console.log(equivalent);
}
compare(obj1, obj2);
I also want to leave my solution here. It will work properly on the data structures like you described above. On whatever object depth and if arrays will contains only primitive values.
function findSimilarities(obj1, obj2) {
if (obj1 instanceof Array && obj2 instanceof Array) {
return findSimilarArrayValues(obj1, obj2)
}
if (!obj1 instanceof Object || !obj1 instanceof Object) {
return [];
}
const similarKeys = findSimilarObjectKeys(obj1, obj2);
return similarKeys.map(key => findSimilarities(obj1[key], obj2[key])).flat()
}
function findSimilarObjectKeys(obj1, obj2) {
return Object.keys(obj1).filter(key => obj2.hasOwnProperty(key));
}
function findSimilarArrayValues(arr1, arr2) {
return arr1.filter(item => arr2.includes(item));
}
Try to start with Object.keys, Object.entries and Object.values.
Good luck!
Use Object.keys to find all keys in first object, Object.protoype.hasOwnProperty() to check if these keys exist in object 2
Repeat that for levels 1 and 2
Extract the values common to level 3 arrays using Array.prototype.filter() on the array from first object to keep only items found in its match from second object:
function findSimilarities(x, y) {
const res = [];
for (const k1 of Object.keys(x)) { //Level 1
if (y.hasOwnProperty(k1)) {
const xo1 = x[k1];
const yo1 = y[k1];
for (const k2 of Object.keys(xo1)) { //Level 2
if (yo1.hasOwnProperty(k2)) {
const xo2 = xo1[k2];
const yo2 = yo1[k2];
if (Array.isArray(xo2) && Array.isArray(yo2)) { //Level 3 are arrays
const dupXo2 = xo2.filter(el => yo2.includes(el));
res.push(...dupXo2);
}
}
}
}
}
return res;
}
const obj1 = {
ch1: {
as: ['a', 'b'],
ns: ['1']
},
ch2: {
ss: ['#', '*', '+'],
ts: ['2', '3']
}
};
const obj2 = {
ch1: {
as: ['a', 'g'],
},
ch2: {
ss: ['#', '-', '+'],
ts: ['1', '5']
}
};
const result = findSimilarities(obj1, obj2);
console.log(result);

Aggregate same key values into an array and avoid undefined

I am trying to aggregate the same key values into an array by value.
so for example I have an array of objects, like so
const data = [{foo: true},{foo: false},{bar: true},{buzz: false}]
when they get aggregated the array transforms into
[
foo: {true: [{foo: true}], false: [{foo: false}]},
bar: {true: [{bar: true}]},
buzz: {false: [{buzz: false}]}
]
the array entries is the original object.
Now I know the keys that I want to group by..
they are foo, bar, buzz and fizz.
But fizz is not part of the original array, so the return is undefined, like so
[
foo: {true:[{foo: true}], false: [{foo: false}]},
bar: {true: [{bar: true}]},
buzz: {false: A[{buzz: false}]}
fizz: {undefined: [{foo: true},{foo: false},{bar: true},{buzz: false}]}
],
how do I reduce the original array without including the fizz value that is undefined?
code here:
let v = [];
let types = ['foo', 'bar', 'buzz', 'fizz' ]
for (let x = 0; x < types.length; x++) {
let data = data.reduce((acc, i) => {
if (!acc[i[types[x]]]) {
acc[i[types[x]]] = [i]
}
else if (Array.isArray(acc[i[types[x]]])) {
acc[i[types[x]]].push(i);
}
else if (typeof acc[i[types[x]]] === 'object') {
acc[i[types[x]]] = [acc[i[types[x]]]]
acc[i[types[x]]].push(i)
}
return acc;
}, {})
v.push({ [types[x]]: data });
}
return v;
You were close, you just need to check if the property you were adding was undefined before adding. You can also check if the reduced object has any properties before adding to the result object.
Note that this may not be the most efficient way of doing it, but sometimes it's better to understand the code than it is to have highly efficient code.
const data = [{
foo: true
}, {
foo: false
}, {
bar: true
}, {
buzz: false
}];
let v = [];
let types = ['foo', 'bar', 'buzz', 'fizz']
for (let x = 0; x < types.length; x++) {
let reduced = data.reduce((acc, i) => {
// /* Added this type check */
if (!acc[i[types[x]]] && typeof i[types[x]] !== 'undefined') {
acc[i[types[x]]] = [i]
} else if (Array.isArray(acc[i[types[x]]])) {
acc[i[types[x]]].push(i);
} else if (typeof acc[i[types[x]]] === 'object') {
acc[i[types[x]]] = [acc[i[types[x]]]]
acc[i[types[x]]].push(i)
}
return acc;
}, {});
// Doesn't add a property for the type if there are no data
if (Object.keys(reduced).length) {
v.push({
[types[x]]: reduced
});
}
}
console.log(v);
Have a look at how Array.prototype.reduce works. It might be the right method to build your approach upon.
A generic way of solving the OP's problem was to iterate the provided data array. For each item one would extract its key and value. In case the item's key is listed (included) in another provided types array, one would continue creating a new data structure and collecting the currently processed item within the latter.
One does not want to iterate the types array for it will cause a unnecessarily complex lookup for the data items, each time a type item is going to be processed.
Thus a generically working (better code reuse) reduce method might be the best solution to the OP's problem ...
const sampleDataList = [
{ foo: true },
{ foo: false },
{ bar: true },
{ baz: false },
{ buzz: false },
{ baz: false },
{ bar: true }
];
// foo: {true: [{foo: true}], false: [{foo: false}]},
// bar: {true: [{bar: true}]},
// buzz: {false: [{buzz: false}]}
function collectItemIntoInclusiveKeyValueGroup(collector, item) {
const { inclusiveKeyList, index } = collector;
const firstItemEntry = Object.entries(item)[0];
const key = firstItemEntry[0];
const isProceedCollecting = ( // proceed with collecting ...
//
!Array.isArray(inclusiveKeyList) // - either for no given list
|| inclusiveKeyList.includes(key) // - or if item key is listed.
);
if (isProceedCollecting) {
let keyGroup = index[key]; // access the group identified
if (!keyGroup) { // by an item's key, ... or ...
// ...create it in case ...
keyGroup = index[key] = {}; // ...it did not yet exist.
}
const valueLabel = String(firstItemEntry[1]); // item value as key.
let valueGroupList = keyGroup[valueLabel]; // acces the group list
if (!valueGroupList) { // identified by an item's
// value, ...or create it in
valueGroupList = keyGroup[valueLabel] = []; // case it did not yet exist.
}
// push original reference into a grouped
// key value list, as required by the OP.
valueGroupList.push(item);
}
return collector;
}
console.log(
"'foo', 'bar', 'buzz' and 'fizz' only :",
sampleDataList.reduce(collectItemIntoInclusiveKeyValueGroup, {
inclusiveKeyList: ['foo', 'bar', 'buzz', 'fizz'],
index: {}
}).index
);
console.log(
"'foo', 'bar' and 'baz' only :",
sampleDataList.reduce(collectItemIntoInclusiveKeyValueGroup, {
inclusiveKeyList: ['foo', 'bar', 'baz'],
index: {}
}).index
);
console.log(
"all available keys :",
sampleDataList.reduce(collectItemIntoInclusiveKeyValueGroup, {
index: {}
}).index
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Try something like:
const data = [{foo: true},{foo: false},{bar: true},{buzz: false}];
let v = [];
let types = ['foo', 'bar', 'buzz', 'fizz' ];
for (let x = 0; x < types.length; x++) {
let filteredlist = data.filter(function (d) {
return Object.keys(d)[0] == types[x];
});
let isTrue = 0;
let isFalse = 0;
if (filteredlist.length > 0) {
for (let i = 0; i < filteredlist.length; i++) {
let trueOrfalse = eval("filteredlist[i]." + types[x]);
if (trueOrfalse) {
isTrue++;
} else {
isFalse++;
}
}
v.push(types[x], {true: isTrue, false: isFalse});
}
}
console.log(v);
Assuming you only want to count the number of each key (e.g. true or false) you can use the following code.
I've written this as a function named 'aggregate' so that it can be called multiple times with different arguments.
const initialData = [{foo: true},{foo: true},{foo: false},{bar: true},{buzz: false}];
const types = ['foo', 'bar', 'buzz', 'fizz'];
const aggregate = (data, types) => {
const result = {};
data.forEach(item => {
// Extract key & value from object
// Note: use index 0 because each object in your example only has a single key
const [key, value] = Object.entries(item)[0];
// Check if result already contains this key
if (result[key]) {
if (result[key][value]) {
// If value already exists, append one
result[key][value]++;
} else {
// Create new key and instantiate with value 1
result[key][value] = 1;
}
} else {
// If result doesn't contain key, instantiate with value 1
result[key] = { [value]: 1 };
}
});
return result;
};
console.log(aggregate(initialData, types));
This will output the following (note I've added another {foo: true} to your initialData array for testing).
The output should also be an object (not array) so that each key directly relates to its corresponding value, as opposed to an Array which will simply place the value as the next item in the Array (without explicitly linking the two).
{
foo: { true: 2, false: 1 },
bar: { true: 1 },
buzz: { false: 1 }
}

change nested array object to objects by value type in javascript

How to modify the array object to object in javascript.
I have object obj1, change the value of details key to new object javascript
function newObj(obj1){
return Object.assign({}, ...obj1.map(e=>(e.details)));
}
var r1= this.newObj(obj1)
var obj1 = [
{
details: {
"info":["stocks","finance",""],
"sales":["analytics"]
}
}
]
var obj2 = [
{
details: {
"city":{"SG"}
}
}
]
Expected Output
//for obj1 (show only first value of array)
{
stocks: "stocks",
analytics: "analytics"
}
//obj2
{
SG: "SG"
}
The object in obj2 should have keys.
To solve this problem, we need to keep an object/map to fill it with the values to be returned by the function. Therefore, you need to iterate over each detail element and get the values of each property. Then, we can check whether it's an array or object and fill the map accordingly:
var obj1 = [
{
details: {
"info":["stocks","finance",""],
"sales":["analytics"]
}
}
]
var obj2 = [
{
details: {
"city":{name:"SG"}
}
}
]
function newObj(obj1){
let map = {};
obj1.forEach(e=>{
let details = e.details;
Object.values(details).forEach(value => {
if(Array.isArray(value) && value.length>0)
map[value[0]]=value[0];
else if(typeof value === 'object')
Object.values(value).forEach(val => { map[val]=val; });
})
});
return map;
}
var r1= this.newObj(obj1)
console.log(r1);
var r2 = this.newObj(obj2)
console.log(r2);

Access dynamic nested key in JS object

I have an array like ['animals', 'cats', 'cute', 'fast', 'small', ...], and want to access nested keys of the object like
let object = {
one: {
two: {
three: {
// and so on
}
}
}
}
Usually I would write object['animals']['cats']['cute']['fast']['small']..
The problem is that keys and the number of levels are dynamic (so I can get objects with 2 nested levels or 50), so I have no idea how it can be done
Thanks in advance for any help
Iterate over the array of keys with .reduce, where the accumulator is the current nested object:
let object = {
one: {
two: {
three: {
prop: 'val'
}
}
}
};
const props = ['one', 'two', 'three', 'prop'];
const nestedVal = props.reduce((a, prop) => a[prop], object);
console.log(nestedVal);
To assign a value at the same point, first pop off the last key, use the same reduce trick to get to the last object, and assign to the property at the last key with bracket notation:
let object = {
one: {
two: {
three: {
prop: 'val'
}
}
}
};
const props = ['one', 'two', 'three', 'prop'];
const lastKey = props.pop();
const nestedObj = props.reduce((a, prop) => a[prop], object);
nestedObj[lastKey] = 'newVal';
console.log(object);

Categories