How to restructure object array to nested arrays? - javascript

The structure of an object array looks like this:
[
{ _id: "id1", metadata: { data: ["somedata1"], link: [] } }
{ _id: "id2", metadata: { data: ["somedata2"], link: ["id2", "id3"] } }
{ _id: "id3", metadata: { data: ["somedata3"], link: ["id2", "id3"] } }
{ _id: "id4", metadata: { data: ["somedata4"] } }
]
As you can see, there is an optional link key, which connects two objects. Now I need to convert the object to array elements, which merges the connected datasets. So the result should look like this:
[
[
{ _id: "id1", metadata: { data: ["somedata1"], link: [] } }
],
[
{ _id: "id2", metadata: { data: ["somedata2"], link: ["id2", "id3"] } },
{ _id: "id3", metadata: { data: ["somedata3"], link: ["id2", "id3"] } }
],
[
{ _id: "id4", metadata: { data: ["somedata4"] } }
]
]
I think I would iterate through all objects, but I don't know how to merge the linked objects into one array element without getting duplicates.
const result = []
data.map(d => {
if (!d.metadata.link?.length)
result.push([d])
else
result.push(
data.getFiles.filter((item) => d.metadata.link.indexOf(item._id) !== -1)
)
// but this would result in a duplicate array, as id2 and id3 have the same link content
})

if you just want to loop on an array you can directly use array.forEach
An idea can be to just add an if before push in result array to check if data have already been added for sample with array.some
if (!result.some(oneArr => oneArr.some(oneData => oneData._id === d._id)))
const data = [
{ _id: "id1", metadata: { data: ["somedata1"], link: [] } },
{ _id: "id2", metadata: { data: ["somedata2"], link: ["id2", "id3"] } },
{ _id: "id3", metadata: { data: ["somedata3"], link: ["id2", "id3"] } },
{ _id: "id4", metadata: { data: ["somedata4"] } }
];
const result = [];
data.forEach(d => {
if (!d.metadata.link?.length) {
result.push([d])
} else {
if (!result.some(oneArr => oneArr.some(oneData => oneData._id === d._id)))
result.push(
data.filter((item) => d.metadata.link.indexOf(item._id) !== -1)
)
}
});
console.log(result);

I think below way object will not get duplicate. data will group as link values. This approach does n't have much complexity. It will be O(N)
I hope this is what you're looking for.
const data = [
{ _id: "id1", metadata: { data: ["somedata1"], link: [] } },
{ _id: "id2", metadata: { data: ["somedata2"], link: ["id2", "id3"] } },
{ _id: "id3", metadata: { data: ["somedata3"], link: ["id2", "id3"] } },
{ _id: "id4", metadata: { data: ["somedata4"] } }
]
const result = data.reduce((acumD, d) => {
const link = d?.metadata?.link;
if (!link) {
if (acumD['NONE']) {
acumD['NONE'].push(d)
} else {
acumD['NONE'] = [d];
}
} else if (link.length === 0) {
if (acumD['EMPTY']) {
acumD['EMPTY'].push(d)
} else {
acumD['EMPTY'] = [d];
}
} else {
const linkString = link.join(',');
if (acumD[linkString]) {
acumD[linkString].push(d)
} else {
acumD[linkString] = [d];
}
}
return acumD;
}, {});
console.log('result', Object.values(result));

Here's a generic grouping function:
function groupBy(a, fn) {
let m = new Map
for (let x of a) {
let key = fn(x)
if (!m.has(key))
m.set(key, [])
m.get(key).push(x)
}
return [...m.values()]
}
Applied to the problem at hand:
result = groupBy(
data,
x => JSON.stringify(x.metadata.link))
produces the desired result.

Related

JS: append array of objects with data from another

