In my code I have two arrays, first one contains multiple objects. While the second one is to store serialized form data (mapped to JSON). So both arrays are having identical keys.
What I want to achieve is to update the values of an object in the original array based on the values of an object in new array dynamically, by ID in the object.
Found some examples online but hard to get them to work. Because most of them are showing either one level of objects but I'm working on complex nested objects in the array.
var products = [
{
Id: 1,
Name: 'Product1',
Attributes: {
Storage: 'Normal',
Size: 'Small'
}
},
{
Id: 2,
Name: 'Product2',
Attributes: {
Storage: 'Normal',
Size: 'Small'
}
}
];
var newData = [
{
Id: 2,
Name: 'French Fries'
},
{
Id: 1,
Attributes: {
Size: 'Medium'
}
}
];
The expected outcome is the products array now updated with the values from the second array.
Output:
[
{
Id: 1,
Name: 'Product1',
Attributes: {
Storage: 'Normal',
Size: 'Medium'
}
},
{
Id: 2,
Name: 'French Fries',
Attributes: {
Storage: 'Normal',
Size: 'Small'
}
}
]
You could take a Map for the update items and iterate products.
If an item is found for an update, take a recursive approach and iterate the entries and check if the value is an object, then iterate the nested properties.
If no nested object found update the property.
This works for arrays as well.
function update(target, source) {
Object.entries(source).forEach(([key, value]) => {
if (value && typeof value === 'object') {
update(target[key] = target[key] || (Array.isArray(value) ? [] : {}), value);
} else if (target[key] !== value) {
target[key] = value;
}
});
}
var products = [{ Id: 1, Name: 'Product1', Attributes: { Storage: 'Normal', Size: 'Small' } }, { Id: 2, Name: 'Product2', Attributes: { Storage: 'Normal', Size: 'Small' } }],
newData = [{ Id: 2, Name: 'French Fries' }, { Id: 1, Attributes: { Size: 'Medium' } }],
map = new Map(newData.map(o => [o.Id, o]));
products.forEach(o => map.has(o.Id) && update(o, map.get(o.Id)));
console.log(products);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can create a function which combine nested objects. And then use map() and find() to create combined array of objects.
var products = [
{
Id: 1,
Name: 'Product1',
Attributes: {
Storage: 'Normal',
Size: 'Small'
}
},
{
Id: 2,
Name: 'Product2',
Attributes: {
Storage: 'Normal',
Size: 'Small'
}
}
];
var newData = [
{
Id: 2,
Name: 'French Fries'
},
{
Id: 1,
Attributes: {
Size: 'Medium'
}
}
];
const haveNested = obj => Object.values(obj).some(x => typeof x === "object");
function combine(obj1,obj2){
if(!haveNested(obj1)) return ({...obj1,...obj2})
let res = obj1
for(let key in obj1){
if(typeof obj1[key] === "object"){
res[key] = combine(obj1[key],obj2[key]);
}
else if(obj2[key]) res[key] = obj2[key]
}
return res;
}
const result = products.map(x => {
let temp = newData.find(a => a.Id === x.Id);
return temp ? combine(x,temp) : x;
})
console.log(result)
Related
I have array of objects, each object must have key and title, but children is optional, and it can be nested, i can have children inside of children many times. I want to remove some object by provided key value (for example key 677). I tried with filter but i only remove first level. Also have tried recursion, but not sure if i did it right.
const data = [{
key: '1',
title: 'title 1',
children: [{
key: '098',
title: 'hey',
children: [{
key: '677',
title: 'child'
}]
}]
},
{
key: '123',
title: 'tile 111'
},
{
key: '345',
title: 'something'
}
];
const rem = '677';
const del = (el) => {
if (!el.children) {
return el.key !== rem;
} else {
if (el.key !== rem) {
del(el.children);
return el;
}
}
};
const res = data.filter((el) => {
return del(el);
});
console.log(res);
I guess your existing solution is like
const data = [
{
key: '1',
title: 'title 1',
children: [{
key: '098',
title: 'hey',
children: [{ key: '677', title: 'child'}]
}]
},
{ key: '123', title: 'tile 111' },
{ key: '345', title: 'something' }
];
function removeByKey(arr, removingKey){
return arr.filter( a => a.key !== removingKey);
}
So it works on the first level but not deeply.
Just change it like that will do the jobs
function removeByKey(arr, removingKey){
return arr.filter( a => a.key !== removingKey).map( e => {
return { ...e, children: removeByKey(e.children || [], removingKey)}
});
}
Little warning, children property will not be set to [] for every item not having any children.
So how it works? Well instead of keeping acceptable items as they are, we make a copy using {...e} that's equivalent to {key:e.key, title:e.title, children:e.children} in this case.
We know force to override the property children with removeByKey(e.children || [], removingKey), so we call the method recursively. Not the function works deeeply.
I would use a recursion approach with findIndex and splice. Using some will allow the code to exit without running through the entire tree.
const data = [{
key: '1',
title: 'title 1',
children: [{
key: '098',
title: 'hey',
children: [{
key: '677',
title: 'child'
}]
}]
},
{
key: '123',
title: 'tile 111'
},
{
key: '345',
title: 'something'
}
];
const removeKey = (data, key) => {
// look to see if object exists
const index = data.findIndex(x => x.key === key);
if (index > -1) {
data.splice(index, 1); // remove the object
return true
} else {
// loop over the indexes of the array until we find one with the key
return data.some(x => {
if (x.children) {
return removeKey(x.children, key);
} else {
return false;
}
})
}
}
console.log(removeKey(data, '677'))
console.log(JSON.stringify(data));
You can use some simple recursion to do the trick:
const data = [
{
key: '1',
title: 'title 1',
children: [
{
key: '098',
title: 'hey',
children: [{ key: '677', title: 'child'}]
}
]
},
{ key: '123', title: 'tile 111' },
{ key: '345', title: 'something' }
];
function removeByKey(key, arr) {
// loop through all items of array
for(let i = 0; i < arr.length; i++) {
// if array item has said key, then remove it
if(arr[i].key === key) {
arr.splice(i, 1);
} else if(typeof(arr[i].children) !== "undefined") {
// if object doesn't have desired key but has children, call this function
// on the children array
removeByKey(key, arr[i].children);
}
}
}
removeByKey('098', data);
console.log(data);
This may be a little easier to understand than the other answer provided.
I would like to know how to change nested array object to object depending on key in javascript
I have objects obj1 and obj2, depending on key item type change the object.
function changeObj(obj){
let result = obj.reduce(function (acc, item) {
if(item.items.trim() !== "" && item.key.trim() !== ""){
acc[item.key] = item.items
return acc
}
return acc
}, {});
return result;
}
let result = this.changeObj(obj2)
var obj1 = [
{ id:0, items:["SG","AU"], count: 2, key:"countries"},
{ id:1, items:["finance"], count: 3 key:"info"}
]
var obj2 = [
{ id:0, items: "SG", key: "country"},
{ id:1, items: "details", key: "info"}
]
Expected Output:
// if items key is array
{
fields: {
countries: ["SG","AU",2],
info: ["finance",3]
}
}
//if items key is string
{
fields: {
country: "SG",
info: "details"
}
}
I think the reason your code is not running is because the wrong format of your objects (1 and 2). Your code is okay except the condition because trim() only works on string type so it errors on array. Try this code snippet
function changeObj(obj){
let result = obj.reduce(function (acc, item) {
acc[item.key] = item.items;
return acc;
}, {});
return result;
}
var obj1 = [
{ id:0, items:["SG","AU"], count: 2, key:"countries"},
{ id:1, items:["finance"], count: 3, key:"info"}
]
var obj2 = [
{ id:0, items: "SG", key: "country"},
{ id:1, items: "details", key: "info"}
]
console.log(changeObj(obj1));
const changeObj = obj =>
obj.reduce((acc, item) => {
if (Array.isArray(item.items)) {
acc[item.key] = [...item.items, item.count];
} else {
acc[item.key] = item.items;
}
return acc;
}, {});
var obj1 = [
{ id: 0, items: ['SG', 'AU'], count: 2, key: 'countries' },
{ id: 1, items: ['finance'], count: 3, key: 'info' }
];
var obj2 = [
{ id: 0, items: 'SG', key: 'country' },
{ id: 1, items: 'details', key: 'info' }
];
console.log(changeObj(obj1));
console.log(changeObj(obj2));
or cleaned up even more
const changeObj = obj =>
obj.reduce((acc, { items, key, count }) => {
Array.isArray(items) ? (acc[key] = [...items, count]) : (acc[key] = items);
return acc;
}, {});
var obj1 = [
{ id: 0, items: ['SG', 'AU'], count: 2, key: 'countries' },
{ id: 1, items: ['finance'], count: 3, key: 'info' }
];
var obj2 = [
{ id: 0, items: 'SG', key: 'country' },
{ id: 1, items: 'details', key: 'info' }
];
console.log(changeObj(obj1));
console.log(changeObj(obj2));
I would like how to convert the object to array of objects in JavaScript.
In object obj, fields should be modified to nested array of objects in JavaScript.
var obj = [{
id: 1,
fields: {
color: "white",
brand: "xyz"
}
}]
function objarray(obj) {
return obj.map(e => ({
label: Object.keys(e.fields)
}))
}
var result = objarray(obj);
console.log(result);
Expected Output:
[
{
label: "color",
children: [{label:"white" }]
},
{
label: "brand",
children: [{label:"xyz" }]
}
]
Obj.map isn't going to work as obj is an array with one element so it will only iterate one time.
let newArray = []
for (var i in obj.fields)
{
newArray.push({label: i, children:obj[[0].fields.i});
}
I doubt this will compile. I suspect i is an object here and can't be used as a reference on obj[0].fields.
but You get the idea.
This should do the trick
var obj = [{
id: 1,
fields: {
color: "white",
brand: "xyz"
}
}]
const mapFields = (obj) => {
return obj.map((o) => {
return Object.keys(o.fields).map((f) => {
var field = {
label: f,
children: [{label: o.fields[f]}]
};
return field;
});
})
}
console.log(mapFields(obj));
You can do this by making use of Object.entries():
var obj = [ { id: 1, fields: { color: "white", brand: "xyz" } }];
result = Object.entries(obj[0].fields).map(([k,v])=>({label:k, children:[{label:v}]}))
console.log(result)
I'm trying to get two values (label and data) from my source data array, rename the label value and sort the label and data array in the result object.
If this is my source data...
const sourceData = [
{ _id: 'any', count: 12 },
{ _id: 'thing', count: 34 },
{ _id: 'value', count: 56 }
];
...the result should be:
{ label: ['car', 'plane', 'ship'], data: [12, 34, 56] }
So any should become car, thing should become plane and value should become ship.
But I also want to change the order of the elements in the result arrays using the label values, which should also order the data values.
Let's assume this result is expected:
{ label: ['ship', 'car', 'plane'], data: [56, 12, 34] }
With the following solution there is the need of two variables (maps and order). I thing it would be better to use only one kind of map, which should set the new label values and also the order. Maybe with an array?!
Right now only the label values get ordered, but data values should be ordered in the same way...
const maps = { any: 'car', thing: 'plane', value: 'ship' }; // 1. Rename label values
const result = sourceData.reduce((a, c) => {
a.label = a.label || [];
a.data = a.data || [];
a.label.push(maps[c._id]);
a.data.push(c.count);
return a;
}, {});
result.label.sort((a, b) => {
const order = {'ship': 1, 'car': 2, plane: 3}; // 2. Set new order
return order[a] - order[b];
})
You could move the information into a single object.
const
data = [{ _id: 'any', count: 12 }, { _id: 'thing', count: 34 }, { _id: 'value', count: 56 }],
target = { any: { label: 'car', index: 1 }, thing: { label: 'plane', index: 2 }, value: { label: 'ship', index: 0 } },
result = data.reduce((r, { _id, count }) => {
r.label[target[_id].index] = target[_id].label;
r.data[target[_id].index] = count;
return r;
}, { label: [], data: [] })
console.log(result);
Instead of separating the data into label and data and then sorting them together, you can first sort the data and then transform.
const sourceData = [
{ _id: 'any', count: 12 },
{ _id: 'thing', count: 34 },
{ _id: 'value', count: 56 }
];
const maps = { any: 'car', thing: 'plane', value: 'ship' };
// Rename label values.
let result = sourceData.map(item => ({
...item,
_id: maps[item._id]
}));
// Sort the data.
result.sort((a, b) => {
const order = {'ship': 1, 'car': 2, plane: 3};
return order[a._id] - order[b._id];
})
// Transform the result.
result = result.reduce((a, c) => {
a.label = a.label || [];
a.data = a.data || [];
a.label.push(c._id);
a.data.push(c.count);
return a;
}, {});
console.log(result);
Take the following two arrays:
const array1 = [
{
props: {
type : 'text',
id : 'item1',
name : 'item1',
value : '#item1#',
},
},
{
props: {
type: 'hidden',
id: 'item2',
name: 'item2',
value: '#item2#',
},
}
];
const array2 = [
{
props: {
type: 'hidden',
id: 'item1',
name: 'item1',
value: '#item1#',
},
}
];
What I'm trying to do is concatenate them into a single array, and remove any duplicates based on the id property. However the caveat here is that the object that does NOT have a type of hidden must have presidence.
So I should basically be left with the contents of array1, as the duplicate item from array2 has the type value of hidden, like so:
// Result
[
{
props: {
type : 'text', // Note the type here is "text"
id : 'item1',
name : 'item1',
value : '#item1#',
},
},
{
props: {
type: 'hidden',
id: 'item2',
name: 'item2',
value: '#item2#',
},
}
];
I can easily concatenate them using:
const array = array1.concat(array2);
My idea was to then use a Filter but I'm having a bit of a brain melt. Here is what I have come up with so far:
const concat = (array1, array2) => {
const array = array1.concat(array2);
const ids = [];
// Create array of ID's
for (const i in array1) {
ids.push(array1[i].props.id);
}
return array.filter((obj) => {
if (obj.props.type !== 'hidden' && ids.includes(obj.props.id)) {
return true;
}
return false;
});
};
Would using a Reduce be a better approach here?
Here is a JSFiddle of what I have so far: https://jsfiddle.net/64uprbhn/
You can
Create a Map of id => object from the first array
Go over the second array and either
add the item if not already in the map
check if the current item is not type: "hidden" and overwrite the other one
otherwise discard the item
Create a new array from the Map
const array1 = [
{
props: {
type : 'text',
id : 'item1',
name : 'item1',
value : '#item1#',
},
},
{
props: {
type: 'hidden',
id: 'item2',
name: 'item2',
value: '#item2#',
},
},
{
props: {
type: 'hidden',
id: 'item3',
name: 'item3',
value: '#item3#',
},
}
];
const array2 = [
{
props: {
type: 'hidden',
id: 'item1',
name: 'item1',
value: '#item1#',
},
},
{
props: {
type: 'text',
id: 'item3',
name: 'item3',
value: '#item3#',
},
}
];
//1. create a map from id => object pairs
const map = new Map(array1.map(obj => [obj.props.id, obj]));
//2. go over the second array
array2.forEach(obj => {
if (!map.has(obj.props.id) //not a duplicate
|| obj.props.type !== "hidden") { //is not hidden
//insert
map.set(obj.props.id, obj);
}
});
//3. convert back into array
const merged = [...map.values()];
console.log(merged);
For the record, you can basically do the same (or pretty similar) thing using .filter but you'll have to do a O(n) lookup for each item. A Map ensures much faster lookups.
const array1 = [
{
props: {
type : 'text',
id : 'item1',
name : 'item1',
value : '#item1#',
},
},
{
props: {
type: 'hidden',
id: 'item2',
name: 'item2',
value: '#item2#',
},
}
];
const array2 = [
{
props: {
type: 'hidden',
id: 'item1',
name: 'item1',
value: '#item1#',
},
}
];
function getUnique(arr, comp) {
const unique = arr
.map(e => e[comp])
// store the keys of the unique objects
.map((e, i, final) => final.indexOf(e) === i && i)
// eliminate the dead keys & store unique objects
.filter(e => arr[e]).map(e => arr[e]);
return unique;
}
const arr = array1.concat(array2);
console.log(getUnique(arr, 'id'));
Here's what worked for me
const someItems = [{ id: 1 }, { id: 2 }, { id: 1 }]
function getUniqueItems(items) {
const uniqueIds = new Set(items.map((item) => item.id))
const itemsWithUniqueIds = [...uniqueIds].map(id => items.find(item => item.id === id)).filter(Boolean)
return itemsWithUniqueIds
}
console.log(getUniqueItems(someItems))
If you are using TypeScript, TS will complain about the .filter(Boolean). In that case just replace Boolean with (item: any | undefined): item is any => Boolean(item). Of course, you can then go ahead and also replace any with whichever type makes sense in your case.