How to compare two array of object and get common objects - javascript

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.

Related

Flatten a deeply nested data structure of arrays, objects + strings into a list of data items while mapping the former parent-child relationship too

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

How do I take an array of objects and reduce it so that data at a repeated object key is combined?

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

Extract properties and unique values from GeoJson collection

Here's a geojson object, which has array of features, each feature has a properties object.
I understand that there are many question related to map arrays and objects but I couldn't find a similar case. I tried to use lodash map and groupBy to map the properties and group the values under their key but honestly I just don't know what the combination of functions should be.
I can get the property name part by doing the following:
// since properties are the same for all features
// I extract them alone first
let properties = Object.keys(features[0].properties)
properties.map(Prentelement =>
{
let formated = {
// this gives me the first part
propertyName: Prentelement,
// I can't figure out this part to map the values uniquely under
children: [
{
value: "alex"
},
{
value: "cairo"
}
]
}
return formated;
})
This is an example of the input format:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"Name": "cairo",
"Type": "Province"
}
},
{
"type": "Feature",
"properties": {
"Name": "alex",
"Type": "Province"
}
}
]
}
And what I want to do is a kind of summary on each available property and their possible values across different features. Please note that a value can be repeated across features but I want it available only once in the end result. So result would be an array like this:
[
{
propertyName: "Name",
children: [
{value: "alex"},
{value: "cairo"}
]
},
{
propertyName: "Type",
children: [
{value: "Province"}
]
}
]
Here you have one solution using first Array.reduce() to group the features array by the properties on an object. Note we use Sets to keep unique values only. Later, on a second step, we can Array.map() the entries of the previously generated object to get the desired structure:
let input = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {"Name": "cairo", "Type": "Province"}
},
{
"type": "Feature",
"properties": {"Name": "alex", "Type": "Province"}
}
]
};
// Step 1, group feature values by property.
let out = input.features.reduce((acc, {properties}) =>
{
Object.entries(properties).forEach(([key, val]) =>
{
acc[key] = acc[key] || new Set();
acc[key].add(val);
});
return acc;
}, {});
// Show the generated object on Step 1.
console.log("Step 1 - After grouping:", out);
for (const key in out)
{
console.log(`${key} => ${[...out[key]]}`);
}
// Step 2, map the entries of the generated object.
out = Object.entries(out).map(([k, v]) =>
({PropertyName: k, Children: [...v].map(x => ({Value: x}))})
);
console.log("Step 2 - After mapping:", out);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
Other documentation you may need to read:
Object.entries()
Spread Syntax
Another approach (using same input data provided by #Shidersz):
const result = input.features.reduce((acc, feature) => {
Object.keys(feature["properties"]).forEach(key => {
const index = acc.findIndex(property => {
return property["propertyName"] === key })
if (index === -1) {
acc.push({ propertyName: key, children: [ { value: feature["properties"][key]}] })
} else {
if (acc[index].children.findIndex(child => child.value === feature["properties"][key]) === -1) {
acc[index].children.push({ value: feature["properties"][key] })
}
}
})
return acc;
}, []);
console.log(JSON.stringify(result));

Find difference between two arrays

I have following Plunkr which works perfectly.
https://plnkr.co/edit/WDjoEK7bAVpKSJbAmB9D?p=preview
It uses the _.differenceWith() function of lodash, in order two save all array values, which are not contained by the two arrays.
var result = _.differenceWith(data, test, _.isEqual);
Now I have two problems:
1.) In our project we use an older Lodash Version where the function differenceWith is not implemented
2.) I only need to compare one value of the array. This currently compares the complete objects. I only need to compare the id property.
This will find the objects in arr1 that are not in arr2 based on the id attribute.
var arr1 = [ { "id": "1" }, { "id": "2" }, { "id": "3" } ];
var arr2 = [ { "id": "1" }, { "id": "2" } ];
var result = arr1.filter(o1 => arr2.filter(o2 => o2.id === o1.id).length === 0);
console.log(result);
Note that this example does not require lodash.
If you want to use a different comparison instead of id, you can change the o2.id === o1.id part to a different property.
Here is a more generic solution:
var arr1 = [ { "name": "a" }, { "name": "b" }, { "name": "c" } ];
var arr2 = [ { "name": "a" }, { "name": "c" } ];
function differenceWith(a1, a2, prop) {
return a1.filter(o1 => a2.filter(o2 => o2[prop] === o1[prop]).length === 0);
}
var result = differenceWith(arr1, arr2, 'name');
console.log(result);