I've got some JS data holding all kinds of data, numbers, child objects, arrays, etc in all manner of different structures:
let datapile = {
cover_img: { uid:'u2a3j4' },
avatar_img: { uid:'u5j3vg' },
created: 8273736384,
friends: [
{ name:'John', img: { uid:'u2726b' }, },
{ name:'Jane', parent: { profile_img: { uid:'u293k4' }, } },
],
occupation: {
past: current,
prior: {
title: 'Accountant',
company: {
logo: { img: { uid:'u29374' } },
}
},
},
...
}
And then I've got this JS list of images:
let imgs : [
{ uid:'u2a3j4', format:'jpg', alt_txt:'Lorem...', size:583729, dominant_color:'#d79273' },
{ uid:'u5j3vg', format:'png', alt_txt:'Lorem...', size:284849, dominant_color:'#f99383' },
{ uid:'u2726b', format:'gif', alt_txt:'Lorem...', size:293742, dominant_color:'#349a83' },
...
],
Now, what I need is a function I can call that will look through the datapile and append img data objects from the imgs list below. So where the datapile now has only the uid reference, it should have the entire img object. And I will then do the same with all kinds of other pieces of referenced data.
I've tried the following function:
function isArray(x){ return ( x !== undefined && Array.isArray(x) ) }
function isObject(x){ return (x && typeof x === "object" && !Array.isArray(x)) }
function get_item(type, uid) { /* loops through eg. imgs and returns img matching uid */ }
function append_referenced_relations(data){
if( !data ) return data
if( isObject(data) && data['uid'] !== undefined ) {
let item = get_item('any', data['uid'])
data = item
}
if( isObject(data) || isArray(data) ) {
for( let key in data ) {
data[key] = this.append_referenced_relations(deepClone(data[key]))
}
}
return data
}
... but I just can't get it to work. And my best googling efforts for similar scenarios have also come up empty. Can the internet help me out here?
you can try something like this
basically it use recursion and Object.fromEntries /entries to check all the keys of the inner object
if you have any specific question feel free to ask me
const decorate = (obj, data) => {
if (typeof obj !== 'object') {
return obj
}
if (Array.isArray(obj)) {
return obj.map(e => decorate(e, data))
}
return Object.fromEntries(
Object.entries(obj).flatMap(([k, v]) => {
if (k === 'uid') {
const imgData = data.find(d => v === d.uid)
return Object.entries(imgData || [[k, v]])
}
return [
[k, decorate(v, data)]
]
})
)
}
let datapile = {
cover_img: {
uid: 'u2a3j4'
},
avatar_img: {
uid: 'u5j3vg'
},
created: 8273736384,
friends: [{
name: 'John',
img: {
uid: 'u2726b'
},
},
{
name: 'Jane',
parent: {
profile_img: {
uid: 'u293k4'
},
}
},
],
occupation: {
past: 'current',
prior: {
title: 'Accountant',
company: {
logo: {
img: {
uid: 'u29374'
}
}
}
}
}
}
let imgs = [{
uid: 'u2a3j4',
format: 'jpg',
alt_txt: 'Lorem...',
size: 583729,
dominant_color: '#d79273'
},
{
uid: 'u5j3vg',
format: 'png',
alt_txt: 'Lorem...',
size: 284849,
dominant_color: '#f99383'
},
{
uid: 'u2726b',
format: 'gif',
alt_txt: 'Lorem...',
size: 293742,
dominant_color: '#349a83'
}
]
console.log(decorate(datapile, imgs))
You need to recurse in the nested datapile to identify the object with uids and add the img properties to be added.
Few cases to consider:
Objects. (If the Object has uid property, then stop recursion for its properties)
Object values having objects.
Array of Objects.
No need to return anywhere in your function actually as we can update objects inline.
Try like below.
let imgs = [ { uid: "u2a3j4", format: "jpg", alt_txt: "Lorem...", size: 583729, dominant_color: "#d79273", }, { uid: "u5j3vg", format: "png", alt_txt: "Lorem...", size: 284849, dominant_color: "#f99383", }, { uid: "u2726b", format: "gif", alt_txt: "Lorem...", size: 293742, dominant_color: "#349a83", }, { uid: "u293k4", format: "gif", alt_txt: "Lorem...", size: 193742, dominant_color: "#349a83", }, { uid: "u29374", format: "gif", alt_txt: "Lorem...", size: 793742, dominant_color: "#349a83", }, ]; let datapile = { cover_img: { uid: "u2a3j4" }, avatar_img: { uid: "u5j3vg" }, created: 8273736384, friends: [ { name: "John", img: { uid: "u2726b" } }, { name: "Jane", parent: { profile_img: { uid: "u293k4" } } }, ], occupation: { past: "current", prior: { title: "Accountant", company: { logo: { img: { uid: "u29374" } }, }, }, }, };
function isArray(x) {
return x !== undefined && Array.isArray(x);
}
function isObject(x) {
return typeof x === "object" && !Array.isArray(x);
}
function get_item(uid) {
return imgs.find((img) => img.uid === uid);
}
function append_referenced_relations(data) {
if (isObject(data)) {
if (data["uid"] !== undefined) {
const img = get_item(data.uid);
// Add img properties to the same object as properties
Object.entries(img).forEach(([key, value]) => {
data[key] = value;
});
} else {
// Recurse for the object values
Object.values(data).forEach((item) => {
append_referenced_relations(item);
});
}
} else if (isArray(data)) {
data.forEach((item) => {
// Recurse for the array entries
append_referenced_relations(item);
});
}
}
append_referenced_relations(datapile);
console.log(JSON.stringify(datapile, null, 2));

