Array.forEach() and Array.slice() together doesn't work correctly [duplicate] - javascript

This question already has answers here:
What is the most efficient way to deep clone an object in JavaScript?
(67 answers)
Closed 6 years ago.
it's supposed to Array.slice() let me to make a copy of an array, and then I can modify that copy without modifying the original array, but when I use Array.forEach() over the copy for delete some values this values also are removed from the original array. Does anybody have an idea why this happens?
Here is the code that I've used:
var originalArray = [
{ id: 1, name: 'Sales', datasources: [
{ id:1 , name: 'datasource1', fields: [] },
{ id:2 , name: 'datasource2', fields: [] },
] },
{ id: 4, name: 'Accounts', datasources: [
{ id:3 , name: 'datasource3', fields: [] },
{ id:4 , name: 'datasource4', fields: [] },
] },
{ id: 123, name: 'my datasources', datasources: [
{ id:1 , name: 'datasource1', fields: [] },
{ id:2 , name: 'datasource2', fields: [] },
{ id:3 , name: 'datasource3', fields: [] },
{ id:4 , name: 'datasource4', fields: [] },
] },
{ id: 12, name: 'shared datasources', datasources: [
{ id:13 , name: 'myshared datasource', fields: [] },
{ id:16 , name: 'hello test', fields: [] },
] },
];
var copyOfOriginalArray = originalArray.slice();
copyOfOriginalArray.forEach((folder, index) => {
folder.datasources = folder.datasources.filter((o) => { return o.name.trim().toLowerCase().includes('hello'); });
});
JSON.stringify(originalArray);
JSON.stringify(copyOfOriginalArray);

Acording to this definition.
The slice() method returns a shallow copy of a portion of an array into a new array object.
Shallow copy is a bit-wise copy of an object. A new object is created that has an exact copy of the values in the original object. If any of the fields of the object are references to other objects, just the reference addresses are copied i.e., only the memory address is copied.
for deep copying any object in javascript you can use this function:
function deepCopy(oldObj) {
var newObj = oldObj;
if (oldObj && typeof oldObj === 'object') {
newObj = Object.prototype.toString.call(oldObj) === "[object Array]" ? [] : {};
for (var i in oldObj) {
newObj[i] = deepCopy(oldObj[i]);
}
}
return newObj;
}

Slice will create a copy of the array itself, but it will not clone the objects in the array (those will still be references).
You will need to recursively clone your array and its contents or use something like Lodash's cloneDeep

Related

Javascript function that merges objects based on values of the keys and restructures them based on values of the keys

