I need to get all "id" and "name" of a big collection I have. An array ob objects, that has array with objects (etc).
I can't get it to work without manually doing foreaches for as many levels there is, which is ugly.
I am using Lodash, so a solution using this would be lovely.
This is how the collection looks like:
[
{
"id": 1,
"name": "Living",
"numberStories": 0,
"subcategories": {
"data": [
{
"id": 2,
"name": "Fashion",
"numberStories": 0,
"subcategories": {
"data": [
{
"id": 3,
"name": "Accessories",
"numberStories": 0,
"subcategories": {
"data": [
]
}
},
{
"id": 4,
"name": "Kid's Fashion",
"numberStories": 0,
"subcategories": {
"data": [
]
}
}, (... etc)
So it needs to look in subcategories in each array of objects and collect id and name, so that I end up with all the ids names from all levels.
Thanks.
As above, this is probably easier to do with native javascript as it's recursive. This is the same idea as the previous answer, but using some cool features of ES6 (destructuring, rest params, arrow functions, default parameters), and it will save a reference to the parent id so you can rebuild the category tree if you need.
const categories=[{id:1,name:"Living",numberStories:0,subcategories:{data:[{id:2,name:"Fashion",numberStories:0,subcategories:{data:[{id:3,name:"Accessories",numberStories:0,subcategories:{data:[]}},{id:4,name:"Kid's Fashion",numberStories:0,subcategories:{data:[]}}]}}]}}];
const flattenCategories = (categories, parent = null, ret = []) =>
categories.reduce((acc, { id, name, subcategories: { data } }) =>
acc.concat(
{ id, name, parent },
...flattenCategories(data, id)
)
, ret)
console.log(
flattenCategories(categories)
)
.as-console-wrapper { top: 0; max-height: 100% !important }
There's no need to use lodash for this purpose. A simple recursive function using .reduce() does the job for you
function flattenId(inArray){
return inArray.reduce(function(output, elem){
output.push(elem.id);
return output.concat(flattenId(elem.subcategories.data));
}, []);
}
Here is a solution using object-scan. Depending on your use case it might be overkill to use a library, but if so desired it can give you a lot more flexibility
// const objectScan = require('object-scan');
const categories = [{ id: 1, name: 'Living', numberStories: 0, subcategories: { data: [{ id: 2, name: 'Fashion', numberStories: 0, subcategories: { data: [{ id: 3, name: 'Accessories', numberStories: 0, subcategories: { data: [] } }, { id: 4, name: "Kid's Fashion", numberStories: 0, subcategories: { data: [] } }] } }] } }];
const r = objectScan(['**[*].id'], {
filterFn: ({ context, value, parents }) => {
context.push({
id: value,
name: parents[0].name,
parent: parents.length > 2 ? parents[3].id : null
});
}
})(categories, []);
console.log(r);
// => [ { id: 4, name: "Kid's Fashion", parent: 2 }, { id: 3, name: 'Accessories', parent: 2 }, { id: 2, name: 'Fashion', parent: 1 }, { id: 1, name: 'Living', parent: null } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan
Related
I have a nested object that looks like this
const test = {
cat1: {
id: "c1",
name: "category1",
items: [
{
itemName: "item1",
points: 1,
used: true
},
{
itemName: "item2",
points: 3,
used: false
},
{
itemName: "item3",
points: 5,
used: true
}
]
},
cat2: {
id: "c2",
name: "category2",
items: [
{
itemName: "item4",
points: 7,
used: true
},
{
itemName: "item5",
points: 9,
used: false
}
]
},
cat3: {
id: "c3",
name: "category3",
items: [
{
itemName: "item6"
}
]
}
};
These items are then drawn as checkbox with used property as its checked value. So whenever I click on Select All all the items having used property inside the objects should be set to true. When Unselect All, all the items used property should be set to false. I prefer having just one function that basically takes type as Select All or Unselect All as key and a category name so that items under that name will be set to either true or false
Here is my try
function handleOperation(id, type){
const output = Object.fromEntries(
Object.entries(test)
.map(([k, { items, ...rest }]) => [
k,
{
...rest,
items : type === 'Select All' ?
items[k] = items[k].map(item => {
item.used = true;
return item;
}) :
items[k] = items[k].map(item => {
item.used = false;
return item;
})
}
]);
);
return output;
}
console.log(handleOperation('category1', 'Select All'));
console.log(handleOperation('category2', 'Unselect All'));
So when I pass handleOperation('category1', 'Select All')
It should give me, since category1 items should be updated to true
const test = {
cat1: {
id: "c1",
name: "category1",
items: [
{
itemName: "item1",
points: 1,
used: true
},
{
itemName: "item2",
points: 3,
used: true
},
{
itemName: "item3",
points: 5,
used: true
}
]
},
cat2: {
id: "c2",
name: "category2",
items: [
{
itemName: "item4",
points: 7,
used: true
},
{
itemName: "item5",
points: 9,
used: false
}
]
},
cat3: {
id: "c3",
name: "category3",
items: [
{
itemName: "item6"
}
]
}
}
Since you want to clone the objects (although you could avoid cloning the ones that don't change; probably an unnecessary optimization), you're on the right track with Object.fromEntries, Object.entries, and map. But you seem to be using the wrong property names, and the code doesn't have to be as complicated as shown.
function handleOperation(type) {
// Get a flag for `type`
const used = type === "Select All";
// Build the updated objects
return Object.fromEntries(
Object.entries(test).map(([key, { items, ...rest }]) => [
key,
{
// Copy the rest of this object
...rest,
// Copy its array while mapping the objects
items: items.map((item) => ({
// Copy `items`'s properties
...item,
// Set the flag from our `used `variable
used,
})),
},
])
);
}
const test = {
cat1: {
id: "c1",
name: "category1",
items: [
{
itemName: "item1",
points: 1,
used: true,
},
{
itemName: "item2",
points: 3,
used: false,
},
{
itemName: "item3",
points: 5,
used: true,
},
],
},
cat2: {
id: "c2",
name: "category2",
items: [
{
itemName: "item4",
points: 7,
used: true,
},
{
itemName: "item5",
points: 9,
used: false,
},
],
},
cat3: {
id: "c3",
name: "category3",
items: [
{
itemName: "item6",
},
],
},
};
function handleOperation(type) {
// Get a flag for `type`
const used = type === "Select All";
// Build the updated objects
return Object.fromEntries(
Object.entries(test).map(([key, { items, ...rest }]) => [
key,
{
// Copy the rest of this object
...rest,
// Copy its array while mapping the objects
items: items.map((item) => ({
// Copy `items`'s properties
...item,
// Set the flag from our `used `variable
used,
})),
},
])
);
}
console.log("Select All:");
console.log(handleOperation("Select All"));
console.log("Unselect All:");
console.log(handleOperation("Unselect All"));
.as-console-wrapper {
max-height: 100% !important;
}
In a comment you've asked:
Thanks a ton. If I want to modify items array with category name being passed. What could be modified? Something like calling handleOperation('category1', 'Select All')), So that only category1 items objects are set to true
One way we could do that is to just avoid calling map on items if the name of the category doesn't match, using the existing items array:
function handleOperation(categoryName, type) {
// Get a flag for `type`
const used = type === "Select All";
// Build the updated objects
return Object.fromEntries(
Object.entries(test).map(([key, { name, items, ...rest }]) => [
key,
{
// Copy the name
name,
// Copy the rest of this object
...rest,
// If the name doesn't match, reuse `items`; if it does match,
// copy its array while mapping the objects
items:
name !== categoryName
? items
: items.map((item) => ({
// Copy `items`'s properties
...item,
// Set the flag from our `used `variable
used,
})),
},
])
);
}
const test = {
cat1: {
id: "c1",
name: "category1",
items: [
{
itemName: "item1",
points: 1,
used: true,
},
{
itemName: "item2",
points: 3,
used: false,
},
{
itemName: "item3",
points: 5,
used: true,
},
],
},
cat2: {
id: "c2",
name: "category2",
items: [
{
itemName: "item4",
points: 7,
used: true,
},
{
itemName: "item5",
points: 9,
used: false,
},
],
},
cat3: {
id: "c3",
name: "category3",
items: [
{
itemName: "item6",
},
],
},
};
function handleOperation(categoryName, type) {
// Get a flag for `type`
const used = type === "Select All";
// Build the updated objects
return Object.fromEntries(
Object.entries(test).map(([key, { name, items, ...rest }]) => [
key,
{
// Copy the name
name,
// Copy the rest of this object
...rest,
// If the name doesn't match, reuse `items`; if it does match,
// copy its array while mapping the objects
items:
name !== categoryName
? items
: items.map((item) => ({
// Copy `items`'s properties
...item,
// Set the flag from our `used `variable
used,
})),
},
])
);
}
console.log("category1 - Select All:");
console.log(handleOperation("category1", "Select All"));
console.log("category1 - Unselect All:");
console.log(handleOperation("category1", "Unselect All"));
.as-console-wrapper {
max-height: 100% !important;
}
But that unnecessarily copies category objects. So let's avoid that as well:
function handleOperation(categoryName, type) {
// Get a flag for `type`
const used = type === "Select All";
// Build the updated objects
return Object.fromEntries(
Object.entries(test).map(([key, category]) => {
if (category.name !== categoryName) {
return [key, category];
}
const { name, items, ...rest } = category;
return [
key,
{
// Copy the name
name,
// Copy the rest of this object
...rest,
// Copy its array while mapping the objects
items: items.map((item) => ({
// Copy `items`'s properties
...item,
// Set the flag from our `used `variable
used,
})),
},
];
})
);
}
const test = {
cat1: {
id: "c1",
name: "category1",
items: [
{
itemName: "item1",
points: 1,
used: true,
},
{
itemName: "item2",
points: 3,
used: false,
},
{
itemName: "item3",
points: 5,
used: true,
},
],
},
cat2: {
id: "c2",
name: "category2",
items: [
{
itemName: "item4",
points: 7,
used: true,
},
{
itemName: "item5",
points: 9,
used: false,
},
],
},
cat3: {
id: "c3",
name: "category3",
items: [
{
itemName: "item6",
},
],
},
};
function handleOperation(categoryName, type) {
// Get a flag for `type`
const used = type === "Select All";
// Build the updated objects
return Object.fromEntries(
Object.entries(test).map(([key, category]) => {
if (category.name !== categoryName) {
return [key, category];
}
const { name, items, ...rest } = category;
return [
key,
{
// Copy the name
name,
// Copy the rest of this object
...rest,
// Copy its array while mapping the objects
items: items.map((item) => ({
// Copy `items`'s properties
...item,
// Set the flag from our `used `variable
used,
})),
},
];
})
);
}
console.log("category1 - Select All:");
console.log(handleOperation("category1", "Select All"));
console.log("category1 - Unselect All:");
console.log(handleOperation("category1", "Unselect All"));
.as-console-wrapper {
max-height: 100% !important;
}
I have an array that looks something like this:
const arrayObj = [
{
id: 1,
itemsList: [
{
name: "Paul",
},
{
name: "Newman",
},
],
},
{
id: 2,
itemsList: [
{
name: "Jack",
},
{
name: "Man",
},
],
},
]
What I want is to filter the objects whose itemsList contain an object with the name of a certain value. For example, I want to be able to filter out an array with objects whose inner objects with names that contain "ul" (in this case the name Paul contains "ul"), it should give me an output as such:
const outputArray = [
{
id: 1,
itemsList: [
{
name: "Paul",
},
{
name: "Newman",
},
]
}
]
So far, I've only been able to filter out a simple flat array of objects with this function:
function filterByName(array: any, string: any) {
return array.filter((obj: any) =>
["name"].some((key: any) =>
String(obj[key]).toLowerCase().includes(string.toLowerCase())
)
);
}
but I don't know how to apply it to my case.
Here you can use the some method combined with the includes method
const arrayObj = [{
id: 1,
itemsList: [{
name: "Paul",
},
{
name: "Newman",
},
],
},
{
id: 2,
itemsList: [{
name: "Jack",
},
{
name: "Man",
},
],
},
]
const getFilterArray = (name) => {
return arrayObj.filter(obj => obj.itemsList.some(x => x.name.toLowerCase().includes(name.toLowerCase())))
}
console.log(getFilterArray("ul"))
const result = arrayObj.filter(({ itemsList }) =>
itemsList.some(({ name }) => name.toLowerCase().includes('ul')));
Can you try this?
I'm trying to expand array in JavaScript.
The object ↓
const tests = [
{
id: 1,
name: 'taro',
designs: [
{
designId: 1,
designName: "design1"
},
{
designId: 2,
designName: "design2"
}
]
},
{
id: 2,
name: 'John',
designs: [
{
designId: 3,
designName: "design3"
},
{
designId: 4,
designName: "design4"
}
]
},
{
id: 3,
name: 'Lisa',
designs: []
},
];
[
{ id: 1, name: 'taro', designId: 1, designName: 'design1' },
{ id: 1, name: 'taro', designId: 2, designName: 'design2' },
{ id: 2, name: 'John', designId: 3, designName: 'design3' },
{ id: 2, name: 'John', designId: 4, designName: 'design4' },
{ id: 3, name: 'Lisa', designId: null, designName: null },
]
It is easy to do this using double for, but I want to use it with higher-order functions.
The code I wrote
for (let i = 0; i < tests.length; i++) {
for (let j = 0; j < tests[i].designs.length; j++) {
const id = tests[i].id
const name = tests[i].name
result.push({
id,
name,
designId: tests[i].designs[j].designId,
designName: tests[i].designs[j].designName
})
}
}
In addition, it would be appreciated if you could additionally explain the difference in performance between double for and higher-order functions.
You can use .flatMap() on your tests array with an inner .map() on each designs array. The inner map on the designs array will take the properties from the currently iterated design object and merge it with the properties from the parent object. The outer .flatMap() can then be used to concatenate all returned maps into the one array:
const tests = [ { id: 1, name: 'taro', designs: [ { designId: 1, designName: "design1" }, { designId: 2, designName: "design2" } ] }, { id: 2, name: 'John', designs: [ { designId: 3, designName: "design3" }, { designId: 4, designName: "design4" } ] }, ];
const res = tests.flatMap(({designs, ...rest}) => designs.map(design => ({
...rest,
...design
})));
console.log(res);
Edit:
If you need null values to appear for your design objects if your designs array is empty, you can add the keys explicitly to a new object that you can return when the designs array is empty:
const tests = [ { id: 1, name: 'taro', designs: [] }, { id: 2, name: 'John', designs: [] }, ];
const res = tests.flatMap(({designs, ...rest}) =>
designs.length
? designs.map(design => ({
...rest,
...design
}))
: {...rest, designId: null, designName: null}
);
console.log(res);
You can use an Array.reduce function with Array.map to generate the array:
const results = tests.reduce((acc, { designs, ...rest }) => [
...acc,
...designs.map(e => ({ ...rest, ...e }))
], []);
const tests = [
{
id: 1,
name: 'taro',
designs: [
{
designId: 1,
designName: "design1"
},
{
designId: 2,
designName: "design2"
}
]
},
{
id: 2,
name: 'John',
designs: [
{
designId: 3,
designName: "design3"
},
{
designId: 4,
designName: "design4"
}
]
},
];
const results = tests.reduce((acc, { designs, ...rest }) => [
...acc,
...designs.map(e => ({ ...rest, ...e }))
], []);
console.log(results);
You can use the higher-order function Array.prototype.reduce() with Array.prototype.map()
const newArr = tests.reduce((prev, {designs, ...current}) => [
...prev, ...designs.map(design => ({...design,...current}));
]
, []);
The performance in your approach and this higher-order approach is the same because Array.prototype.reduce runs through the whole array and just facilitates the initialValue approach for us.
I have a json object nested inside a param for parent json objects. I'm trying to access a property of the parent based on if an ID exists in the ID field of the child objects.
The JSON structure is as follows:
{
id: 1,
map: "test",
parameter_definitions: [{ID: 1, parameterUnits: "%"},{ID: 2, parameterUnits: "%"}],
},
{
id: 2,
map: "test2",
parameter_definitions: [{ID: 3, parameterUnits: "%"},{ID: 4, parameterUnits: "%"}],
}
The value I need to return is the map of the right json object based on if my given value is one of the IDs in the parameter_definitions object. I can't seem to wrap my head around what this consists of.
Maybe this function "getMapElementById" will help you:
let maps = [{
id: 1,
map: "test",
parameter_definitions: [{ID: 1, parameterUnits: "%"},{ID: 2, parameterUnits: "%"}],
},
{
id: 2,
map: "test2",
parameter_definitions: [{ID: 3, parameterUnits: "%"},{ID: 4, parameterUnits: "%"}],
}]
function getMapElementById( maps, requiredId ) {
for(let i=0;i<maps.length;i++){
for(let j=0;j<maps[i].parameter_definitions.length;j++){
if( maps[i].parameter_definitions[j].ID == requiredId ) {
return maps[i];
}
}
}
}
let mapResult = getMapElementById(maps, 3);
console.log(mapResult);
It's unclear whether multiple parent objects can have children object with the same id, but either way, you could use a combination of filter and find:
let objects = [
{
id: 1,
map: "test",
parameter_definitions: [{ ID: 1, parameterUnits: "%" }, { ID: 2, parameterUnits: "%" }],
},
{
id: 2,
map: "test2",
parameter_definitions: [{ ID: 3, parameterUnits: "%" }, { ID: 4, parameterUnits: "%" }],
}
];
let filteredObjects = objects.filter(object => object.parameter_definitions.find(param => param.ID === 2));
if (filteredObjects.length > 0) {
// If you know there will only be one parent object per child object ID, just return the first from the array
console.log(filteredObjects[0].map);
// If several parent objects can have the same child object ID, you can use the map function to get the map property of all the parent objects
console.log(filteredObjects.map(object => object.map));
}
Unfortunately your question is not very clear. Is this what you are looking for?
// const objectScan = require('object-scan');
const myData = [{ id: 1, map: 'test', parameter_definitions: [{ ID: 1, parameterUnits: '%' }, { ID: 2, parameterUnits: '%' }] }, { id: 2, map: 'test2', parameter_definitions: [{ ID: 3, parameterUnits: '%' }, { ID: 4, parameterUnits: '%' }] }];
const getRootElement = (data, id) => objectScan(['[*].parameter_definitions[*].ID'], {
abort: true,
filterFn: ({ value, parents, context }) => {
if (value === id) {
context.push(...parents);
return true;
}
return false;
}
})(data, [])[2];
console.log(getRootElement(myData, 5));
// => undefined
console.log(getRootElement(myData, 3));
// => { id: 2, map: 'test2', parameter_definitions: [ { ID: 3, parameterUnits: '%' }, { ID: 4, parameterUnits: '%' } ] }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan
I'm receiving a JSON from a Laravel API in this way:
[
{
"id":48,
"parentid":0,
"title":"Item 1",
"child_content":[
{
"id":49,
"parentid":48,
"title":"Itema 1.1",
},
{
"id":52,
"parentid":48,
"title":"Item 1.2",
}
]
},
{
"id":58,
"parentid":0,
"title":"Item 2",
"child_content":[
{
"id":59,
"parentid":58,
"title":"Itema 2.1",
},
{
"id":60,
"parentid":58,
"title":"Item 2.2",
}
]
}
]
and what I need is change the JSON into this:
{
"data":
[
{
"data":
{
"id":68,
"parentid":0,
"title":"Item 1"
},
"children":
[
{
"data":
{
"id":69,
"parentid":68,
"title":"Item 1.1"
},
},
{
"data":
{
"id":69,
"parentid":68,
"title":"Item 1.2"
}
}
]
}
]
}
I've been dealing with this... but I'm not able to find the way to do this properly...
How can I do this in PHP or Javascript / TypeScript (Angular 2).
Thank you in advance.
This should achieve your goal. Basically I'm just grabbing child_content, renaming it to children and copying the 3 other attributes. The children.map iteration is putting the existing data inside an object with a key of data:
const input = [{"id":48,"parentid":0,"title":"Item 1","child_content":[{"id":49,"parentid":48,"title":"Itema 1.1"},{"id":52,"parentid":48,"title":"Item 1.2"}]},{"id":58,"parentid":0,"title":"Item 2","child_content":[{"id":59,"parentid":58,"title":"Itema 2.1"},{"id":60,"parentid":58,"title":"Item 2.2"}]}]
const output = {
data: input.map((data) => {
const {
child_content: children,
id,
parentId,
title,
} = data;
return {
id,
parentId,
title,
children: children.map(data => ({data})),
};
})
}
console.log(output);
You can use JavaScript Array.prototype.map():
var json = [{"id": 48,"parentid": 0,"title": "Item 1","child_content": [{"id": 49,"parentid": 48,"title": "Itema 1.1",}, {"id": 52,"parentid": 48,"title": "Item 1.2",}]}, {"id": 58,"parentid": 0,"title": "Item 2","child_content": [{"id": 59,"parentid": 58,"title": "Itema 2.1",}, {"id": 60,"parentid": 58,"title": "Item 2.2",}]}],
result = {
data: json.map(function (item) {
return {
data: {
id: item.id,
parentid: item.parentid,
title: item.title
},
children: item.child_content.map(function (childItem) {
return {
data: {
id: childItem.id,
parentid: childItem.parentid,
title: childItem.title
}
}
})
};
})
};
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Assuming that the differences in the two datasets are due to copy/paste from different datasets, you can use the array map method to transform the data for you.
map works by iterating through each item of an array, and allows you to return a new item in the shape which you'd like.
var input = [{"id":48,"parentid":0,"title":"Item 1","child_content":[{"id":49,"parentid":48,"title":"Itema 1.1"},{"id":52,"parentid":48,"title":"Item 1.2"}]},{"id":58,"parentid":0,"title":"Item 2","child_content":[{"id":59,"parentid":58,"title":"Itema 2.1"},{"id":60,"parentid":58,"title":"Item 2.2"}]}];
var output = {
data: input.map(function(parent) {
// return a new object which contains the properties which you need
return {
data: {
id: parent.id,
parentid: parent.parentid,
title: parent.title
},
// children is an array, so we can use map again to transform them
children: parent.child_content.map(function(child) {
return {
data: {
id: child.id,
parentid: parent.id,
title: child.title
}
};
})
}
})
}
console.log(output);
You could convert the structure without mutating the original object with iterating and recursive calls of the convert function.
It works for any depth.
function convert(o) {
var temp = { data: {} };
Object.keys(o).forEach(function (k) {
if (k === 'child_content') {
temp.children = o[k].map(convert);
} else {
temp.data[k] = o[k];
}
});
return temp;
}
var data = [{ id: 48, parentid: 0, title: "Item 1", child_content: [{ id: 49, parentid: 48, title: "Itema 1.1" }, { id: 52, parentid: 48, title: "Item 1.2" }] }, { id: 58, parentid: 0, title: "Item 2", child_content: [{ id: 59, parentid: 58, title: "Itema 2.1" }, { id: 60, parentid: 58, title: "Item 2.2" }] }],
result = { data: data.map(convert) };
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Ok, what I finally did is maybe not too much elegant... but it works, probably I should create a recursive function to manage different nested levels. I took the #RobM solution and replicated the children functionality at the second level like this:
convert(input)
{
const output =
{
data: input.map((data) =>
{
const { children: children } = data;
delete data.children;
return {
data,
children: children.map(data =>
{
const { children: children } = data;
delete data.children;
return {
data,
children: children.map(data => ({data})),
};
}),
};
})
}
return output.data;
}