How to transform nested object of objects into array of objects - javascript

Hi all I have the following code
the data that I want to transform.
const obj = {
numbers: {
label: "main numbers",
pageTitle: "Numbers",
key: "1",
items: {
firstNumber: {
label: "first number",
pageTitle: "first",
key: "first"
},
secondNumber: {
label: "second number",
pageTitle: "second",
key: "second"
}
}
},
letters: {
label: "main Letters",
pageTitle: "Letters",
key: "2",
items: {
firstLetter: {
label: "first Letter",
pageTitle: "first",
key: "first"
}
}
},
signs: {
label: "main sign",
pageTitle: "Sign",
key: "3"
}
};
In my obj variable I have 3 other objects
numbers object which has items property which includes 2 other objects.
letters object which has items property which includes only one object.
signs object.
I need to transform my obj to the following way.
[
{
label:"main numbers",
pageTitle:"Numbers",
key:1,
children: [{label,pageTitle,key},{label,pageTitle,key}]
},
{
label:"main Letters",
pageTitle:"Letters",
key:1,
children: [{label,pageTitle,key}]
},
{
label:"main sign",
pageTitle:"Sign",
key:1,
children: []
},
]
for that transformation, I wrote the following code.
const transformedData = Object.values(obj).map((menuitem) => menuitem);
const data = [];
transformedData?.map((x) => {
const newData = {};
newData.label = x.label;
newData.pageTitle = x.pageTitle;
newData.key = x.key;
newData.children = x?.Object?.values(items)?.map((el) => {
newData.children.label = el.label;
newData.children.pageTitle = el.pageTitle;
newData.children.key = el.key;
});
data.push(newData);
});
Everything was working, but for children instead of printing an array it prints undefined.
Please help me to resolve this issue.

I created a function for your case.
const convert = data =>
Object.values(data)?.map(x => ({
label: x.label,
pageTitle :x.pageTitle ,
key: x.pathname,
children: x.items
? Object.values(x.items || {}).map(el => ({ label: el.label,
key:el.pathname,pageTitle:el.pageTitle }))
: null,
}));
You can use like const items = convert(obj).

xdoesn't have Objects. Change it to:
newData.children = Object.values(x.items)?.map(/*...*/);

Is this what you're after?
const transformedData = Object.values(obj).map((menuitem) => menuitem);
const data = [];
transformedData?.map((x) => {
const newData = {};
newData.label = x.label;
newData.pageTitle = x.pageTitle;
newData.key = x.key;
if(x.hasOwnProperty('items')){
newData.children = Object.values(x.items).map((el) => {
const obj={
label:el.label,
pageTitle:el.pageTitle,
key:el.key
}
return obj
})};
data.push(newData);
});
console.log(data)
Your code return undefined because inside map you didn't return anything so newData.children was never populated with anything.
Also, I think accessing and assigning newData.children.label was problematic since there was no newData.children yet. So we declare a temp obj inside map and we return it
Lastly we need to check if items is a property that exists in the first place.

Related

Retain array structure when filtering nested array