How to filter in two deep arrays

I'm looking to filter in two deep arrays, actually my JSON:
{
"0": {
"product":[{
"uuid":"uid",
"name":"Rice"
},
{
"uuid":"uid",
"name":"Pasta"
}]
},
"1": {
"product":[{
"uuid":"uid",
"name":"Milk"
}]
}
}
I would like to get something like that when I filter with the word "ric":
{
"0": {
"product":[{
"uuid":"uid",
"name":"Rice"
}]
}
}
But I got this result:
{
"0": {
"product":[{
"uuid":"uid",
"name":"Rice"
},
{
"uuid":"uid",
"name":"Pasta"
}]
}
}
My code:
dataSort.categories = the json and
event.target.value.toLowerCase() = the specific word
dataSort.categories.filter(s => s.products.find(p => p.name.toLowerCase().includes(event.target.value.toLowerCase())));
You can achieve this with a combination of reduce and filter
var input = {
"0": {
"product":[{
"uuid":"uid",
"name":"Rice"
},
{
"uuid":"uid",
"name":"Pasta"
}]
},
"1": {
"product":[{
"uuid":"uid",
"name":"Milk"
}]
}
}
var search = "ric"
var result = Object.entries(input).reduce( (acc, [key,val]) => {
found = val.product.filter(x => x.name.toLowerCase().includes(search.toLowerCase()))
if(found.length){
acc[key] = {...val, product: found}
}
return acc
},{})
console.log(result)
There is many approach to do this, one is to map your top level array to the subArrays filtered results then filter it after:
dataSort.categories
.map(s => s.products.filter(p => p.name.toLowerCase().includes(event.target.value.toLowerCase())))
.filter(s => !!s.products.length);
You may also prefer to get a "flat" array as result because it is easier to use after :
dataSort.categories
.reduce((acc, s) => [...acc, s.products.filter(p => p.name.toLowerCase().includes(event.target.value.toLowerCase()))], []);
Please find below the code to filter out values inside the product.name and only return the value which are matching the equality condition in product array.
const json = [
{
product: [
{
uuid: "uid",
name: "Rice",
},
{
uuid: "uid",
name: "Pasta",
},
],
},
{
product: [
{
uuid: "uid",
name: "Milk",
},
],
},
];
const inputValue = "rIc";
const filteredArray = [];
json.map((s) => {
const item = s.product.find((p) =>
p.name.toLowerCase().includes(inputValue.toLowerCase())
);
item && filteredArray.push({ product: item });
});
console.dir(filteredArray);
Your dataset is an Object, not an Array and the filter is an Array method. You can use reduce by looping on the object values by Object.values then filter your products array.
const data = {
'0': {
product: [
{
uuid: 'uid',
name: 'Rice',
},
{
uuid: 'uid',
name: 'Pasta',
},
],
},
'1': {
product: [
{
uuid: 'uid',
name: 'Milk',
},
],
},
};
const keyword = 'ric';
const dataset = Object.values(data);
const results = dataset.reduce((acc, item, index) => {
const search = keyword.toLowerCase();
const product = item.product.filter(product => product.name.toLowerCase().includes(search));
if (product.length) acc[index] = { ...item, product };
return acc;
}, {});
console.log(results);