Turn an object to a square bracket string (not using JSON.stringify)

I have this Javascript object (that is created on-the-fly by my plugin code):
{
"field": {
"name": "Name",
"surname": "Surname"
},
"address": {
"street": "Street",
"number": 0,
"postcode": 0,
"geo": {
"city": "City",
"country": "Country",
"state": "State"
}
},
"options": [1,4,6,8,11]
}
I don't want to turn this object to a JSON string, but I want to turn this object into another object, but with each field represented by a string, like this:
{
"field[name]": "Name",
"field[surname]": "Surname",
"address[street]": "Street",
"address[number]": 0,
"address[postcode]": 0,
"address[geo][city]": "City",
"address[geo][country]": "Country",
"address[geo][state]": "State",
"options[0]":1,
"options[1]":4,
"options[2]":6,
"options[3]":8,
"options[4]":11
}
Scenario:
I dont know how the original object will look like (or how deep it'll be), since it's part of a plugin and I have no idea how people will build their forms
I'm going to put this new object inside a FormData object, if it would only accept objects, it would be easier, because JSON can't upload files, but FormData object can
As I said in the comments, you need a for...in [MDN] loop to iterate over the properties of the object and can use recursion to subsequently convert nested objects:
function convert(obj, prefix, result) {
result = result || {};
// iterate over all properties
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
var value = obj[prop];
// build the property name for the result object
// first level is without square brackets
var name = prefix ? prefix + '[' + prop + ']' : prop;
if (typeof value !== 'object') {
// not an object, add value to final result
result[name] = value;
}
else {
// object, go deeper
convert(value, name, result);
}
}
}
return result;
}
// Usage:
var converted_data = convert(data);
DEMO
Still, I would recommend using JSON.
If you want to handle files as well, you might have to add an additional check for File objects. You'd want them raw in the result object:
else if (window.File && value instanceof File) {
result[name] = value;
}
// and for file lists
else if (window.FileList && value instanceof FileList) {
for (var i = 0, l = value.length; i < l; i++) {
result[name + '[' + i + ']'] = value.item(i);
}
}
It could be that the File (FileList) constructor is named differently in IE, but it should give you a start.
Not a big fan of reinventing the wheel, so here is how you could answer your question using object-scan. It's a great tool for data processing - once you wrap your head around it that is.
// const objectScan = require('object-scan');
const convert = (haystack) => objectScan(['**'], {
filterFn: ({ key, value, isLeaf, context }) => {
if (isLeaf) {
const k = key.map((e, idx) => (idx === 0 ? e : `[${e}]`)).join('');
context[k] = value;
}
}
})(haystack, {});
const data = { field: { name: 'Name', surname: 'Surname' }, address: { street: 'Street', number: 0, postcode: 0, geo: { city: 'City', country: 'Country', state: 'State' } }, options: [1, 4, 6, 8, 11] };
console.log(convert(data));
/* =>
{ 'options[4]': 11,
'options[3]': 8,
'options[2]': 6,
'options[1]': 4,
'options[0]': 1,
'address[geo][state]': 'State',
'address[geo][country]': 'Country',
'address[geo][city]': 'City',
'address[postcode]': 0,
'address[number]': 0,
'address[street]': 'Street',
'field[surname]': 'Surname',
'field[name]': 'Name' }
*/
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.7.1"></script>
Disclaimer: I'm the author of object-scan

Categories