My brain froze with this advanced filtering. This task has exceeded my basic knowledge of filter, map etc.
Here I have an array with nested objects with array:
const DATA = [
{
title: 'Spongebob',
data: [
{ id: 1, name: 'Mr Crabs' },
{ id: 2, name: 'Sandy' }
]
},
{
title: 'Dragon Balls Z',
data: [
{ id: 1, name: 'GoKu' },
{ id: 2, name: 'Zamasu' }
]
}
];
You may have seen this sort of style if you've worked with React Native (RN). This question is not for RN. I need to perform a filter on the name property in the nested array and when I get a match, I must return the format as the DATA variable.
const handleFiltering = (value) => {
const _value = value.toLowerCase();
const results = DATA.map(o => {
return o.data.filter(o => o.name.toLowerCase().indexOf(_value) != -1)
});
console.log(results);
};
My limited knowledge of deep filtering returns the basic filtering for the data array but need to retain the structure for DATA. The expected results I'd expect:
// I'm now querying for "ZAMASU"
const handleFiltering = (value='ZAMA') => {
const _value = value.toLowerCase();
const results = DATA.map(o => {
return o.data.filter(o => o.name.toLowerCase().indexOf(_value) != -1)
});
// console.log(results) should now be
// [
// {
// title: 'Dragon Balls Z',
// data: [
// { id: 2, name: 'Zamasu' }
// ]
// }
// ];
};
What comes to mind is the use of {...DATA, something-here } but my brain has frozen as I need to get back the title property. How to achieve this, please?
Another solution would be first use filter to find only objects containing the name in data passed through the argument, subsequently mapping data.
Here is your adjusted filter method
const handleFiltering = (value) => {
const _value = value.toLowerCase();
const results = DATA.filter((obj) =>
obj.data.some((character) => character.name.toLowerCase() === _value)
).map((obj) => ({
title: obj.title,
data: obj.data.filter(
(character) => character.name.toLowerCase() === _value
),
}));
console.log(results);
};
You can use reduce method of array. First find out the object inside data array and then add that to accumulator array as new entry by preserving the original structure.
const DATA = [
{
title: 'Spongebob',
data: [
{ id: 1, name: 'Mr Crabs', where: 'tv' },
{ id: 2, name: 'Sandy' }
]
},
{
title: 'Dragon Balls Z',
data: [
{ id: 1, name: 'GoKu' },
{ id: 2, name: 'Zamasu' }
]
}
];
let handleFiltering = (value='tv') => {
return DATA.reduce((acc,d) => {
let obj = d.data.find(a => a.name?.toLowerCase().includes(value.toLowerCase())
|| a.where?.toLowerCase().includes(value.toLowerCase()));
obj ? acc.push({...d, data:[obj]}) : null;
return acc;
}, []);
}
let result = handleFiltering();
console.log(result);

How to construct an array of objects by using the keys of another array of objects and values from the array of arrays?

I have two arrays as listed below. I'm trying to create a new array of objects by using the field key in array_1 and the values in array_2.
const result = []
array_1 = [{ name: "Color" , field: "color"}, {name: "Shape", field: "shape" }, { name: "Whatever", field: "whatever" }]
array_2 = [["green", "rectangular", "whatever1"], ["yellow", "circle", "whatever2"]]
The result should be:
console.log(result)
// [{color:"green", shape:"rectangular", whatever: "whatever1"},
// { color:"yellow", shape: "circle", whatever:"whatever2"}]
I did this at my final trial:
const rowObj = {}
const result = array.map((subarray) => subarray.map((cell, index) => {
console.log(cell,index)
rowObj[columns[index].field] = cell
return rowObj
}))
Basically, I was overwriting the same object.
Thanks,
One way to do it is to map() over the array_2 and in each iteration:
Create a new object
Iterate over the array_1 to fill the newly created object. You can use the index parameter of the forEach() method's callback function to get the field property from the objects inside array_1.
and then return that object from the callback function of the map() method.
const array_1 = [
{ name: 'Color', field: 'color' },
{ name: 'Shape', field: 'shape' },
{ name: 'Whatever', field: 'whatever' },
];
const array_2 = [
['green', 'rectangular', 'whatever1'],
['yellow', 'circle', 'whatever2'],
];
const result = array_2.map(arr => {
const o = {};
arr.forEach((str, idx) => {
o[array_1[idx].field] = str;
});
return o;
});
console.log(result);
You can use array.map to iterate both arrays and take advantage of Object.fromEntries to build new objects based on the order of array elements:
array_1 = [{ name: "Color" , field: "color"}, {name: "Shape", field: "shape" }, { name: "Whatever", field: "whatever" }]
array_2 = [["green", "rectangular", "whatever1"], ["yellow", "circle", "whatever2"]]
let result = array_2.map(
x => Object.fromEntries(
array_1.map((y,i) => ([y.field, x[i]]))))
console.log(result);
Explanation
You could create a function that creates a constructor based on the descriptions of your object's fields like this:
function createConstructor(fieldsDescriptor) {
return function(fields) {
fieldsDescriptor.forEach((descriptor, index) => {
this[descriptor.field] = fields[index]
})
}
}
Then you could, for example, make a sampleConstructor that creates objects based on the field names of array_1:
const SampleConstructor = createConstructor(array_1)
And then, for each entry in array_2 you could apply your SampleConstructor:
const result = array_2.map(fields => new SampleConstructor(fields))
Motivation
Creating a dedicated constructor adds some clear semantics to your app, shows readers what you are doing and also stores constructor information in the created objects at runtime.
When you later want to know which constructor made which objects you can just call object.constructor and use this information to determine what kind of objects they are.
For example calling result[0].constructor == SampleConstructor will be true because SampleConstructor is the constructor that created the first result.
Demo
Here is a full demo
const array_1 = [{ name: "Color" , field: "color"}, {name: "Shape", field: "shape" }, { name: "Whatever", field: "whatever" }]
const array_2 = [["green", "rectangular", "whatever1"], ["yellow", "circle", "whatever2"]]
function createConstructor(fieldsDescriptor) {
return function(fields) {
fieldsDescriptor.forEach((descriptor, index) => {
this[descriptor.field] = fields[index]
})
}
}
const SampleConstructor = createConstructor(array_1)
const results = array_2.map(fields => new SampleConstructor(fields))
console.log(results)
const EmptyConstructor = createConstructor([])
console.log(results[0].constructor == SampleConstructor)
console.log(results[0].constructor == EmptyConstructor)
You can try this
array_1 = [
{ name: 'Color', field: 'color' },
{ name: 'Shape', field: 'shape' },
{ name: 'Whatever', field: 'whatever' }
];
array_2 = [
['green', 'rectangular', 'whatever1'],
['yellow', 'circle', 'whatever2']
];
const keys = array_1.map(item => item.field);
const output = [];
array_2.forEach(item => {
const temp = {};
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const value = item[i];
temp[key] = value;
}
output.push(temp);
});
console.log(output);