transform an array of objects into an array containing arrays of objects

I have an array of objects like this:
[{...}, {...}, {...}, {...}, {...}]
An object looks like this:
{
id: ...
name: ...
association: {
id: ...
}
}
I'd like to collect objects with the same association id and get a array like this:
[ [ { ... association { id: 1} }, { ... association { id: 1} } ], [ { ... association { id: 2 } } ] ]
How can I do this?
Sounds like you're looking for a function that will return an array of objects that contain an association id that is provided
const data = [{...},{...},{...}]
const getByAssociationID = (source, id) => source.filter(obj => obj.association.id === id)
console.log(getByAssociationID(data, id))
This should group the data as you describe
function groupByAssociation(data) {
return data.reduce((list, value) => {
let added = false;
list.forEach(group => {
if(group[0].association.id === value.association.id) {
group.push(value);
added = true;
}
});
if(!added) {
list.push([ value ]);
}
return list;
}, []);
}
Use forEach to build and object with keys as association.id and values are accumulated.
const data = [
{
id: 1,
name: "blah",
association: {
id: "a1"
}
},
{
id: 2,
name: "foo",
association: {
id: "a2"
}
},
{
id: 3,
name: "test",
association: {
id: "a2"
}
}
];
const process = data => {
const obj = {};
data.forEach(item => {
const aId = item.association.id;
const newItem = obj[aId] || [];
newItem.push(item);
obj[aId] = newItem;
});
return Object.values(obj);
};
console.log(process(data));

Delete specific object with id

