Related
Trying to turn an array of objects into a nested object. Is there a good method for this? and how do I make it depending on the array length?
Working but is not universal:
https://codesandbox.io/s/thirsty-roentgen-3mdcjv?file=/src/App.js
What I have:
sorting: [
{
"id": "HighestDegree",
"options": [
"HighSchool",
"Undergraduate",
"Bachelor",
"Master",
"Doctor"
]
},
{
"id": "gender",
"options": [
"male",
"female"
]
}
]
What I want:
value: {
"Region": "Oklahoma",
"HighestDegree": {
"HighSchool": {
"male": null,
"female":null
},
"Undergraduate":{
"male": null,
"female":null
}
//and so on...
}
}
The code beneath works but is hardcoded for only two different options. I want it to be able to nest the length of the array. So lets say another object was age it would be {"HighSchool":{male:{"<25":null,"25-35":null}}} etc..
function testSortingArray() {
let sorting = [
{
id: "HighestDegree",
options: ["HighSchool", "Undergraduate", "Bachelor", "Master", "Doctor"]
},
{
id: "gender",
options: ["male", "female"]
}
];
let GoalArray = {};
if (sorting.length > 0) {
sorting[0].options.map((firstArray) => {
let currObject = {};
sorting[1].options.map((secondOption) => {
currObject[secondOption] = null;
});
GoalArray[firstArray] = currObject;
});
}
return GoalArray;
}
console.log(testSortingArray());
You can do it with a recursive function.
The function below reduces every options array to an object, and then continues populating that object if there are rest elements left from the original sorting array.
const fn = ([{ options }, ...rest]) => options.reduce((a, v) => ({
...a,
[v]: rest.length ? fn(rest): null
}), {});
const result = fn(sorting);
Besides the reduce() method, the code above makes use of object and array destructuring and spread syntax.
Complete snippet:
const sorting = [{
"id": "HighestDegree",
"options": [
"HighSchool",
"Undergraduate",
"Bachelor",
"Master",
"Doctor"
]
}, {
"id": "gender",
"options": [
"male",
"female"
]
}, {
"id": "age",
"options": [
"<25",
"25-35"
]
}];
const fn = ([{ options }, ...rest]) => options.reduce((a, v) => ({
...a,
[v]: rest.length ? fn(rest): null
}), {});
const result = fn(sorting);
console.log(result);
I have a array as follows:
data = [
{
"id":1
"name":"london"
},
{
"id":2
"name":"paris"
},
{
"id":3
"name":"london"
},
{
"id":4
"name":"paris"
},
{
"id":5
"name":"australia"
},
{
"id":6
"name":"newzearland"
}
]
At runtime this array can have n number of elements. I want to group this array with respect to name attribute. All the elements with same name should be moved to a separate array. I don't know the what value can name have in advance. This is coming at runtime. For example, from above array I want final output as follows:
output:
newArray1 = [
{
"id":1
"name":"london"
},
{
"id":3
"name":"london"
}
]
newArray2 = [
{
"id":2
"name":"paris"
},
{
"id":4
"name":"paris"
}
]
newArray3 = [
{
"id":5
"name":"australia"
}
]
newArray4 = [
{
"id":6
"name":"newzearland"
}
]
How can I do that?
As Teemu has already pointed out in a comment, creating new variables to store the data is not ideal. You would have no way of knowing how many groups you've created and using variables that you can't be sure exist is not the best way to write code. Fortunately, JavaScript has objects, which can store data like this in a much cleaner way. Here's the code I've come up with:
function groupBy(arr, key) {
let res = {}
for (let element of arr) {
if (res.hasOwnProperty(element[key])) {
res[element[key]].push(element)
} else {
res[element[key]] = [element]
}
}
return res
}
This code is not the best, most efficient code ever, but it is written to be easier to understand for someone still learning. This code loops over every element in your data and checks whether our result already contains an array for elements with that name. If there's already an array for elements with that name, the current element is added to it. If there isn't one, a new one is created with the current element inside it. To do exactly what you want, you'd call this function with groupBy(data, "name") and assign it to a new variable like groupedData (THIS DOES NOT MODIFY THE DATA, IT RETURNS A NEW OBJECT OF GROUPED DATA) .
Start by getting all the unique .names, then map them to the original array filtered by each .name:
const data = [{
"id": 1, "name": "london"
},
{
"id": 2, "name": "paris"
},
{
"id": 3, "name": "london"
},
{
"id": 4, "name": "paris"
},
{
"id": 5, "name": "australia"
},
{
"id": 6, "name": "newzearland"
}
];
const newData = [...new Set(data
//Get all names in an array
.map(({name}) => name))]
//For each name filter original array by name
.map(n => data.filter(({name}) => n === name));
console.log( newData );
//OUTPUT: [newArray1, newArray2, .....]
You can get the expected result with grouping by key approach.
const data = [{"id":1,"name":"london"},{"id":2,"name":"paris"},{"id":3,"name":"london"},{"id":4,"name":"paris"},{"id":5,"name":"australia"},{"id":6,"name":"newzearland"}];
const result = Object.values(data.reduce((acc, obj) =>
({ ...acc, [obj.name]: [...(acc[obj.name] ?? []), obj] }), {}));
console.log(result);
const [newArray1, newArray2, newArray3, newArray4, ...rest] = result;
console.log('newArray1:', newArray1);
console.log('newArray2:', newArray2);
console.log('newArray3:', newArray3);
console.log('newArray4:', newArray4);
.as-console-wrapper{min-height: 100%!important; top: 0}
Restructuring array of objects to new array
Problem
There’s an array of objects that contains plain strings and might contain nested arrays as well. We want to create a new Array that will contain a node for each item in the array and separate nodes for each array item connected to its parent. Each parent node should have the following structure:
{
id: uuidv4(),
position: { x: 0, y: 0 },
data: { label: <item data goes here> }
}
Each array node with the following schema above, should also have a connection edge item added to the array with the following properties:
{
id: ‘e<array item Id>-<parentId>’,
source: <array item Id>,
target: <parentId>,
}
Example
We have the following array of objects for example:
[
{
"author": "John Doe",
"age": 26,
"books": [
{
"title": "Book 1"
},
{
"title": "Book 2",
"chapters": [
{
"title": "No Way Home",
"page": 256
}
]
}
]
}
]
The expected output is:
[
{
"id": "1",
"data": {
"label": {
"author": "John Doe",
"age": 26,
}
}
},
{
"id": "2",
"data": {
"label": "books" // key of array
}
},
{
"id": "3",
"data": {
"label": {
"title": "Book 1"
}
}
},
{
"id": "4",
"data": {
"label": {
"title": "Book 2"
}
}
},
{
"id": "5",
"data": {
"label": "chapters" // key of array
}
},
{
"id": "6",
"data": {
"label": {
"title": "No Way Home",
"page": 256
}
}
},
{
"id": "e2-1",
"source": "2",
"target": "1"
},
{
"id": "e3-2",
"source": "3",
"target": "2"
},
{
"id": "e4-2",
"source": "4",
"target": "2"
},
{
"id": "e5-4",
"source": "5",
"target": "4"
},
{
"id": "e6-5",
"source": "6",
"target": "5"
}
]
First of all, I would not be answering if there was not already a good answer. Please, on StackOverflow, always show your own attempts and explain where you got stuck. But since there is already an answer, I think this version might be a bit simpler.
Second, I'm assuming this output format is some sort of directed graph, that the first half is your list of vertices and the second half a list of edges. If so I don't know if your output format is constrained here. But if you had the option, I would think a better structure would be an object with vertices and edges properties, each containing an array. You might then not need the edges' ids. And the code could also be simplified.
This version first converts to an intermediate structure like this:
[
{id: "1", data: {label: {author: "John Doe", age: 26}}, children: [
{id: "2", data: {label: "books"}, children: [
{id: "3", data: {label: {title: "Book 1"}}, children: []},
{id: "4", data: {label: {title: "Book 2"}}, children: [
{id: "5", data: {label: "chapters"}, children: [
{id: "6", data: {label: {title: "No Way Home"}}, children: []}
]}
]}
]}
]}
]
Then we flatten that structure into the first section of the output and use it to calculate the relationships (edges?) between nested nodes to go in the second section.
The code looks like this:
const transform = (input) => {
const extract = (os, nextId = ((id) => () => String (++ id)) (0)) => os .map ((o) => ({
id: nextId(),
data: {label: Object .fromEntries (Object .entries (o) .filter (([k, v]) => !Array .isArray (v)))},
children: Object .entries (o) .filter (([k, v]) => Array .isArray (v)) .flatMap (([k, v]) => [
{id: nextId(), data: {label: k}, children: extract (v, nextId)},
])
}))
const relationships = (xs) =>
xs .flatMap (({id: target, children = []}) => [
... children .map (({id: source}) => ({id: `e${source}-${target}`, source, target})),
... relationships (children),
])
const flatten = (xs) =>
xs .flatMap (({children, ...rest}) => [rest, ... flatten (children)])
const res = extract (input)
return [...flatten (res), ... relationships (res)]
}
const input = [{author: "John Doe", age : 26, books: [{title: "Book 1"}, {title: "Book 2", chapters: [{title: "No Way Home", page: 256}]}]}]
console .log (transform (input))
.as-console-wrapper {max-height: 100% !important; top: 0}
We use three separate recursive functions. One does the recursive extract into that intermediate format. Along the way, it adds id nodes using a nextId stateful function (something I usually avoid, but seems to simplify things here.) Then flatten simply recursively lifts the children to sit alongside their parents. And relationships (again recursively) uses the ids of the parent- and child-nodes to add an edge node.
Using these three separate recursive calls is probably less efficient than some other solutions, but I think it leads to much cleaner code.
One has to choose a self recursive approach which in a generic way can process both, array-items and object-entries. Also, while the recursive process takes place, one not only has to create and collect the consecutively/serially numbered (the incremented id value) data nodes, but one in addition needs to keep track of every data node's parent reference in order to finally concatenate the list of edge items (as the OP calls it) to the list of data nodes.
function flattenStructureRecursively(source = [], result = [], tracker = {}) {
let {
parent = null, edgeItems = [],
getId = (id => (() => ++id))(0),
} = tracker;
const createEdgeItem = (id, pid) => ({
id: `e${ id }-${ pid }`,
source: id,
target: pid,
});
const putNodeData = node => {
result.push(node);
if (parent !== null) {
edgeItems.push(createEdgeItem(node.id, parent.id));
}
// every data node is a parent entity too.
parent = node;
};
if (Array.isArray(source)) {
result.push(
...source.flatMap(item =>
flattenStructureRecursively(item, [], {
getId, parent, edgeItems,
})
)
);
} else {
let {
dataNode,
childEntries,
} = Object
.entries(source)
.reduce(({ dataNode, childEntries }, [key, value]) => {
if (value && (Array.isArray(value) || (typeof value === 'object'))) {
// collect any object's iterable properties.
childEntries.push([key, value]);
} else {
// aggregate any object's non iterable
// properties at data node level.
(dataNode ??= {
id: getId(),
data: { label: {} }
}).data.label[key] = value;
}
return { dataNode, childEntries };
}, { dataNode: null, childEntries: [] });
if (dataNode !== null) {
putNodeData(dataNode);
}
childEntries
.forEach(([key, value]) => {
// every object's iterable property is supposed
// to be created as an own parent entity.
dataNode = {
id: getId(),
data: { label: key },
};
putNodeData(dataNode);
result.push(
...flattenStructureRecursively(value, [], {
getId, parent, edgeItems,
})
);
});
}
if (parent === null) {
// append all additionally collected edge items
// in the end of all the recursion.
result.push(...edgeItems);
}
return result;
}
console.log(
flattenStructureRecursively([{
author: "John Doe",
pseudonym: "J.D.",
books: [{
title: "Book 1",
}, {
title: "Book 2",
chapters: [{
title: "No Way Home",
page: 256,
}],
}],
age: 26,
}])
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
I'm working on a react app that mimics a retail website. My main page displays an item, and below has card components of related products. When I click a button on one of the related products, I open a comparison modal that compares features of the current product and the clicked upon product. I figured that to accomplish this, I would create an array of the combined features of the clicked on product and the main page product. I've been struggling to get create an array of objects, where each unique feature has an object with data inside about the features and which product the feature belongs to.
As of right now, I've been able to get an array of all the features that the two products have, but this array has repeats if the products have overlapping features. This makes me unsure of how to render the comparison table because I was planning on mapping over the array and creating a table row for each feature. My current code to format these features is as follows:
formatFeatures: (currentProd, clickedProd) => {
let combinedFeatures = [];
if (clickedProd.features) {
clickedProd.features.forEach(feature => {
let obj = {}
let vals = Object.values(feature);
obj[vals[0]] = [vals[1], clickedProd.id]
combinedFeatures.push(obj)
})
}
currentProd.features.forEach(feature => {
let obj = {}
let vals = Object.values(feature);
obj[vals[0]] = [vals[1], currentProd.id]
combinedFeatures.push(obj)
})
let formattedFeatures = combinedFeatures.reduce((allFeatures, feature) => {
if (Object.keys(feature) in allFeatures) {
allFeatures = [allFeatures[Object.keys(feature)]].concat(feature);
} else {
allFeatures.push(feature);
}
return allFeatures;
}, [])
The result of this is:
[{
"Fabric": ["100% Cotton", 28214]
}, {
"Cut": ["Skinny", 28214]
}, {
"Fabric": ["Canvas", 28212]
}, {
"Buttons": ["Brass", 28212]
}]
This is pretty close to what I am looking for, where I have an array of objects that contain information about the feature and product id of the product, but the repeat in "Fabric" is something I'm struggling to sort out. Ideally, the result would look like this:
[{
"Fabric": ["100% Cotton", 28214],
["Canvas", 28212]
}, {
"Cut": ["Skinny", 28214]
}, {
"Buttons": ["Brass", 28212]
}]
If anyone can help guide me as to how to change my formatting function to accomplish this, I'd be very grateful. Alternatively, if anyone knows a better way to dynamically format a table with a single row for each unique feature given my current result, that would be great too.
The data coming into my helper function is as follows:
CurrentProd:
{
"id": 28212,
"name": "Camo Onesie",
"slogan": "Blend in to your crowd",
"description": "The So Fatigues will wake you up and fit you in. This high energy camo will have you blending in to even the wildest surroundings.",
"category": "Jackets",
"default_price": "140.00",
"created_at": "2021-07-10T17:00:03.509Z",
"updated_at": "2021-07-10T17:00:03.509Z",
"features": [{
"feature": "Fabric",
"value": "Canvas"
}, {
"feature": "Buttons",
"value": "Brass"
}]
}
ClickedProd:
{
"name": "Morning Joggers",
"category": "Pants",
"originalPrice": "40.00",
"salePrice": null,
"photo": "https://images.unsplash.com/photo-1552902865-b72c031ac5ea?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=300&q=80",
"id": 28214,
"features": [{
"feature": "Fabric",
"value": "100% Cotton"
}, {
"feature": "Cut",
"value": "Skinny"
}]
}
There seems to be a bigger question of how to structure your data. You say that ideally your results would look like:
[
{
"Fabric":
["100% Cotton",28214],
["Canvas",28212]
},
{
"Cut":
["Skinny",28214]
},
{
"Buttons":
["Brass",28212]
}
]
But what you're really trying to get out of this is a combined list of rows and associated values for each item feature, if it exists. All you really need then is an array of keys for each row you want to display, and objects that let you access the needed property by that key.
The array of keys could look like this:
["Fabric", "Cut", "Buttons"]
The objects you want to access the properties using those keys, for example your CurrentProd, could be this (notice that you can access a feature by calling CurrentProd.features["FeatureName"]):
{
"id":28212,
"name":"Camo Onesie",
// ... //
"features": {
"Fabric": "Canvas",
"Buttons": "Brass"
}
}
Having said that, to get those things you can get the array of keys, which we'll call allFeatureKeys, by reducing over a combined array of CurrentProd.features and ClickedProd.features:
const allFeatureKeys = [
...CurrentProd.features,
...ClickedProd.features
].reduce((acc, cur) => {
return acc.findIndex(cur.feature) > -1 ? [...acc, cur.feature] : acc
},
[]
);
And you can modify your CurrentProd to the above data shape by reducing over the array of its features, let's call this modifiedCurrentProd:
const modifiedCurrentProd = {
...CurrentProd,
features: CurrentProd.features.reduce((acc, cur) => {
return {...acc, [cur.feature]: cur.value}
}, {})
}
Repeat that for a modifiedClickedProd object, then you have both CurrentProd.features and ClickedProd.features values available for a lookup when you create your table values.
As an example only, since I don't know your react structure or what data you want to display, you can then render the values in the table rows mapping over the keys to make each row, and for each feature key, you access the value from the modifiedCurrentProd or modifiedClickedProd object's features property:
<div id="table">
{allFeatureKeys.map((featureKey) => {
return <div id="table-row">
<div>{featureKey}</div>
<div>
{
modifiedCurrentProd.features[featureKey] !== undefined
? modifiedCurrentProd.id
: "n/a"
}
</div>
<div>
{
modifiedClickedProd.features[featureKey] !== undefined
? modifiedClickedProd.id
: "n/a"
}
</div>
</div>
})}
</div>
Firstly the target data structure needs to be fixed/optimized. It looks like the OP does concentrate on something which is based on a generic Feature (like Fabric, Cut, Buttons) whereas such feature values seem to be associated more with the Product. Thus for one and the same feature the values are unique to the product feature. In order to not loose the product information, a target format's feature item needs to reflect its related product's id property.
A viable and still flexible enough target data structure then might look like this ...
{
"Fabric": [{
productId: 28214,
value: "100% Cotton",
}, {
productId: 28212,
value: "Canvas",
}],
"Cut": [{
productId: 28214,
value: "Skinny",
}],
"Buttons": [{
productId: 28212,
value: "Brass",
}],
}
Any approach should start with a data-normalizing mapping-process of a product's features list where each feature item will get its product related id assigned.
Thus a feature item like { feature: "Buttons", value: "Brass" } gets mapped temporarily into { productId: 28212, feature: "Buttons", value: "Brass" }.
The two normalized data-item lists now can be concatenated and finally processed/reduced into the final target structure ...
function mergeBoundProductId(item) {
return { ...this, ...item };
}
function aggregateProductFeatureValueLists(index, productFeature) {
const { feature, ...featureValue } = productFeature;
const featureList = index[feature] ??= [];
//const featureList = index[feature] || (index[feature] = []);
featureList.push(featureValue);
return index;
}
function createIndexOfProductFeatureValues(clickedProd, currentProd) {
const { features:clickedFeatures } = clickedProd;
const { features:currentFeatures } = currentProd;
return [
...clickedFeatures.map(mergeBoundProductId, { productId: clickedProd.id }),
...currentFeatures.map(mergeBoundProductId, { productId: currentProd.id }),
].reduce(aggregateProductFeatureValueLists, {});
}
const currentProduct = {
id: 28212,
name: "Camo Onesie",
// ... more properties ...
features: [{
feature: "Fabric",
value: "Canvas",
}, {
feature: "Buttons",
value: "Brass",
}],
};
const clickedProduct = {
name: "Morning Joggers",
// ... more properties ...
id: 28214,
features: [{
feature: "Fabric",
value: "100% Cotton",
}, {
feature: "Cut",
value: "Skinny",
}],
};
console.log(
'createIndexOfProductFeatureValues(clickedProduct, currentProduct) ...',
createIndexOfProductFeatureValues(clickedProduct, currentProduct)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
The advantage of breaking the code into dedicated processes comes with easier refactoring for e.g. changed target structures like something closer to what the OP was looking for.
The changes to the reducer function are minimal. It's just two changes, each barely noticeable in its line ...
function mergeBoundProductId(item) {
return { ...this, ...item };
}
function aggregateProductFeatureValueLists(index, productFeature) {
const { feature, productId, value } = productFeature;
const featureList = index[feature] ??= [];
featureList.push([value, productId]);
return index;
}
function createIndexOfProductFeatureValues(clickedProd, currentProd) {
const { features:clickedFeatures } = clickedProd;
const { features:currentFeatures } = currentProd;
return [
...clickedFeatures.map(mergeBoundProductId, { productId: clickedProd.id }),
...currentFeatures.map(mergeBoundProductId, { productId: currentProd.id }),
].reduce(aggregateProductFeatureValueLists, {});
}
console.log(
'createIndexOfProductFeatureValues(clickedProduct, currentProduct) ...',
createIndexOfProductFeatureValues(clickedProduct, currentProduct)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
const currentProduct = {
id: 28212,
name: "Camo Onesie",
// ... more properties ...
features: [{
feature: "Fabric",
value: "Canvas",
}, {
feature: "Buttons",
value: "Brass",
}],
};
const clickedProduct = {
name: "Morning Joggers",
// ... more properties ...
id: 28214,
features: [{
feature: "Fabric",
value: "100% Cotton",
}, {
feature: "Cut",
value: "Skinny",
}],
};
</script>
The last example's purpose too is to prove the advantage of an easy to refactor code base.
Here the main function gets renamed from createIndexOfProductFeatureValues to createListOfProductFeatureValues.
It's implementation also changes likewise but only in the way how the reducer function gets invoked with its initial value.
The reducer function also does not change dramatically, only in the way of how the accumulating/aggregating collector object gets handled.
And the result is a clean array based object structure ...
function mergeBoundProductId(item) {
return { ...this, ...item };
}
function aggregateProductFeatureValueLists(collector, productFeature) {
const { feature, productId, value } = productFeature;
const { index, list } = collector;
const featureItem = index[feature] ??= { feature, values: [] };
if (featureItem.values.length === 0) {
list.push(featureItem);
}
featureItem.values.push([value, productId]);
return collector;
}
function createListOfProductFeatureValues(clickedProd, currentProd) {
const { features:clickedFeatures } = clickedProd;
const { features:currentFeatures } = currentProd;
return [
...clickedFeatures.map(mergeBoundProductId, { productId: clickedProd.id }),
...currentFeatures.map(mergeBoundProductId, { productId: currentProd.id }),
].reduce(aggregateProductFeatureValueLists, { index: {}, list: [] }).list;
}
console.log(
'createListOfProductFeatureValues(clickedProduct, currentProduct) ...',
createListOfProductFeatureValues(clickedProduct, currentProduct)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
const currentProduct = {
id: 28212,
name: "Camo Onesie",
// ... more properties ...
features: [{
feature: "Fabric",
value: "Canvas",
}, {
feature: "Buttons",
value: "Brass",
}],
};
const clickedProduct = {
name: "Morning Joggers",
// ... more properties ...
id: 28214,
features: [{
feature: "Fabric",
value: "100% Cotton",
}, {
feature: "Cut",
value: "Skinny",
}],
};
</script>
You are already looping through both once. You can get it without reducing.
const formatFeatures = (currentProd, clickedProd) => {
const formattedFeatures = {};
if (clickedProd.features) {
clickedProd.features.forEach(feature => {
const vals = Object.values(feature);
if (!formattedFeatures.hasOwnProperty(vals[0])) {
formattedFeatures[vals[0]] = [];
}
formattedFeatures[vals[0]].push([vals[1], clickedProd.id]);
});
}
currentProd.features.forEach(feature => {
const vals = Object.values(feature);
if (!formattedFeatures.hasOwnProperty(vals[0])) {
formattedFeatures[vals[0]] = [];
}
formattedFeatures[vals[0]].push([vals[1], currentProd.id]);
})
return formattedFeatures;
}
const currentProd = {
"id": 28212,
"name": "Camo Onesie",
"slogan": "Blend in to your crowd",
"description": "The So Fatigues will wake you up and fit you in. This high energy camo will have you blending in to even the wildest surroundings.",
"category": "Jackets",
"default_price": "140.00",
"created_at": "2021-07-10T17:00:03.509Z",
"updated_at": "2021-07-10T17:00:03.509Z",
"features": [{
"feature": "Fabric",
"value": "Canvas"
}, {
"feature": "Buttons",
"value": "Brass"
}]
};
const clickedProd = {
"name": "Morning Joggers",
"category": "Pants",
"originalPrice": "40.00",
"salePrice": null,
"photo": "https://images.unsplash.com/photo-1552902865-b72c031ac5ea?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=300&q=80",
"id": 28214,
"features": [{
"feature": "Fabric",
"value": "100% Cotton"
}, {
"feature": "Cut",
"value": "Skinny"
}]
};
console.log(formatFeatures(currentProd, clickedProd));
.as-console-wrapper { min-height: 100%!important; top: 0; }
Hello guys I have two arrays
var elements = [{
"id": "id_1",
"type": "input",
"businesstype": { "type": "text" }
},
{
"type": "label",
"id": "id_234"
},
{
"id": "id_16677",
"type": "div",
},
{
"id": "id_155",
"type": "input",
"businesstype": { "type": "password" }
}
]
var filterArray=[{type:'input',businesstype:{type:'text'}},{type:'div'}]
and want common obejct like
var output = [{
"id": "id_1",
"type": "input",
"businesstype": { "type": "text" }
},
{
"id": "id_16677",
"type": "div",
}
]
How do I compare these two objects to get equal objects from elements.
You could filter it with a recursive approach for the nested objects.
const isObject = o => o && typeof o === 'object',
isEqual = (f, o) =>
isObject(o) && Object.keys(f).every(k =>
isObject(f[k]) && isEqual(f[k], o[k]) || o[k] === f[k]
);
var elements = [{ id: "id_1", type: "input", businesstype: { type: "text" } }, { type: "label", id: "id_234" }, { id: "id_16677", type: "div" }, { id: "id_155", type: "input", businesstype: { type: "password" } }],
filterArray = [{ type: 'input', businesstype: { type: 'text' } }, { type: 'div' }],
result = elements.filter(o => filterArray.some(f => isEqual(f, o)));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
If your filterArray does not have further objects in its hierarchy, you can make do with this solution - see demo below:
var elements=[{id:"id_1",type:"input",businesstype:{type:"text"}},{type:"label",id:"id_234"},{id:"id_16677",type:"div"},{id:"id_155",type:"input",businesstype:{type:"password"}}],filterArray=[{type:"input",businesstype:{type:"text"}},{type:"div"}];
var result = elements.filter(function(e) {
return filterArray.some(function(f) {
return Object.keys(f).every(function(k) {
return e.hasOwnProperty(k) && Object.keys(f[k]).every(function(n) {
return e[k][n] == f[k][n];
});
});
});
});
console.log(result);
.as-console-wrapper {top: 0;max-height: 100%!important;}
(Since you tagged Ramda)
Ramda already has many useful (object) comparison functions you can use to make the filter a bit easier to read. (i.e.: equals and other functions that use it under the hood, like contains)
You could, for example, write:
const elements=[{id:"id_1",type:"input",businesstype:{type:"text"}},{type:"label",id:"id_234"},{id:"id_16677",type:"div"},{id:"id_155",type:"input",businesstype:{type:"password"}}];
const filterArray=[{type:'input',businesstype:{type:'text'}},{type:'div'}];
// Describes how to define "equality"
// i.e.: elements are equal if type and businesstype match
// e.g.: pick(["a", "b"], { a: 1, b: 2, c: 3}) -> { a: 1, b: 2}
const comparisonObjectFor = pick(["type", "businesstype"]);
// Compares an object's comparison representation to another object
const elEquals = compose(whereEq, comparisonObjectFor);
// Creates a filter method that searches an array
const inFilterArray = matchElements => el => any(elEquals(el), matchElements);
// Run the code on our data
filter(inFilterArray(filterArray), elements);
Running example here
I don't think this is necessarily the best solution (in terms of reusability, readability), but I'd advice you to not inline deep object/array comparison methods since:
You're probably going to use them more than once
They are hard to understand/predict if not given the right name & documentation
They are prone to (small) bugs because of their complexity
In other words: since you've tagged lodash and Ramda, I can safely advice to use a well tested, well used library for the comparison of your objects.