how to add objects to arrays

I'm trying to construct an array of objects from a set of two different arrays. I'm a little comfussed on where I need to go from here.
I'm creating a unique key value, but the object is going into the array individual.
const startingArray = [
{
key: "description",
value: "my description"
},
{
key: "description2",
value: "my description2"
},
{
key: "description3",
value: "my description3"
},
]
my logic
const k = mystartingArray.reduce((acc, d, count) => {
const name = Object.value(d)[0]
const title = Object.value(d)[1]
const value = {
[name]: title
}
acc.push(value)
return acc
},[])
how I want the Array to look
const finishedArray = [
{
description: "my description",
description2: "my description2,
description3: "my description3,
}
How far am I off?
I think this would be simpler to solve just by using a basic forEach.
let value = {};
startingArray.forEach(obj => {
value[obj.key] = obj.value;
});
const finishedArray = [value];
Or, if you don't want to have a value object:
const finishedArray = [{}];
startingArray.forEach(obj => {
finishedArray[0][obj.key] = obj.value;
});
const finishedArray = [
startingArray.reduce((a, v) => {a[v.key] = v.value; return a}, {})
]
To finish your code:
const startingArray = [
{
key: "description",
value: "my description"
},
{
key: "description2",
value: "my description2"
},
{
key: "description3",
value: "my description3"
},
];
const k = startingArray.reduce((acc, d, count) => {
return [{
...(acc[0] || {}),
[d.key]: d.value
}]
},[])
console.log(k);
However, I think the solution of Rocket Hazmat is more reasonable than this.

How to create an object with properties in the array from the string?

I want to create objects with properties in an array by string. I'm extracting "name" from string and "data" without round brackets and trying to create objects in the array with the property "name"
and property "data". But actual result differs from expected, please help to solve
const names = ["name1 /2 (data1)", "name1 /2 (data2)", "name2 /1 (data1)"]
const flag = true
names.forEach(name => {
console.log(getStructuredDataFromNames(name))
})
function getStructuredDataFromNames(name) {
const names = {}
// extract name from string and conver to valid format - 'name1'
const formattedName = name.substr(0, name.indexOf("/")).replace(/\s+/g, "")
// extract data from string and conver to valid format - 'data1'
const formattedData = name.match(/\((.*)\)/).pop()
const reference = {
formattedData,
flag
}
// if no name then create a new property by this name
if (!names[name]) {
names[name] = [{
data: [reference]
}]
} else {
// if object have name but name have more then 1 data then push this data to array
names[name].push(reference)
}
const result = Object.keys(names)
return result.map(el => ({
name: el,
data: names[el]
}))
}
Expected result
[{
name: "name1",
data: [{
flag: true,
formattedData: "data1"
},
{
flag: true,
formattedData: "data2"
}
]
},
{
name: "name2",
data: [{
flag: true,
formattedData: "data1"
}]
}
]
Although the code is not that clean, it can do the job.
My flow is first to remove the spaces, and then split the name and the data, remove the number before the data and remove (). If "formattedNames" already have the same "name" object, push the data to the "name" object.
const names = ["name1 /2 (data1)", "name1 /2 (data2)", "name2 /1 (data1)"]
const formattedNames = []
names.forEach(value =>{
const processedName = value.replace(/ /g,'').split("/")
const formattedName = formattedNames.find((object)=>{ return object.name === processedName[0]})
const formattedData = processedName[1].split("(")[1].replace(")","")
if (!formattedName) {
formattedNames.push({name: processedName[0],data: [{flag: true, formattedData}]})
} else {
formattedName.data.push({flag: true, formattedData})
}
})
console.log(formattedNames)
Here is a really inefficient way to do it
var names2 = ["name1 /2 (data1)", "name1 /2 (data2)", "name2 /1 (data1)"]
var flag2 = true
var result2 = names2.map(x =>
{
return {
name: x.split(" ")[0],
data:
[
{
flag: flag2,
formattedData: x.match(/\((.*)\)/).pop()
}
]
}
})
result2.forEach((el, index, arr) =>
{
let el2 = arr.filter(x => x.name == el.name)[0];
if(el2 == el)
return;
el2.data = el2.data.concat(el.data);
delete arr[index];
})
result2 = result2.filter(x => true);
console.log(result2);

How to get values in an array of objects using another array - Javascript

I have a function that interacts with 2 arrays, 1st array is an array of objects that contain my dropdown options, second array is an array of values. I'm trying to filter the 1st array to return what has matched the values in my 2nd array. How do I achieve this?
1st Array:
const books = [
{
label: "To Kill a Mockingbird",
value: 1
},
{
label: "1984",
value: 2
},
{
label: "The Lord of the Rings",
value: 3
},
{
label: "The Great Gatsby",
value: 4
}
]
Code Snippet below:
const idString = "1,2,3";
function getSelectedOption(idString, books) {
const ids = idString.split(",");
const selectedOptions = [];
ids.map(e => {
const selected = books.map(options => {
if (options.value === e){
return {
label: options.label,
value: options.value
}
}
})
selectedOptions.push(selected)
})
return selectedOptions
}
Result:
[
[undefined, undefined, undefined, undefined],
[undefined, undefined, undefined, undefined],
[undefined, undefined, undefined, undefined]
]
Expected Result:
[
{
label: "To Kill a Mockingbird",
value: 1
},
{
label: "1984",
value: 2
},
{
label: "The Lord of the Rings",
value: 3
}
]
Assuming that value is unique, you can update your code as following to get them in order.
const idString = "1,2,3";
function getSelectedOption(idString, books) {
const ids = idString.split(",");
return ids.map(id => books.find(book => book.value == id)).filter(Boolean)
}
You can also filter the books array if you don't care about the order or in case that the value is not unique.
const idString = "1,2,3";
function getSelectedOption(idString, books) {
const ids = idString.split(",");
return books.filter(book => ids.includes(book.value.toString()))
}
Please note that these are O(n*m) algorithms and it should not be used with large sets of data, however if one of the arrays is relatively small you can use it.
function getSelectedOption(idString, books) {
const idArray = convertStringToArray(idString)
return books.filter(item => idString.includes(item.value))
}
function convertStringToArray(string) {
return string.split(",")
}
Using an array filter:
function getSelectedOption(idString, books) {
const ids = idString.split(",");
return books.filter((item) => ids.includes(item.value.toString()));
}
const books = [{
label: "To Kill a Mockingbird",
value: 1
},
{
label: "1984",
value: 2
},
{
label: "The Lord of the Rings",
value: 3
},
{
label: "The Great Gatsby",
value: 4
}
]
const idString = "1,2,3";
getSelectedOption(idString, books);
console.log(getSelectedOption(idString, books));
Some fixes to your solution
After split idString, it would result in array of string value, so you have to cast it to number
Instead of use map to get selected, you should use find
const books = [
{
label: 'To Kill a Mockingbird',
value: 1
},
{
label: '1984',
value: 2
},
{
label: 'The Lord of the Rings',
value: 3
},
{
label: 'The Great Gatsby',
value: 4
}
]
const idString = '1,2,3'
function getSelectedOption(idString, books) {
const ids = idString.split(',').map(Number)
const selectedOptions = []
ids.forEach(e => {
const selected = books
.map(options => {
if (options.value === e) {
return {
label: options.label,
value: options.value
}
}
})
.filter(options => options !== undefined)
selectedOptions.push(selected[0])
})
return selectedOptions
}
console.log(getSelectedOption(idString, books))

Categories