I am trying to write a javascript function that receives at least two parameters - array of objects and empty nested object.
Array of objects contain objects that are not nested. All of them have same keys, but some keys might have different values. Each key name is something like "primary key" on certain level of nesting. Where the key should hold place as "primary key" is defined by empty nested object.
Empty nested object defines which key is "primary key" for which level of nesting. It defines the structure we need the final merged object to be.
The function should find and merge together all objects in array of objects, that have same value for the key thats first in empty nested object AND is type of object. However, because the objects hold other keys that have the same name but might have different values, if merged instantly, we would lose the different values.
So it takes value of last key that was found in empty nested object and was type of object, and creates a new key with name of that value and makes it a object. Inside it, it creates the name of key that this object is from, creates the key with same name and assigns it that value.
Then it keeps doing the same, based on empty nested object. In other words, the function is keep looking on empty nested object and checks if on the current level there is any other key thats type of object, before merging all objects together. It is merging the keys with same values from the bottom up.
All keys dont necessarily have to be "primary keys" on some level. These keys are present inside "DATA" level. In "DATA" might also appear a key that was a "primary key" on some level. Again, this is defined by empty nested object.
Example of empty nested object:
{
PERSON: {
CRT: {
CRT_VALUE: {
CRT: null,
CODE: {
CODE_VALUE: {
CODE: null,
DATA: {
CODE: {
VALUE: null,
META: null,
NAME: null
},
NAME: {
VALUE: null,
META: null,
NAME: null
},
SUM: {
VALUE: null,
META: null,
NAME: null
},
CONTEXT: {
VALUE: null,
META: null,
NAME: null
}
}
}
}
}
}
}
}
Example of array of objects:
[
{
PERSON: "21",
CODE: "CMSS",
CONTEXT: "",
NAME: "SALARY",
CRT: "Common",
SUM: 162000
},
{
PERSON: "21",
CODE: "DOS",
CONTEXT: "",
NAME: "TAX",
CRT: "Common",
SUM: 192000
},
{
PERSON: "21",
CODE: "UPL",
CONTEXT: "",
NAME: "WAGE",
CRT: "Other",
SUM: 255000
},
{
PERSON: "22",
CODE: "CMSS",
CONTEXT: "",
NAME: "SALARY",
CRT: "Common",
SUM: 150000
}
]
The final array of objects should be merged based on values of the keys and based on empty nested object.
For given examples, the final array of objects should look like this:
[
{
PERSON: "21",
CRT: {
"Common": {
CRT: "Common",
CODE: {
"CMSS": {
CODE: "CMSS",
DATA: {
NAME: {
VALUE: "SALARY",
META: undefined,
NAME: "NAME",
},
SUM: {
VALUE: 162000,
META: undefined,
NAME: "SUM",
},
CONTEXT: {
VALUE: "",
META: undefined,
NAME: "CONTEXT",
},
CODE: {
VALUE: "CMSS",
META: undefined,
NAME: "CODE",
}
}
},
"DOS": {
CODE: "DOS",
DATA: {
NAME: {
VALUE: "TAX",
META: undefined,
NAME: "NAME",
},
SUM: {
VALUE: 192000,
META: undefined,
NAME: "SUMA",
},
CONTEXT: {
VALUE: "",
META: undefined,
NAME: "KONTEXT",
},
CODE: {
VALUE: "DOS",
META: undefined,
NAME: "CODE",
}
}
}
},
},
"Other": {
CRT: "Other",
CODE: {
"UPL": {
CODE: "UPL",
DATA: {
NAME: {
VALUE: "WAGE",
META: undefined,
NAZEV: "NAME",
},
SUM: {
VALUE: 255000,
META: undefined,
NAZEV: "SUM",
},
CONTEXT: {
VALUE: "",
META: undefined,
NAME: "CONTEXT",
},
CODE: {
VALUE: "UPL",
META: undefined,
NAME: "CODE",
}
}
}
}
}
}
},
{
PERSON: "22",
CRT: {
"Common": {
CRT: "Common",
CODE: {
"CMSS": {
CODE: "CMSS",
DATA: {
NAME: {
VALUE: "SALARY",
META: undefined,
NAME: "NAME",
},
SUM: {
VALUE: 150000,
META: undefined,
NAME: "SUM",
},
CONTEXT: {
VALUE: "",
META: undefined,
NAME: "CONTEXT",
},
CODE: {
VALUE: "CMSS",
META: undefined,
NAME: "CODE",
}
}
}
}
}
}
}
]
The function should be generic, so it works for any given array of objects, and any given empty nested object - however, empty nested object must always have defined structure by the key names.
The array of objects must (will) always contain all the needed keys based on empty nested object.
What I tried and did not work:
const mergeObjectsByKey = (array, key, newObject = {}, level = 1) => {
let newObjects = []
array.forEach(obj => {
let currentObject = newObject;
for (let k of Object.keys(key)) {
if (typeof key[k] === 'object') {
if (obj.hasOwnProperty(k)) {
if (!currentObject.hasOwnProperty(obj[k])) {
currentObject[obj[k]] = {};
}
currentObject = currentObject[obj[k]];
mergeObjectsByKey([obj], key[k], currentObject, level + 1);
}
} else {
if (Object.keys(key)[Object.keys(key).length - 1] === k) {
currentObject["HODNOTA"] = obj[k];
currentObject["META"] = null;
currentObject["NAZEV"] = k;
} else {
if (!currentObject.hasOwnProperty(k)) {
currentObject[k] = obj[k];
}
}
}
}
newObjects.push(newObject);
});
return newObjects;
}
newObject - the object that will be returned as the final object
level - a number that keeps track of the current level of nesting in the key object. It is used to determine if the current key in the empty nested object is a parent or child key
The function loops through each key in the empty nested object. If the current key is an object, it loops through each object in the array of objects and checks if the object has a key with the same name as the current key. If it does, it calls the function recursively with the current object, current key and current level+1 as the parameters. By doing this, it will check the next level of key object and compare it with the next level of the array of objects.
In the next level, it will again check if the key is an object or not. If it is an object, it will check if the key is already present in the current object or not. If it is not present, it will create a new property with the key and make it equal to an empty object.
Then it will call the function recursively with the current object, current key, and current level+1 as the parameters. If the key is not an object, it will check if it is the last key of the key object or not.
If it is the last key, it will add three properties HODNOTA, META and NAZEV to the current object.
If it is not the last key, it will add the key to the current object and make it equal to an array of values of the same key in the array of objects.
What I expected:
the function will return final merged and structured objects in an array
What I got:
array of objects of objects. Each object in array contain same number of objects, and all these objects in the array are same.
Inside objects of the array, each object has the name of value of "PERSON" that was used to merging.
In each PERSON object, there are objects named by all CRT values found for that PERSON value. Each CRT object is then empty.
I can see that partially it was merged succesfully, but from CRT the merging failed and lost the data.
Not sure it's perfect, but it does something close. The technique here starts with Object.fromEntries(Object.entries(obj).map(...)) to modify the keys and values and build a new object based on the template object.
const TPL = {PERSON:null,CRT:{CRT_VALUE:{CRT:null,CODE:{CODE_VALUE:{CODE:null,DATA:{CODE:{VALUE:null,META:null,NAME:null},NAME:{VALUE:null,META:null,NAME:null},SUM:{VALUE:null,META:null,NAME:null},CONTEXT:{VALUE:null,META:null,NAME:null}}}}}}};
const values = [{PERSON:"21",CODE:"CMSS",CONTEXT:"",NAME:"SALARY",CRT:"Common",SUM:162000},{PERSON:"21",CODE:"DOS",CONTEXT:"",NAME:"TAX",CRT:"Common",SUM:192000},{PERSON:"21",CODE:"UPL",CONTEXT:"",NAME:"WAGE",CRT:"Other",SUM:255000},{PERSON:"22",CODE:"CMSS",CONTEXT:"",NAME:"SALARY",CRT:"Common",SUM:150000}];
function fillTpl(tpl, values, parentKey = '') {
return Object.fromEntries(Object.entries(tpl).map(([k, v]) => {
if (k.endsWith('_VALUE')) k = values[k.slice(0, -6)];
if (v == null) v = (k === 'VALUE') ? values[parentKey] : values[k];
else v = fillTpl(v, values, k);
return [k, v];
}));
}
console.log(values.map(vals => fillTpl(TPL, vals)));
If you want to add new properties to the object, you can do like Object.fromEntries(Object.entries(obj).flatMap(([k, v]) => [[k, v], [k+'_2', v]]))