I have an array of objects, I need to delete a complete object based on the id
Input :
filters: [
{
key: "status",
label: "En attente",
value: "waiting",
id: 0
},
{
key: "dateDue[min]",
label: "15/12/2019",
value: "15/12/2019",
id: 1
},
{
key: "dateDue[max]",
label: "02/02/2020",
value: "02/02/2020",
id: 2
},
{
key: "bien",
values: [
{
label: "Studio Bordeaux",
value: 36,
id: 3
},
{
label: "Studio 2",
value: 34,
id: 184
}
]
},
{
key: "type",
values: [
{
type: "receipts",
label: "Loyer",
value: "loyer",
id: 4
},
{
type: "receipts",
label: "APL",
value: "apl",
id: 5
},
{
type: "spending",
label: "taxes",
value: "taxes",
id: 6
}
]
}
]
So I created a removeItem method with the id that must be deleted in parameters
removeItem method :
removeItem = (e, id) => {
const { filters } = this.state;
const remove = _.reject(filters, el => {
if (!_.isEmpty(el.values)) {
return el.values.find(o => o.id === id);
}
if (_.isEmpty(el.values)) {
return el.id === id;
}
});
this.setState({
filters: remove
});
};
I use lodash to make my job easier and more specifically _.reject
My issue is the following :
I manage to correctly delete the classic objects for example
{
key: "status",
label: "En attente",
value: "waiting",
id: 0
}
but my method however does not work for objects of the following form
{
key: "bien",
values: [
{
label: "Studio Bordeaux",
value: 36,
id: 3
},
{
label: "Studio 2",
value: 34,
id: 184
}
]
},
currently the whole object is deleted and not only the object in the values array according to its id
Here is my codesandbox!
thank you in advance for your help
EDIT
I found a solution with lodash (compact), I share my solution here :
removeIdFromCollection = id => {
const { filters } = this.state;
const newFilters = [];
_.map(filters, filter => {
if (filter.values) {
const valuesTmp = _.compact(
_.map(filter.values, value => {
if (value.id !== id) return value;
})
);
if (!_.isEmpty(valuesTmp)) {
return newFilters.push({
key: filter.key,
values: valuesTmp
});
}
}
if (filter.id && filter.id !== id) return newFilters.push(filter);
});
return newFilters;
};
removeItem = id => e =>
this.setState({
filters: this.removeIdFromCollection(id)
});
The values false, null, 0, "", undefined, and NaN are removed with lodash compact (_.compact(array))
Here is my updated codesandbox
You will need to filter the filters array and each values separately. Below is a recursive function which will remove items with the given id from the filters array and from the values property.
PS. This example is not using Lodash as I think it is not needed in this case.
removeIdFromCollection = (collection, id) => {
return collection.filter(datum => {
if (Array.isArray(datum.values)) {
datum.values = this.removeIdFromCollection(datum.values, id);
}
return datum.id !== id;
});
}
removeItem = (e, id) => {
const { filters } = this.state;
this.setState({
filters: this.removeIdFromCollection(filters, id),
});
};
The problem would be the structure of the object. You'll need to refactor for that inconvenient array out of nowhere for uniformity:
// Example
filters: [
...
{
key: "type",
values: [
{
type: "receipts",
label: "Loyer",
value: "loyer",
id: 4
},
...
]
...
}
// could be
filters: [
...
{
key: "type-receipts",
label: "Loyer",
value: "loyer",
id: 4
}
...
]
Repeat the pattern on all of it so you could just use the native array filter like this:
const newFilters = filters.filter(v => v.id !== id);
this.setState({
filters: newFilters,
});
I found a solution with lodash, I share it with you here :
removeIdFromCollection = id => {
const { filters } = this.state;
const newFilters = [];
_.map(filters, filter => {
if (filter.values) {
const valuesTmp = _.compact(
_.map(filter.values, value => {
if (value.id !== id) return value;
})
);
if (!_.isEmpty(valuesTmp)) {
return newFilters.push({
key: filter.key,
values: valuesTmp
});
}
}
if (filter.id && filter.id !== id) return newFilters.push(filter);
});
return newFilters;
};
removeItem = id => e =>
this.setState({
filters: this.removeIdFromCollection(id)
});
Here is my updated codesandbox

Merge array of objects with underscore

I have array of objects like this. And they have duplicated property 'contactName' values
[
{
categoryId:1
categoryName:"Default"
contactId:141
contactName:"Anonymous"
name:"Mobile"
value:"+4417087654"
},
{
categoryId:1
categoryName:"Default"
contactId:325
contactName:"Anonymous"
name:"Email"
value:"test2#gmail.com"
},
{
categoryId:1
categoryName:"Default"
contactId:333
contactName:"Anonymous"
name:"Email"
value:"ivdtest#test.com"
}
]
I want to merge them in one object by the name of property 'contactName'
To something like this
[
{
categoryId: 1,
categoryName: "Default",
contactId: 141,
contactName: "Anonymous",
names: {
1: "Mobile",
2: "Email",
3: "Email"
},
values: {
1: '+2234324',
2: "ivdtest#test.com",
3: "test2#gmail.com"
}
}
];
Edit: How can I group objects also by categoryName ?
var grouped = _.groupBy(input, 'contactName');
var output = _.map(grouped, function(entries) {
return _.extend(
_.pick(entries[0], 'categoryId', 'categoryName', 'contactId', 'contactName'),
{
names: _.indexBy(_.pluck(entries, 'name'), function(val, index) { return index +1; }),
values: _.indexBy(_.pluck(entries, 'value'), function(val, index) { return index +1; })
}
);
});
https://jsfiddle.net/f1x4tscu/3/
Another variant with array inside the object
var grouped = _.groupBy(this.contacts, 'contactName');
var output = _.map(grouped, function (entries) {
return _.extend(
_.pick(entries[0], 'categoryId', 'categoryName', 'contactId', 'contactName'),
{
addresses: _.map(entries, function (m) {
return {
name: m.name,
value: m.value
}
}),
}
);
});

Categories

Resources