I'm attempting to convert two lists to json.
For example :
l1 = ['a','b','a']
l2 = ['q','r','s']
should be converted to :
[{
"name": "g",
"children": [{
"name": "a",
"children": [{
"name": "q"
}, {
"name": "s"
}]
},
{
"name": "b",
"children": [{
"name": "r"
}]
}
]
}]
Closest I have is :
l1 = ['a','b','a']
l2 = ['q','r','s']
nameDict = {}
childrenDict = {}
l1 = l1.map(x => {
return({name: x});
});
console.log(l1);
l2 = l2.map(x => {
return({children: x});
});
console.log(l2);
var c = l1.map(function(e, i) {
return [e, l2[i]];
});
console.log(JSON.stringify(c))
which produces :
[[{"name":"a"},{"children":"q"}],
[{"name":"b"},{"children":"r"}],
[{"name":"a"},{"children":"s"}]]
How to combine the elements produce ? :
[{
"name": "g",
"children": [{
"name": "a",
"children": [{
"name": "q"
}, {
"name": "s"
}]
},
{
"name": "b",
"children": [{
"name": "r"
}]
}
]
}]
Disclaimer: Since we don't know where the g comes from, I will only build the root children array.
Since your arrays have the same length, you can use a plain for and use with the index to play with both arrays. Just build an array and check each iteration if the "child" already exists. If not, create it.
l1 = ['a','b','a']
l2 = ['q','r','s']
let gChildren = []
for(let i = 0; i < l1.length; i++){
let group = gChildren.find(c => c.name === l1[i])
if(!group){
group = { name: l1[i], children: [] }
gChildren.push(group)
}
group.children.push({ name: l2[i] })
}
console.log(gChildren)
Here is working code that accounts for your pre-existing structure that accomplishes the result you are looking for.
let data1 = ["a","b","a"];
let data2 = ["q","r","s"];
let outputData = [{name: "g", children: []}];
for (let i=0;i < data1.length;i++) {
let found = false;
for (let j=0;j < outputData[0].children.length;j++) {
if (outputData[0].children[j].name === data1[i]) {
outputData[0].children[j].children.push({name: data2[i]});
found = true;
}
}
if (found === false) {
outputData[0].children.push({name: data1[i], children: [{name: data2[i]}]});
}
}
console.log(JSON.stringify(outputData));
This is a good use case for a Array.prototype.reduce, where you want to iterate over an array but end up with a single value.
l1.reduce((acc, val, i) => {
const l2Val = l2[i]
const foundObj = acc.find(o => o.name === val)
if (foundObj) {
foundObj.children.push({name: l2Val})
} else {
acc.push({
name: val,
children: [{name: l2Val}]
})
}
return acc
}, [])
Here, on each iteration I'm just adding the child to the children array for that item, or creating the value for the item if it doesn't already exist.
I have no idea what g corresponds to so I've left it out, but you can add the array created from reduce to another object or array if you want.
You could transpose the array and use the information as path to the final child object
l1 = ['a', 'b', 'a']
l2 = ['q', 'r', 's']
transposes to
[
['a', 'q'], // a -> q
['b', 'r'], // b -> r
['a', 's'] // a -> s
]
which is now works with reduce.
The advantage is to use it with longer pathes to the final children, like
[
['a', 'x', 'y', 'z'],
...
]
which returns a nested object with the given relation to each other.
const
transpose = array => array.reduce((r, a) => a.map((v, i) => [...(r[i] || []), v]), []);
var l1 = ['a', 'b', 'a'],
l2 = ['q', 'r', 's'],
result = transpose([l1, l2]).reduce((r, a) => {
a.reduce((q, name) => {
var temp = (q.children = q.children || []).find(o => o.name === name);
if (!temp) q.children.push(temp = { name });
return temp
}, r);
return r;
}, { name: 'g' });
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
A bit shorter with filter on the distinct keys :
var l1 = ['a','b','a'], l2 = ['q','r','s']
var children = [...new Set(l1)].map(k => ({ name: k, children:
l2.filter((v, i) => l1[i] == k).map(v => ({ name: v })) }))
console.log( [{ name: 'g', children }] )
Or more efficient with intermediate object of the groups :
var l1 = ['a','b','a'], l2 = ['q','r','s']
var children = Object.entries(
l1.reduce((o, k, i) => ((o[k] = o[k] || []).push(l2[i]), o), {})
).map(([k, v]) => ({ name: k, children: v.map(v => ({ name: v})) }))
console.log( [{ name: 'g', children }] )
Related
i'm trying to duplicate objects based on two properties that have multiple values differentiated by a comma.
For example:
I have an object
const obj = {
id: 1
date: "2021"
tst1: "111, 222"
tst2: "AAA, BBB"
}
And I would like the result to be an array of 2 objects in this case (because there are 2 values in tst1 OR tst2, these 2 properties will always have the same nr of values differentiated by a comma)
[{
id: 1,
date: "2021",
tst1: "111",
tst2: "AAA",
},
{
id: 1,
date: "2021",
tst1: "222",
tst2: "BBB",
}]
What I tried is this:
I created a temporary object
const tempObject = {
id: obj.id,
date: obj.date,
}
And then I would split and map the property that has multiple values, like this:
cont newObj = obj.tst1.split(",").map(function(value) {
let finalObj = {}
return finalObj = {
id: tempObject.id,
date: tempObject.date,
tst1: value,
})
And now, the newObj is an array of objects and each object contains a value of tst1.
The problem is I still have to do the same for the tst2...
And I was wondering if there is a simpler method to do this...
Thank you!
Here is an example that accepts an array of duplicate keys to differentiate. It first maps them to arrays of entries by splitting on ',' and then trimming the entries, then zips them by index to create sub-arrays of each specified property, finally it returns a result of the original object spread against an Object.fromEntries of the zipped properties.
const mapDuplicateProps = (obj, props) => {
const splitProps = props.map((p) =>
obj[p].split(',').map((s) => [p, s.trim()])
);
// [ [[ 'tst1', '111' ], [ 'tst1', '222' ]], [[ 'tst2', 'AAA' ], [ 'tst2', 'BBB' ]] ]
const dupeEntries = splitProps[0].map((_, i) => splitProps.map((p) => p[i]));
// [ [[ 'tst1', '111' ], [ 'tst2', 'AAA' ]], [[ 'tst1', '222' ], [ 'tst2', 'BBB' ]] ]
return dupeEntries.map((d) => ({ ...obj, ...Object.fromEntries(d) }));
};
const obj = {
id: 1,
date: '2021',
tst1: '111, 222',
tst2: 'AAA, BBB',
};
console.log(mapDuplicateProps(obj, ['tst1', 'tst2']));
Not sure if that's what you're searching for, but I tried making a more general use of what you try to do:
const duplicateProperties = obj => {
const properties = Object.entries(obj);
let acc = [{}];
properties.forEach(([key, value]) => {
if (typeof value === 'string' && value.includes(',')) {
const values = value.split(',');
values.forEach((v, i) => {
if (!acc[i]) {
acc[i] = {};
}
acc[i][key] = v.trim();
});
} else {
acc.forEach(o => o[key] = value);
}
});
return acc;
};
const obj = {
id: 1,
date: '2021',
tst1: '111, 222',
tst2: 'AAA, BBB',
};
console.log(duplicateProperties(obj));
You could start by determining the length of the result using Math.max(), String.split() etc.
Then you'd create an Array using Array.from(), returning the correct object for each value of the output index.
const obj = {
id: 1,
date: "2021",
tst1: "111, 222",
tst2: "AAA, BBB",
}
// Determine the length of our output array...
const length = Math.max(...Object.values(obj).map(s => (s + '').split(',').length))
// Map the object using the relevant index...
const result = Array.from({ length }, (_, idx) => {
return Object.fromEntries(Object.entries(obj).map(([key, value]) => {
const a = (value + '').split(/,\s*/);
return [key, a.length > 1 ? a[idx] : value ]
}))
})
console.log(result)
.as-console-wrapper { max-height: 100% !important; }
I have an array of objects i want to filter only the unique style and is not repeated .
const arrayOfObj = [ {name:'a' , style:'p'} , {name:'b' , style:'q'} , {name:'c' , style:'q'}]
result expected : [ {name:'a' , style:'p'}]
Here is a solution in O(n) time complexity. You can iterate all entries to track how often an entry occurs. And then use the filter() function to filter the ones that occur only once.
const arrayOfObj = [
{ name: "a", style: "p" },
{ name: "b", style: "q" },
{ name: "c", style: "q" },
]
const styleCount = {}
arrayOfObj.forEach((obj) => {
styleCount[obj.style] = (styleCount[obj.style] || 0) + 1
})
const res = arrayOfObj.filter((obj) => styleCount[obj.style] === 1)
console.log(res)
On of the possible solutions depending on your performance / readability needs can be:
arrayOfObj.filter(a => arrayOfObj.filter(obj => obj.style === a.style).length === 1)
Use splice when you find the existing item and remove it
const arrayOfObj = [{
name: 'a',
style: 'p'
}, {
name: 'b',
style: 'q'
}, {
name: 'c',
style: 'q'
}]
const result = arrayOfObj.reduce((acc, x) => {
const index = acc.findIndex(y => y.style === x.style);
if (index >= 0) {
acc.splice(index, 1);
} else {
acc.push(x);
}
return acc;
}, [])
console.log(result)
Here is a solution in O(n) time complexity. You can iterate all entries to track how often an entry occurs. And then use the filter() function to filter the ones that occur only once.
const arrayOfObj = [ {name:'a' , style:'p'} , {name:'b' , style:'q'} , {name:'c' , style:'q'}];
let count = {};
arrayOfObj.forEach(({style}) => {
count[style] = (count[style] || 0) + 1;
});
let result = arrayOfObj.filter(({style}) => count[style] === 1);
console.log(result);
You reduce it. Check if in the array already an element with the same style exists and remove it from the accumulator otherwise push it to the accumulator
const arr = [
{ name: "a", style: "p" },
{ name: "b", style: "q" },
{ name: "c", style: "q" }
];
let result = arr.reduce((a,v) => {
let i = a.findIndex(el => el.style === v.style);
if(i !== -1) {
a.splice(i,1);
return a;
}
a.push(v)
return a;
},[])
console.log(result);
There is a one liner answer too if you are using lodash library
(uniqBy(array, iteratee))
const arr = [
{ name: "a", style: "p" },
{ name: "b", style: "q" },
{ name: "c", style: "q" }
];
let result = _.uniqBy(arrayOfObj,'style')
console.log(result)
My existing array object are
//existing object
var existing = [
{
'ProviderCode':'aa',
'msg':'....',
},{
'ProviderCode':'bb',
'msg':'....',
},{
'ProviderCode':'cc',
'msg':'....',
},{
'ProviderCode':'dd',
'msg':'....',
},{
'ProviderCode':'ee',
'msg':'....',
}];
new object I'm comparing to
var new = [
{
'ProviderCode':'bb',
'msg':'....',
},{
'ProviderCode':'cc',
'msg':'....',
},{
'ProviderCode':'ee',
'msg':'....',
},{
'ProviderCode':'ff',
'msg':'....',
},{
'ProviderCode':'gg',
'msg':'....',
}];
I would like to generate same, remove and add array based on the two array objects, I can get the same array object but not the remove and add object from the objects.
var same = []; //bb, cc, ee //these will be the match
var remove = []; //aa , dd //will be remove from existing
var add = []; //ff, gg //will be consider as add
//I can get the same using below:
e.forEach(function(ev,ei,ea){
n.forEach(function(nv,ni,na){
if( ev.ProviderCode === nv.ProviderCode ){
s.push({ProviderCode:ev.ProviderCode,msg:"Same, do nothing"});
}
});
});
/* //output:
[{
"ProviderCode": "bb",
"msg": "Same, do nothing"
}, {
"ProviderCode": "cc",
"msg": "Same, do nothing"
}, {
"ProviderCode": "ee",
"msg": "Same, do nothing"
}]
*/
//but how do I get remove and add array object?
//remove will be:
/* //output:
[{
"ProviderCode": "aa",
"msg": "removed"
}, {
"ProviderCode": "dd",
"msg": "removed"
}]
*/
//add will be:
/* //output:
[{
"ProviderCode": "ff",
"msg": "added"
}, {
"ProviderCode": "gg",
"msg": "added"
}]
*/
You can use Array.prototype.filter & Array.prototype.find for this:
let existing = [{ProviderCode:'aa'},{ProviderCode:'bb'},{ProviderCode:'cc'},{ProviderCode:'dd'},{ProviderCode:'ee'}];
let newData = [{ProviderCode:'bb'},{ProviderCode:'cc'},{ProviderCode:'ee'},{ProviderCode:'ff'},{ProviderCode:'gg'}];
let added = newData.filter(d => !existing.find(e => d.ProviderCode === e.ProviderCode));
console.log("ADDED:", added);
let removed = existing.filter(d => !newData.find(e => d.ProviderCode === e.ProviderCode));
console.log("REMOVED:", added);
let same = newData.filter(d => existing.find(e => d.ProviderCode === e.ProviderCode));
console.log("SAME:", same);
With a library like lodash this is a bit easier:
let existing = [{ProviderCode:'aa'},{ProviderCode:'bb'},{ProviderCode:'cc'},{ProviderCode:'dd'},{ProviderCode:'ee'}];
let newData = [{ProviderCode:'bb'},{ProviderCode:'cc'},{ProviderCode:'ee'},{ProviderCode:'ff'},{ProviderCode:'gg'}];
console.log("ADDED:" , _.differenceBy(newData, existing, 'ProviderCode'));
console.log("REMOVED:", _.differenceBy(existing, newData, 'ProviderCode'));
console.log("SAME:" , _.intersectionBy(newData, existing, 'ProviderCode'));
<script src="https://unpkg.com/lodash#4.17.15/lodash.min.js"></script>
You can use this little "library" that provides set operations for JS Map objects:
function mapUnion(m1, m2) {
let m = new Map();
for (let [k, v] of m1)
m.set(k, v);
for (let [k, v] of m2)
m.set(k, v);
return m;
}
function mapIntersection(m1, m2) {
let m = new Map();
for (let [k, v] of m1)
if (m2.has(k))
m.set(k, v);
return m;
}
function mapDifference(m1, m2) {
let m = new Map();
for (let [k, v] of m1)
if (!m2.has(k))
m.set(k, v);
return m;
}
Having this, you can convert both your arrays to Maps:
let m1 = new Map(oldArray.map(x => [x.ProviderCode, x]))
let m2 = new Map(newArray.map(x => [x.ProviderCode, x]))
and do whatever you want with these, for example,
console.log(mapIntersection(m1, m2)) // bb=>..., cc=>..., ee=>...
console.log(mapDifference(m1, m2)) // aa=>..., dd=>...
If you need arrays as results:
commonObjects = Array.from(mapIntersection(m1, m2).values())
You could take some sets and get the wanted items for each change.
const
providerCode = ({ ProviderCode }) => ProviderCode;
var existingData = [{ ProviderCode: 'aa' }, { ProviderCode: 'bb' }, { ProviderCode: 'cc' }, { ProviderCode:'dd' }, { ProviderCode: 'ee' }],
newData = [{ ProviderCode: 'bb' }, { ProviderCode: 'cc' }, { ProviderCode: 'ee' }, { ProviderCode: 'ff' }, { ProviderCode: 'gg' }],
existingSet = new Set(existingData.map(providerCode)),
newSet = new Set(newData.map(providerCode)),
same = [...existingSet].filter(Set.prototype.has, newSet),
add = [...newSet].filter(v => !existingSet.has(v)),
remove = [...existingSet].filter(v => !newSet.has(v));
console.log(...same);
console.log(...add);
console.log(...remove);
I have two list, as bellow:
var a = ["a", "b"]
var b = [{name:"a1", belong_type:"a" }, {name:"a2", belong_type:"a" }, {name:"b1", belong_type:"b" },]
I want to put them like this:
var data = {}
a.forEach(a_item => {
data[a_item] = []
b.forEach(b_item => {
if (a_item === b_item.belong_type){
data[a_item].push(b_item)
}
})
})
console.log(data)
the result is :
{ a:
[ { name: 'a1', belong_task_type: 'a' },
{ name: 'a2', belong_task_type: 'a' } ],
b: [ { name: 'b1', belong_task_type: 'b' } ] }
I think my method use two forEach, I don't know whether there is a better way to realize the result, who can tell me if there is a better way?
You could use reduce method on a array and inside use filter method on b array to return objects where belong_type is equal to current element in reduce.
var a = ["a", "b"]
var b = [{name:"a1", belong_type:"a" }, {name:"a2", belong_type:"a" }, {name:"b1", belong_type:"b" }]
const result = a.reduce((r, e) => {
r[e] = b.filter(({belong_type}) => belong_type == e)
return r;
}, {})
console.log(result)
You could also use Object.assign method inside reduce to write it as a one-liner.
var a = ["a", "b"]
var b = [{name:"a1", belong_type:"a" }, {name:"a2", belong_type:"a" }, {name:"b1", belong_type:"b" }]
const result = a.reduce((r, e) => Object.assign(r, {[e]: b.filter(({belong_type}) => belong_type == e)}), {})
console.log(result)
I have an array of objects as following :
[
{"id":1,"lib":"A","categoryID":10,"categoryTitle":"Cat10","moduleID":"2","moduleTitle":"Module 2"},
{"id":2,"lib":"B","categoryID":10,"categoryTitle":"Cat10","moduleID":"2","moduleTitle":"Module 2"},
...
{"id":110,"lib":"XXX","categoryID":90,"categoryTitle":"Cat90","moduleID":"4","moduleTitle":"Module 4"}
]
I want to group this array by (moduleID,moduleTitle) and then by (categoryID,categoryTitle).
This is what I tried :
function groupBy(data, id, text) {
return data.reduce(function (rv, x) {
var el = rv.find(function(r){
return r && r.id === x[id];
});
if (el) {
el.children.push(x);
} else {
rv.push({ id: x[id], text: x[text], children: [x] });
}
return rv;
}, []);
}
var result = groupBy(response, "moduleID", "moduleTitle");
result.forEach(function(el){
el.children = groupBy(el.children, "categoryID", "categoryTitle");
});
The above code is working as expected, but as you can see, after the first grouping I had to iterate again over the array which was grouped by the moduleId in order to group by the categoryId.
How can I modify this code so I can only call groupBy function once on the array ?
Edit:
Sorry this might be late, but I want this done by using ES5, no Shim and no Polyfill too.
Here's one possible (although may be a bit advanced) approach:
class DefaultMap extends Map {
constructor(factory, iter) {
super(iter || []);
this.factory = factory;
}
get(key) {
if (!this.has(key))
this.set(key, this.factory());
return super.get(key);
}
}
Basically, it's the a Map that invokes a factory function when a value is missing. Now, the funny part:
let grouper = new DefaultMap(() => new DefaultMap(Array));
for (let item of yourArray) {
let firstKey = item.whatever;
let secondKey = item.somethingElse;
grouper.get(firstKey).get(secondKey).push(item);
}
For each firstKey this creates a Map inside grouper, and the values of those maps are arrays grouped by the second key.
A more interesting part of your question is that you're using compound keys, which is quite tricky in JS, since it provides (almost) no immutable data structures. Consider:
items = [
{a: 'one', b: 1},
{a: 'one', b: 1},
{a: 'one', b: 2},
{a: 'two', b: 2},
]
let grouper = new DefaultMap(Array);
for (let x of items) {
let key = [x.a, x.b]; // wrong!
grouper.get(key).push(x);
}
So, we're naively grouping objects by a compound key and expecting to see two objects under ['one', 1] in our grouper (which is one level for the sake of the example). Of course, that won't work, because each key is a freshly created array and all of them are different for Map or any other keyed storage.
One possible solution is to create an immutable structure for each key. An obvious choice would be to use Symbol, e.g.
let tuple = (...args) => Symbol.for(JSON.stringify(args))
and then
for (let x of items) {
let key = tuple(x.a, x.b); // works
grouper.get(key).push(x);
}
You could extend your function by using an array for the grouping id/names.
function groupBy(data, groups) {
return data.reduce(function (rv, x) {
groups.reduce(function (level, key) {
var el;
level.some(function (r) {
if (r && r.id === x[key[0]]) {
el = r;
return true;
}
});
if (!el) {
el = { id: x[key[0]], text: x[key[1]], children: [] };
level.push(el);
}
return el.children;
}, rv).push({ id: x.id, text: x.lib });
return rv;
}, []);
}
var response = [{ id: 1, lib: "A", categoryID: 10, categoryTitle: "Cat10", moduleID: "2", moduleTitle: "Workflow" }, { id: 2, lib: "B", categoryID: 10, categoryTitle: "Cat10", moduleID: "2", moduleTitle: "Module 2" }, { id: 110, lib: "XXX", categoryID: 90, categoryTitle: "Cat90", moduleID: "4", moduleTitle: "Module 4" }],
result = groupBy(response, [["moduleID", "moduleTitle"], ["categoryID", "categoryTitle"]]);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Version with path as id.
function groupBy(data, groups) {
return data.reduce(function (rv, x) {
var path = [];
var last = groups.reduce(function (level, key, i) {
path.length = i;
path[i] = key[0].slice(0, -2).toUpperCase() + ':' + x[key[0]];
var id = path.join(';'),
el = level.find(function (r) {
return r && r.id === id;
});
if (!el) {
el = { id: path.join(';'), text: x[key[1]], children: [] };
level.push(el);
}
return el.children;
}, rv);
last.push({ id: path.concat('NODE:' + x.id).join(';') });
return rv;
}, []);
}
var response = [{ id: 1, lib: "A", categoryID: 10, categoryTitle: "Cat10", moduleID: "2", moduleTitle: "Workflow" }, { id: 2, lib: "B", categoryID: 10, categoryTitle: "Cat10", moduleID: "2", moduleTitle: "Module 2" }, { id: 110, lib: "XXX", categoryID: 90, categoryTitle: "Cat90", moduleID: "4", moduleTitle: "Module 4" }];
var result = groupBy(response, [["moduleID", "moduleTitle"], ["categoryID", "categoryTitle"]]);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could do it like this:
const exit = Symbol("exit");
function groupBy(arr, ...props){
const root = {};
for(const el of arr){
const obj = props.map(key => el[key])
.reduce((obj, key) => obj[key] || (obj[key] = {}), root);
(obj[exit] || (obj[exit] = [])).push(el);
}
}
So you can access it like:
const grouped = groupBy(response, "moduleID", "moduleTitle");
console.log( grouped[2]["workflow"][exit] );
You might leave away that exit symbol, but it feels a bit wrong to mix a nested tree with arrays.