Return all values of nested arrays using string identifier

Given an object searchable, is there a simple way of returning all the id values using lodash or underscore.js (or equivalent) where I can define the path to id?
const searchable = {
things: [
{
id: 'thing-id-one',
properties: [
{ id: 'd1-i1' },
{ id: 'd1-i2' },
]
},
{
id: 'thing-id-two',
properties: [
{ id: 'd2-i1' },
{ id: 'd2-i2' },
]
}
]
}
I am looking to see if this is possible in a manner similar to how we can use lodash.get e.g. if we wanted to return the things array from searchable we could do
const things = _.get(searchable, 'things');
I can't seem to find anything similar in the documentation. I am looking for something
that could contain an implementation similar to:
_.<some_function>(searchable, 'things[].properties[].id')
Note: I am well aware of functions like Array.map etc and there are numerous ways of extracting the id property - it is this specific use case that I am trying to figure out, what library could support passing a path as a string like above or does lodash/underscore support such a method.
Found a solution using the package jsonpath
const jp = require('jsonpath');
const result = jp.query(searchable, '$.things[*].properties[*].id')
console.log(result);
// outputs: [ 'd1-i1', 'd1-i2', 'd2-i1', 'd2-i2' ]
you can do it easily in plain js
like this
const searchable = {
things: [
{
id: 'thing-id-one',
properties: [
{ id: 'd1-i1' },
{ id: 'd1-i2' },
]
},
{
id: 'thing-id-two',
properties: [
{ id: 'd2-i1' },
{ id: 'd2-i2' },
]
}
]
}
const search = (data, k) => {
if(typeof data !== 'object'){
return []
}
return Object.entries(data).flatMap(([key, value]) => key === k ? [value]: search(value, k))
}
console.log(search(searchable, 'id'))
_.map and _.flatten together with iteratee shorthands let you expand nested properties. Every time you need to expand into an array, just chain another map and flatten:
const searchable = {
things: [
{
id: 'thing-id-one',
properties: [
{ id: 'd1-i1' },
{ id: 'd1-i2' },
]
},
{
id: 'thing-id-two',
properties: [
{ id: 'd2-i1' },
{ id: 'd2-i2' },
]
}
]
}
// Let's say the path is "things[].properties[].id"
const result = _.chain(searchable)
.get('things').map('properties').flatten()
.map('id').value();
console.log(result);
<script src="https://cdn.jsdelivr.net/npm/underscore#1.13.4/underscore-umd-min.js"></script>

Trying to return a filter() value from an array of objects based on conditionals

