JS: append array of objects with data from another - javascript

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));

Related

How to restructure object array to nested arrays?

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.

make object structure recursive structure with same keys in javascript

Currently, I have this object
const path ={
"posts": {
"backend": {
"a.mdx": "./pages/posts/backend/a.mdx"
},
"frontend": {},
"retrospective": {
"b.mdx": "./pages/posts/retrospective/b.mdx",
"c.mdx": "./pages/posts/retrospective/c.mdx",
"d.mdx": "./pages/posts/retrospective/d.mdx"
}
}
}
What I want is..
const path = [{
title: 'posts',
sub: [
{
title: 'backend',
sub: [
{ title: 'a.mdx', path: './pages/posts/backend/a.mdx' },
],
},
{
title: 'frontend',
sub: [],
},
{
title: 'retrospective',
sub: [
{ title: 'b.mdx', path: './pages/posts/retrospective/b.mdx' },
{ title: 'c.mdx', path: './pages/posts/retrospective/c.mdx' },
{ title: 'd.mdx', path: './pages/posts/retrospective/d.mdx' },
],
},
],
}];
In this situation, how can I make the structure recursively?
I've read the lodash library document but I couldn't find a good combination to handle this issue.
Can I get any hint for this?
You can create recursive function and run it again if value is object, or set path if it is string. Check inline comments:
// Object
const path = {
"posts": {
"backend": {
"a.mdx": "./pages/posts/backend/a.mdx"
},
"frontend": {},
"retrospective": {
"b.mdx": "./pages/posts/retrospective/b.mdx",
"c.mdx": "./pages/posts/retrospective/c.mdx",
"d.mdx": "./pages/posts/retrospective/d.mdx"
}
}
};
// Recursive function
const recursiveFn = data => {
// Set array for function runtime result
const res = [];
// Iterate through keys in your object
for(const key in data) {
// If value is object, process it
// again in recursion
if(typeof data[key] === 'object' && data[key] !== null) {
res.push({
"title": key,
"sub": recursiveFn(data[key])
});
// If value is string
} else if(typeof data[key] === 'string') {
res.push({
"title": key,
"path": data[key]
});
}
}
// Return result
return res;
}
// Run test
console.log(recursiveFn(path));

How to update deeply nested array of objects?

I have the following nested array of objects:
const data = [
{
product: {
id: "U2NlbmFyaW9Qcm9swkdWN0OjEsz",
currentValue: 34300,
},
task: {
id: "R2VuZXJpY1Byb2R1Y3Q6MTA",
name: "My Annuity",
},
instrumentDetails: [
{
instrument: {
id: "U2NlbmFyaW9JbnN0cnVtZW50OjEz",
supplier: {
id: "U3VwcGxpZXJJbnN0cnVtZW50OjUzNjQ",
supplierDetails: {
name: "Local - Class A",
},
},
currentValue: 44323,
},
assets: {
current: 1.2999270432626702,
fixed: 0.5144729302004819,
financial: 0.0723506386331588,
cash: 0.00006003594786398524,
alternative: 0.05214078143244779,
property: 0.548494862567579,
local: 0.10089348539739094,
global: 0,
},
},
],
},
{
product: {
id: "U2NlbmFyaW9Qcm9swkfefewdWN0OjEsz",
currentValue: 3435300,
},
task: {
id: "R2VuZXJpYfewfew1Byb2R1Y3Q6MTA",
name: "Living",
},
instrumentDetails: [
{
instrument: {
id: "U2NlbmFyadewwW9JbnN0cnVtZW50OjEz",
supplier: {
id: "U3VwcGxpZdwdwXJJbnN0cnVtZW50OjUzNjQ",
supplierDetails: {
name: "Local - Class B",
},
},
currentValue: 434323,
},
assets: {
current: 1.294353242,
fixed: 0.514434242004819,
financial: 0.07434286331588,
cash: 0.0000434398524,
alternative: 0.05242348143244779,
property: 0.543242567579,
local: 0.100432439739094,
global: 0,
},
},
],
},
];
The above data presents an array of products which consist of instruments that are described in instrumentDetails array. I am trying to find an instrument by supplier id and update its assets by multiplying all of the asset values by a given number.
Here is my function:
export const updateObject = (
productsArr: any,
supplierInstrumentId: string
) => {
return productsArr.map(
(product: any) => {
product.instrumentDetails.map(
(instrumentDetail: any) => {
if (
instrumentDetail.instrument.supplier.id ===
supplierInstrumentId
) {
instrumentDetail.assets.current = instrumentDetail.assets.current + 5;
instrumentDetail.assets.fixed= instrumentDetail.assets.fixed+ 5;
instrumentDetail.assets.financial= instrumentDetail.assets.financial+ 5;
instrumentDetail.assets.cash= instrumentDetail.assets.cash+ 5;
}
}
);
}
);
};
This function is giving an error :
Uncaught TypeError: Cannot assign to read only property 'current' of
object '#'
How can I deeply update the above data? Please help.
You need to return a new instrumentDetail-type object from the map function. Don't try to update the existing object.
(instrumentDetail: any) => {
const assets = instrumentDetail.instrument.supplier.id === supplierInstrumentId
? Object.fromEntries(
Object.entries(instrumentDetail.assets).map(([k, v]) => [k, v + 5])
)
:
instrumentDetail.assets;
return {
...instrumentDetail,
assets
};
}
Your product map is not returning which is why you're likely getting an undefined. I wasn't getting the typescript error which you mentioned above. This should leave the array in the state at which you intended.
const updateObject = (
productsArr: any,
supplierInstrumentId: string
) => {
return productsArr.map(
(product: any) => {
product.instrumentDetails.map(
(instrumentDetail: any) => {
if (
instrumentDetail.instrument.supplier.id ===
supplierInstrumentId
) {
instrumentDetail.assets.current += 5;
instrumentDetail.assets.fixed= instrumentDetail.assets.fixed+ 5;
instrumentDetail.assets.financial= instrumentDetail.assets.financial+ 5;
instrumentDetail.assets.cash= instrumentDetail.assets.cash+ 5;
return instrumentDetail;
}
}
);
return product;
}
);
};

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

Sort array of objects by property length

I have this array:
var itemList = [
{
image: "images/home.jpg",
name: "Home"
},
{
name: "Elvis",
},
{
name: "Jonh"
},
{
image: "images/noah.jpg",
name: "Noah"
},
{
name: "Turtle"
}
]
How can I organize the array to objects with image property come first, so that it looks like this?:
var itemList = [
{
image: "images/home.jpg",
name: "Home"
},
{
image: "images/noah.jpg",
name: "Noah"
},
{
name: "Elvis",
},
{
name: "Jonh"
},
{
name: "Turtle"
}
]
This code put at the beginning elements that have the property 'image'. Other elements stay in the same order.
function compare(a,b) {
if ('image' in a) {
return 1;
} else if ('image' in b) {
return -1;
} else {
return 0;
}
}
itemList.sort(compare);
Try this:
function compare(a,b) {
if (a.image && b.image)
return 0;
if (a.image)
return 1;
return -1;
}
objs.sort(compare);
A bit late, but an alternative:
itemList.sort(function(e1,e2){ return (e1.image === undefined) - (e2.image === undefined); });

Categories

Resources