Best way to merge two objects on a matching key-value pair - javascript

I'm having trouble making two objects into one on a matching key.
I have two objects coming from 2 apis and there is one matching key in the objects.
I want to iterate over them and if the storeId matches in both objects, I want to merge the two together as seen in the perfectObject.
I have tried the spread operator, Object.assign, for...in loop instead of the for loop seen here, but found close to none success.
Thanks for the help!
const logistics = [{
logisticsId: "L5E69E26D8FCAE",
storeId: 409388,
logisticsDate: "2020-03-12T07:19:09.000Z",
}, ];
const stores = [{
storeId: 409388,
ka: 0,
country: "ru",
name: "test",
city: "Moscow",
cxw: 1,
cx: 1,
plz: 22448,
}, ];
const perfetObject = {
storeId: 409388,
ka: 0,
country: "ru",
name: "test",
city: "Moscow",
cxw: 1,
cx: 1,
plz: 22448,
"storeId": 409388,
logisticsId: "L5E69E26D8FCAE",
storeId: 409388,
logisticsDate: "2020-03-12T07:19:09.000Z",
};
"logisticsId": "L5E69E26D8FCAE",
let d = {};
}
for (let i = 0; i < logistics.length; ++i) {
for (let k = 0; k < stores.length; ++k) {
if (logistics.storeId === stores.storeId) {
d = {
...stores.name,
...stores.city,
...logistics.logisticsId,
};
}
}
let d = {}
console.log(d);

Leaving aside the nested for loop (there are better data structures for you to take advantage of that make this unnecessary), the easiest method would be to use Object.assign() or the spread operator, especially if the key names are guaranteed to never conflict.
const logistics = [{
logisticsId: "L5E69E26D8FCAE",
storeId: 409388,
logisticsDate: "2020-03-12T07:19:09.000Z",
}, ];
const stores = [{
storeId: 409388,
ka: 0,
country: "ru",
name: "test",
city: "Moscow",
cxw: 1,
cx: 1,
plz: 22448,
}, ];
for (let i = 0; i < logistics.length; ++i) {
for (let k = 0; k < stores.length; ++k) {
if (logistics[i].storeId === stores[k].storeId) {
console.log(Object.assign({}, logistics[i], stores[k]));
}
}
}
This assumes all store IDs are valid (e.g. have the expected store data) and all logistics elements have a valid store defined.

This is a working example answer from a reddit user u/albedoa:
const storesByStoreId = stores.reduce(
(obj, store) => {
if (!obj.hasOwnProperty(store.storeId)) {
obj[store.storeId] = store;
}
return obj;
},
{}
);
const perfectArray = logistics.map(logistic => ({
...storesByStoreId[logistic.storeId],
...logistic
}));
This way, it combines the two as supposed to, or returns the object itself if there is no match.

Related

Insert all properties from an object within an array to another object in array using JS/TS

I have been looking a simple way to copy/insert/move properties in an object within an array to another object. I came up with a basic logic which does the job perfectly but am not satisfied with this. There has to be a better way, any help here?
var first = [
{
"AGREE_EFF_DATE__0": "02-Aug-2018",
"AGREE_TERM_DATE__0": "30-Apr-2021",
"AGREE_IND__0": "P1",
"P_DBAR_IND__0": "N",
"AGREE_EFF_DATE__1": "01-May-2021",
"AGREE_TERM_DATE__1": null,
"AGREE_IND__1": "NP",
"P_DBAR_IND__1": "N",
"PROVIDER_SPECIALITY__0": "PSYCHOLOGY, CLINICAL",
"PROVIDER_SPECIALITY_CODE__0": "CK"
}
];
var second = [
{
"STATUS": "ACTIVE",
"MEDICARE_NUMBER" : 12345
}
];
for(let i = 0; i < second.length; i++) {
var first_keys = Object.keys(first[i]);
var first_values = Object.values(first[i]);
for(let j = 0; j < first_keys.length; j++) {
second[i][first_keys[j]] = first_values[j];
}
}
console.log(second);
//Output-
[
{
STATUS: 'ACTIVE',
MEDICARE_NUMBER: 12345,
AGREE_EFF_DATE__0: '02-Aug-2018',
AGREE_TERM_DATE__0: '30-Apr-2021',
AGREE_IND__0: 'P1',
P_DBAR_IND__0: 'N',
AGREE_EFF_DATE__1: '01-May-2021',
AGREE_TERM_DATE__1: null,
AGREE_IND__1: 'NP',
P_DBAR_IND__1: 'N',
PROVIDER_SPECIALITY__0: 'PSYCHOLOGY, CLINICAL',
PROVIDER_SPECIALITY_CODE__0: 'CK'
}
]
When possible, you should prefer iteration to manually indexed loops. This means arr.map() or arr.forEach() or arr.reduce(), to name a few.
Also, You can use an object spread to easily merge objects together.
Putting those together, you can reduce this logic to:
const result = first.map((firstObj, i) => ({ ...firstObj, ...second[i] }))
Here we map() over all members of first, which returns a new array where each member is the result of the function. This function takes the array member as the first argument, and the index of that member as the second argument. Then we can use that index to find the corresponding item in the second array.
Then you just spread both objects into a new object to assemble the final result.
var first = [
{ a: 1, b: 2 },
{ a: 4, b: 5 },
];
var second = [
{ c: 3 },
{ c: 6 },
];
const result = first.map((firstObj, i) => ({ ...firstObj, ...second[i] }))
console.log(result)
Which is all perfectly valid typescript as well.
NOTE: there is one difference between my code any yours. Your code modifies the objects in second. My code returns new objects and does not change the contents of second at all.
This is usually the better choice, but it depends on how you use this value and how data is expected to flow around your program.
You need to be careful with iterating, because you can have different count of elements in first and second arrays. So the possible solution will be like this:
const first = [
{
"AGREE_EFF_DATE__0": "02-Aug-2018",
"AGREE_TERM_DATE__0": "30-Apr-2021",
"AGREE_IND__0": "P1",
"P_DBAR_IND__0": "N",
"AGREE_EFF_DATE__1": "01-May-2021",
"AGREE_TERM_DATE__1": null,
"AGREE_IND__1": "NP",
"P_DBAR_IND__1": "N",
"PROVIDER_SPECIALITY__0": "PSYCHOLOGY, CLINICAL",
"PROVIDER_SPECIALITY_CODE__0": "CK"
}
];
const second = [
{
"STATUS": "ACTIVE",
"MEDICARE_NUMBER": 12345
}
];
console.log(mergeAll(first, second));
function mergeAll(firstArray, secondArray) {
const result = [];
const minLength = firstArray.length < secondArray.length ? firstArray.length : secondArray.length;
for (let i = 0; i < minLength; i++) {
result.push({...firstArray[i], ...secondArray[i]});
}
return result;
}

How can I delete a key in a nested Javascript object with a dynamic number of properties and without eval?

I am looking to delete a specific key from a nested Javascript object based on a list of dynamic properties. Here is an example of what I mean:
This is a sample object:
employees: [
{
name: "John",
id: 1234567890,
salary: 60000
},
{
name: "Jack",
id: 0987654321,
salary: 55000
}
],
location: {
building: {
address: "111 Main St"
}
}
I am looking to delete the address key when I am provided an array of ['location', 'building', 'address']
When I say "dynamic" I mean that I could also be provided with an array of ['employees', 1] so I cannot rely on a set number of nested properties.
The only approach that works for me right now is to use the dreaded eval, which is not a permanent solution since the Javascript objects that I am reading are written by users.
let jsObject = ... // the object shown above
let properties = ['location', 'building', 'address']
let evalString = ''
for (let i = 0; i < properties.length; i++){
evalString += '[\''+properties[i]+'\']'
}
eval('delete jsObject'+evalString)
What is an alternative to eval that will accomplish this same goal?
You could reduce the object by the keys and save the last key for deleting the object with that key.
function deleteKey(object, keys) {
var last = keys.pop();
delete keys.reduce((o, k) => o[k], object)[last];
return object;
}
var object = { employees: [{ name: "John", id: '1234567890', salary: 60000 }, { name: "Jack", id: '0987654321', salary: 55000 }], location: { building: { address: "111 Main St" } } };
console.log(deleteKey(object, ['location', 'building', 'address']));
.as-console-wrapper { max-height: 100% !important; top: 0; }
This method accepts an object and an array of properties, and removes the inner most property as required
function remove(obj, props) {
delete props.slice(0, -1).reduce((init, curr) => init && init[curr], obj)[[...props].pop()];
}
You can break your array into everything except the last element, get a reference to that and the call delete on the object using the last element. You can use reduce to easily build the object reference. You need to be careful with arrays because you can't use delete without leaving an empty slot — delete doesn't change the length.
Here's the basic idea:
function deleteProp(obj, keys){
let prop = keys.pop() // get last key
let c = keys.reduce((a, c) => a[c], obj) // get penultimate obj
if (Array.isArray(c)) c.splice(prop, 1) // if it's an array, slice
else delete c[prop] // otherwise delete
}
// Delete address
let obj = {employees: [{name: "John",id: 1234567890,salary: 60000},{name: "Jack",id: 0987654321,salary: 55000}],location: {building: {address: "111 Main St"}}}
deleteProp(obj, ['location', 'building', 'address'])
console.log(obj)
//Delete employee 1
obj = {employees: [{name: "John",id: 1234567890,salary: 60000},{name: "Jack",id: 0987654321,salary: 55000}],location: {building: {address: "111 Main St"}}}
deleteProp(obj, ['employees', 1])
console.log(obj)
//Delete employee 1 id
obj = {employees: [{name: "John",id: 1234567890,salary: 60000},{name: "Jack",id: 0987654321,salary: 55000}],location: {building: {address: "111 Main St"}}}
deleteProp(obj, ['employees', 1, 'id'])
console.log(obj)
Here is a sample that i'm sure could be trimmed down a bit but it explains each step and you should be able to see whats happening in it:
let jsObject = {
employees: [{
name: "John",
id: 1234567890,
salary: 60000
},
{
name: "Jack",
id: 0987654321,
salary: 55000
}
],
location: {
building: {
address: "111 Main St"
}
}
};
let properties = ['location', 'building', 'address'];
// we use this to traverse the object storing the parent
let parent = null;
// run over each property in the array
for (let i = 0; i < properties.length; i++) {
// check if this is the last property and we have the parent object
if (i + 1 == properties.length && parent)
delete parent[properties[i]]; // just delete the property from the object
else if (parent === null)
parent = jsObject[properties[i]] // set the initial parent
else
parent = parent[properties[i]] // set the parent to the property in the existing object
}
// log the output
console.log(jsObject);
You are going to want to throw error handling and checks to make sure you don't end up outside the object as well.
Navigate to the object that contains the property you want to delete, then delete it:
let jsObject = {
employees: [
{
name: "John",
id: 1234567890,
salary: 60000
},
{
name: "Jack",
id: 0987654321,
salary: 55000
}
],
location: {
building: {
address: "111 Main St"
}
}
};
let properties = ['location', 'building', 'address'];
let innerMost = jsObject;
for (let i = 0; i < properties.length - 1; i++) {
if (typeof innerMost !== "object" || innerMost === null) {
innerMost = null;
break;
}
innerMost = innerMost[properties[i]];
};
if (
innerMost !== null &&
typeof innerMost === "object" &&
properties.length > 0 &&
innerMost.hasOwnProperty(properties[properties.length - 1])
) {
delete innerMost[properties[properties.length - 1]];
console.log(jsObject);
} else console.log("Invalid path");

How to construct an object in javascript not an array object using push method or similar

I'm trying to build a javascript object through the push method of an array. There are some loops inside to fill the values, but the result is a matrix. I know it's an object too, but I like to use dot notation to use the resulting object.
How to do this and achieve an object, not an array
let person = [{
age: 0,
name: "",
second_name: "",
objeto2: {
name2: "",
second_name2: ""
}
}]
for (let i = 1; i <= 3; i++) {
person.push({ age: i, name: i + 1, second_name: "segundo", objeto2: { name2: "xxsxs", second_name2: "adfa" } })
};
the results:
[ { age: 0,
name: '',
second_name: '',
objeto2: { name2: '', second_name2: '' } },
{ age: 1,
name: 2,
second_name: 'segundo',
objeto2: { name2: 'xxsxs', second_name2: 'adfa' } },
{ age: 2,
name: 3,
second_name: 'segundo',
objeto2: { name2: 'xxsxs', second_name2: 'adfa' } },
{ age: 3,
name: 4,
second_name: 'segundo',
objeto2: { name2: 'xxsxs', second_name2: 'adfa' } } ]
I think this is what you're looking for:
let person = {
"$0": {age:0, name:"", alias:"", "$1":{name:"", alias:""}}
};
for (let i=0; i<3; i++) {
let j = Object.keys(person).length;
person["$"+j] = {age:j, name:j+1, alias:"segundo", "$1":{name:"xxsxs", alias:"adfa"}};
}
console.log(person);
console.log(person.$2.$1.name);
console.log(person.$3.alias);
console.log(person.$3.$1.alias);
codepen: https://codepen.io/anon/pen/qoGoKp?editors=0012
In objects, properties always need a key ($1,$2,etc).
And those keys always have to be a string, numbers will be automatically typecasted into strings: see this link (under "Property names").
For the above reason, and specific to dot-notation itself, you cannot use only a number to access the object's properties when using dot-notation. Using person.2.1.name is not possible. Therefor I prepended the numbers with $, so it becomes person.$2.$1.name, which is possible.
You can change $ to any other valid character, like _, but I found this the most readable.
In order to be able to 'push' the next property to the end of the existing object, I use Object.keys(person).length to get the next available index number.
Note that in your case you could also use the integer i from the for-loop, but Object.keys().length most closely resembles the Array.push() method.
Also, if at a later point you would need to add another couple of properties, you wouldn't have to alter your for-loop to correct for the index numbers. Now, the 3 in i<3; stands for the number of properties you want to add to the object, so if later on you wanted to add another 5 properties, your for-loop would start like for (let i=0; i<5; i++) { and everything else would stay the same. You could even make a function for it to make it more flexible:
function addPersons(n) {
for (let i=0; i<n; i++) {
let j = Object.keys(person).length;
person["$"+j] = {age:j, name:j+1, alias:"segundo", "$1":{name:"xxsxs", alias:"adfa"}};
}
}
addPersons(3);
addPersons(5);

Javascript unwanted "empty" property after merging objects

I want to merge objects based on a property, and I want to use this property as the key for the merged array.
here is my code:
let mergedProfiles = [];
for (let set of profiles) {
if (Object.keys(mergedProfiles).indexOf(String(set.profile_id)) >= 0) { // if profile id exists
mergedProfiles[set.profile_id].push(set);
} else {
mergedProfiles[set.profile_id] = [];
mergedProfiles[set.profile_id].push(set);
}
}
example of profile object: {user_id: 17, name: "test", country: "US", bid: 0.02, profile_id: "1", user_id: 12}
the merge is working fine but for some reason I cant understand I always end up with empty as my first property in the mergedProfiles, any idea what am Imissing?
You could take an object instead of an array, because you get a sparse array with not used indices.
var profiles = [{ user_id: 17, name: "test", country: "US", bid: 0.02, profile_id: "1", user_id: 12 }],
mergedProfiles = {},
s;
for (s of profiles) {
if (!mergedProfiles[s.profile_id]) {
mergedProfiles[s.profile_id] = [];
}
mergedProfiles[s.profile_id].push(s);
}
console.log(mergedProfiles);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You will want to store the results in an object so you can reference by key.
const allProfiles = {};
for(let set of profiles) {
if(typeof allProfiles[set.profile_id] === "undefined") {
allProfiles[set.profile_id] = [set];
}
else {
allProfiles[set.profile_id].push(set);
}
}
The reason your first element is empty is that you are declaring an empty JavaScript array and then failing to initialize all of the elements.
If you do something like
var someArray = [];
someArray[5] = 10;
console.log(someArray); // [empty × 5, 5]
then the result is a sparse array where the first 5 elements (0 - 4) are all empty or in other words undefined. In your example you are doing the same as above where your profile_id is some integer greater than 0.

how to rescursively deep merge an array of objects

I want to merge two arrays of objects. Those objects have the same structure, but one of them is missing the hide property. I want to copy the value of hide property from one object to the other that is missing this property. The important part is that I don't want to mutate any of these arrays!
The first array looks like this (notice that there is hide property):
let first_array = [
{
name: 'John',
age: 40,
hide: true,
childs: [
{
name: 'Alice',
age: 20,
hide: false,
childs: [
{
name: 'Mike',
age: 2,
hide: true
}
]
}
]
},
{
name: 'Peter',
age: 40,
hide: true,
childs: [
{
name: 'Andrew',
age: 20,
hide: true,
childs: [
{
name: 'Jessica',
age: 2,
hide: true
}
]
}
]
}
]
The second array looks almost the same! The only thing missing is hide property.
let second_array = [
{
name: 'John',
age: 40,
childs: [
{
name: 'Alice',
age: 20,
childs: [
{
name: 'Mike',
age: 2,
}
]
}
]
},
{
name: 'Peter',
age: 40,
childs: [
{
name: 'Andrew',
age: 20,
childs: [
{
name: 'Jessica',
age: 2,
}
]
}
]
}
]
Now, I want to create new array with where within each object there is hide property.
I know how to do this recursively in the imperative way, but unfortunately I'm mutating data - which I don't want to do.
function getHideProperty(first, second) {
for (let i = 0; i < second.length; i++) {
for (let j = 0; j < first.length; j++) {
if (second[i].name === first[j].name) {
second[i].hide = first[j].hide
if (second[i].childs) {
second[i].childs = getHideProperty(first[j].childs, second[i].childs)
}
}
}
}
return second
}
Now I can create new array with merged objects:
const newArray = getHideProperty(second_array, first_array)
Now, every object in second_array has hide property. But I mutated the array :(
How to achieve such result without mutating the array?
You'll need to:
Create a new array to store the new information, and return that
Deep-copy second[i] to store in the new array, prior to modifying anything
For #2, choose your favorite answer from What is the most efficient way to deep clone an object in JavaScript?
For #1, very roughly (see comments):
function getHideProperty(first, second) {
const result = []; // Our result array
for (let i = 0; i < second.length; i++) {
const secondEntry = result[i] = deepCopy(second[i]); // The deep copy, and let's avoid constantly re-retrieving second[i]/result[i]
for (let j = 0; j < first.length; j++) {
if (secondentry.name === first[j].name) {
secondentry.hide = first[j].hide
if (secondEntry.childs) {
// Could be more efficient here, since the entries in `childs` are already copies; left as an exercise to the reader...
secondEntry.childs = getHideProperty(first[j].childs, secondEntry.childs)
}
}
}
}
return result;
}
This is not meant to be an all-singing, all-dancing solution. It's meant to help you along the way. Note the deepCopy placeholder for your preferred solution to #2. :-)
If you do something like the above (nested loops) and find that it's a performance problem, you can create a Map of the entries in first keyed by their names, and then look them up in the map when looping through second (rather than the nested loop). The complexity is only useful if you run into a performance problem with the simple nested loops solution.
This is a functional approach that doesn't mutate any of the original arrays or their items:
function getHideProperty(first, second) {
return second.map(function(item) {
var corresponding = first.find(function(searchItem) {
return searchItem.name === item.name;
});
return Object.assign({},
item,
{ hide: corresponding.hide },
item.childs
? { childs: getHideProperty(item.childs, corresponding.childs) }
: {}
);
});
}

Categories

Resources