I'm trying to return only a part of my object as an array based on conditionals but I'm kinda stumped.
I have an array of objects and I want to return an array of names for each key : value they fit in.
I do get only the unique pairings but I'm returning the food key:value as well and all of it is still inside an object not a new array. Some insight would be greatly appreciated. Newer to coding.
const organizeNames = function (foods) {
let foodNames = foods.filter((names) => {
if (names.food === 'oranges') {
return names.name;
}
});
console.log(foodNames);
};
console.log(
organizeNames([
{ name: 'Samuel', food: 'oranges' },
{ name: 'Victoria', food: 'pizza' },
{ name: 'Karim', food: 'pizza' },
{ name: 'Donald', food: 'pizza' },
])
);
You're really close here. What you need to incorporate is .map() to map your list of objects to a list of names. Your filter part works, partly by accident, so I've fixed it to be more correct (return names.food === 'oranges'), and then once you have your list of objects that match 'oranges' for their food, you map that filtered list into a list of names by doing .map(names => names.name)
const organizeNames = function (foods) {
let foodNames = foods.filter((names) => {
// only keep items whose .food property === 'oranges'
return names.food === 'oranges'; // returns true or false
}).map(names => {
// map the filtered list of objects into a list of names by
// returning just the object's .name property
return names.name;
});
return foodNames;
};
console.log(
organizeNames([
{ name: 'Samuel', food: 'oranges' },
{ name: 'Victoria', food: 'pizza' },
{ name: 'Karim', food: 'pizza' },
{ name: 'Donald', food: 'pizza' },
])
);
The filter() callback function should just return true or false. Then you can use map() to get a specific property from each of the filtered items.
const organizeNames = function(foods) {
let foodNames = foods.filter((names) => names.food == 'oranges').map(names => names.name);
return foodNames;
}
console.log(
organizeNames([{
name: 'Samuel',
food: 'oranges'
},
{
name: 'Victoria',
food: 'pizza'
},
{
name: 'Karim',
food: 'pizza'
},
{
name: 'Donald',
food: 'pizza'
},
])
);

Javascript: How to create nested from a linear array based on parent ID [duplicate]

This question already has answers here:
Recursive depth function on an array
(6 answers)
Closed 1 year ago.
I have an array of categories as:
var categories = [
{
name: 'Books',
slug: 'books',
parent_name: null,
},
{
name: 'Fiction',
slug: 'books/fiction',
parent_name: 'Books'
},
{
name: 'Romance fiction',
slug: 'books/fiction/romance',
parent_name: 'Fiction'
},
{
name: 'Bags',
slug: 'bags',
parent_name: null
}
];
I want to make this array into nested array/object so I can render them in dropdowns menu (upto two levels of dropdowns). But do not have any idea how to do it.
Something like this:
var nestedCatefories = [
{
name: 'Books',
slug: 'books',
parent_name: null,
children: [
{
name: 'Fiction',
slug: 'books/fiction',
parent_name: 'books',
children: [
{
name: 'Romance',
slug: 'books/fiction/romance',
parent_name: 'Fiction'
}
]
}
]
},
{
name: 'Bags',
slug: 'bags',
parent_name: null
}
];
How do I change this linear array to nested array?
Reduce the array to a Map, with a predefined root array. For each new item, add it to the Map using it's name as the key. Then get / initialize it's parent's children array, or use root for items without parents. Add the item to it's parent. At the end get the root that hold the entire tree:
const categories = [{"name":"Books","slug":"books","parent_name":null},{"name":"Fiction","slug":"books/fiction","parent_name":"Books"},{"name":"Romance fiction","slug":"books/fiction/romance","parent_name":"Fiction"},{"name":"Bags","slug":"bags","parent_name":null}];
const result = categories.reduce((acc, item) => {
acc.set(item.name, item)
const parent = item.parent_name === null
? acc.get('root')
: (acc.get(item.parent_name).children ??= [])
parent.push(item)
return acc
}, new Map([['root', []]])).get('root')
console.log(result)
Have you tried using a simple Array.map()?
Something like this:
var nestedArr = linearArr.map(obj=>obj.children= [{obj},{obj}])

json object from javascript nested array

I'm using a nested array with the following structure:
arr[0]["id"] = "example0";
arr[0]["name"] = "name0";
arr[1]["id"] = "example1";
arr[1]["name"] = "name1";
arr[2]["id"] = "example2";
arr[2]["name"] = "name2";
now I'm trying to get a nested Json Object from this array
arr{
{
id: example0,
name: name00,
},
{
id: example1,
name: name01,
},
{
id: example2,
name: name02,
}
}
I tought it would work with JSON.stringify(arr); but it doesen't :(
I would be really happy for a solution.
Thank you!
If you are starting out with an array that looks like this, where each subarray's first element is the id and the second element is the name:
const array = [["example0", "name00"], ["example1", "name01"], ["example2", "name02"]]
You first need to map it to an array of Objects.
const arrayOfObjects = array.map((el) => ({
id: el[0],
name: el[1]
}))
Then you can call JSON.stringify(arrayOfObjects) to get the JSON.
You need to make a valid array:
arr = [
{
id: 'example0',
name: 'name00',
},
{
id: 'example1',
name: 'name01',
},
{
id: 'example2',
name: 'name02',
}
];
console.log(JSON.stringify(arr));
Note that I am assigning the array to a variable here. Also, I use [] to create an array where your original code had {}.

Categories

Resources