I had an array of objects with duplicated ids. Each object associated with the id has a property A and property B. For each loop, first index array's property A has value, but property B is null. In second index array, property A null, property B has value. The first two index will merge according to the same id. The output doesn't produce values for both property A and property B, but either one still null.
Array
{
id: 123,
A: “value A1”
B: null
},
{
id: 123,
A: null,
B: “value b”
},
{
id: 123,
A: “value A2”
B: null
},
{
id: 456,
A: "a2 value",
B: "b2 value"
}
Code
var output = _.groupBy(arr, function(o){
return o.id;
})
Output
{
id: 123,
A: [“value A1”,“value A2”]
B: null
},
{
id: 456,
A: ["a2 value"],
B: "b2 value"
}
Expected
{
id: 123,
A: [“value A1”,“value A2”]
B: “value b”
},
{
id: 456,
A: ["a2 value"],
B: "b2 value"
}
You can do it without underscore and loadash:
var arr = [
{
id: 123,
A: "value A1",
B: null
},
{
id: 123,
A: null,
B: "value b"
},
{
id: 123,
A: "value A2",
B: null
},
{
id: 456,
A: "a2 value",
B: "b2 value"
}
];
var arr2 = arr.reduce((m, o) => {
var sameObj = m.find(it => it.id === o.id);
if (sameObj) {
o.A && (sameObj.A.push(o.A));
sameObj.B = sameObj.B || o.B;
} else {
o.A = o.A ? [o.A] : [];
m.push(o);
}
return m;
}, []);
console.log(arr2);
Following is without underscore or lodash. Just with vanilla JavaScript.
var data = [{
id: 123,
A: "some value",
B: null
},
{
id: 123,
A: null,
B: "b value"
},
{
id: 456,
A: "a2 value",
B: "b2 value"
}
];
var outputObject = {},
outputArray = [];
data.forEach(function(obj) {
if (!outputObject[obj.id]) {
outputObject[obj.id] = obj;
} else {
if (obj.B !== null) {
outputObject[obj.id].B = obj.B;
}
if (obj.A !== null) {
outputObject[obj.id].A = obj.A;
}
}
});
//Convert to an array
Object.keys(outputObject).forEach(function(key) {
outputArray.push(outputObject[key]);
});
console.log(outputArray);
Related
Given an array of objects all with the same property names, with javascript how would you create a new object made up of key:value pairs that are found in all the objects of the array?
For example, given:
[
{
a: 'foo',
b: 'bar',
c: 'zip'
},
{
a: 'urg',
b: 'bar',
c: 'zip'
},
{
a: 'foo',
b: 'bar',
c: 'zip'
}
]
Result:
{
b: 'bar',
c: 'zip'
}
Start with all the elements of the first item (cloned, because we don't want to change the original data), then remove any key-value pairs that do not show up in the subsequent items:
const data = [
{
a: 'foo',
b: 'bar',
c: 'zip'
},
{
a: 'urg',
b: 'bar',
c: 'zip'
},
{
a: 'foo',
b: 'bar',
c: 'zip'
}
];
const [first, ...rest] = data;
const result = rest.reduce((a, e) => {
Object.keys(a).forEach(k => {
if (!k in e || e[k] !== a[k]) {
delete a[k];
}
});
return a;
}, {...first});
console.log(result);
Just compare each value in the first object with every other object.
const [first, ...others] = [
{
a: 'foo',
b: 'bar',
c: 'zip'
},
{
a: 'urg',
b: 'bar',
c: 'zip'
},
{
a: 'foo',
b: 'bar',
c: 'zip'
}
];
for (const [key, value] of Object.entries(first)) {
for (const object of others) {
if (object[key] !== value) {
delete first[key];
}
}
}
console.log(first);
I have an array of objects that has a nested array of objects in it. So the array looks something like:
const list = [
{
A: "a1",
B: "b1",
C: [
{
A: "a22",
B: "b12"
},
{
A: "a11",
B: "b11"
},
{
A: "a10",
B: "b10"
}
]
},
{
A: "a2",
B: "b2",
C: [
{
A: "a10",
B: "b10"
},
{
A: "a01",
B: "b01"
}
]
},
{
A: "a0",
B: "b0",
C: [
{
A: "a22",
B: "b22"
},
{
A: "a21",
B: "b21"
},
{
A: "a20",
B: "b20"
}
]
}
];
As can be seen I have an array of objects and each object as one or more fields that is also an array of objects. I can sort the array of objects based on one of the keys and it works just fine. What I want to do is sort by one of the keys in the nested array. For example sorting on C.A would yield something like (expected):
[
{
A: "a0",
B: "b0",
C: [
{
A: "a22",
B: "b22"
},
{
A: "a21",
B: "b21"
},
{
A: "a20",
B: "b20"
}
]
},
{
A: "a1",
B: "b1",
C: [
{
A: "a12",
B: "b12"
},
{
A: "a11",
B: "b11"
},
{
A: "a10",
B: "b10"
}
]
},
{
A: "a2",
B: "b2",
C: [
{
A: "a10",
B: "b10"
},
{
A: "a01",
B: "b01"
}
}
];
Ideas?
The way to get one's head clear on such things is to factor out the sort functions to explicitly state the objective, like this (I think I understand the objective)...
// sort a and b by the smallest value of A in their C arrays
const myCompare = (a, b) => {
return a.minAinC.localeCompare(b.minAinC);
};
// get the lexically smallest value of A in an object's C array
const minAinC = obj => {
const minC = obj.C.reduce((acc, o) => acc.A.localeCompare(o.A) > 0 ? o : acc, obj.C[0])
return minC.A;
};
// preprocess the outer array and cache a minAinC value on each, making the next sort efficient (optional)
const data = getData()
const readyToSort = data.map(o => ({ ...o, minAinC: minAinC(o) }));
const sorted = readyToSort.sort(myCompare)
console.log(sorted)
function getData() {
return [{
A: "a1",
B: "b1",
C: [{
A: "a22",
B: "b12"
},
{
A: "a11",
B: "b11"
},
{
A: "a10",
B: "b10"
}
]
},
{
A: "a2",
B: "b2",
C: [{
A: "a10",
B: "b10"
},
{
A: "a01",
B: "b01"
}
]
},
{
A: "a0",
B: "b0",
C: [{
A: "a22",
B: "b22"
},
{
A: "a21",
B: "b21"
},
{
A: "a20",
B: "b20"
}
]
}
];
}
I am trying to transform a nested structure, using the library of lodash, I have achieved the expected result, but they are not functional if the structure changes, so I come to you to help me make more robust the function that transforms the JSON.
the initial structure looks like this
const data = {
foo: {
bar: {
baz: [{ a: 1, b: 2, c: 3 }]
},
baz: {
bar: [{ a: 1, b: 2, c: 3 }]
},
foo: {
bar: [{ a: 1, b: 2, c: 3 }]
}
},
bar: {
baz: {
bar: [{ a: 1, b: 2, c: 3 }]
}
},
baz: {
foo: {
bar: [{ a: 1, b: 2, c: 3 }]
}
}
};
after being transformed
const transform = [
{
name: 'barfoo',
results: [{ a: 1, b: 2, c: 3 }]
},
{
name: 'bazfoo',
results: [{ a: 1, b: 2, c: 3 }]
},
{
name: 'foofoo',
results: [{ a: 1, b: 2, c: 3 }]
},
{
name: 'bazbar',
results: [{ a: 1, b: 2, c: 3 }]
},
{
name: 'foobaz',
results: [{ a: 1, b: 2, c: 3 }]
}
];
The idea of the transformation is to join the nested key of the first level with the key of the parent node to generate the value of name in the new object and the value of the object in the 2 level as the value of results
for example for the first iteration of foo object in data
name = key(foo.bar) + key(foo)
results = value(foo.bar.baz)
name = 'barfoo'
results = [{ a: 1, b: 2, c: 3 }]
name = key(foo.baz) + key(foo)
results = value(foo.baz.bar)
name = 'bazfoo'
results = [{ a: 1, b: 2, c: 3 }]
name = key(foo.foo) + key(foo)
results = value(foo.foo.bar)
name = 'foofoo'
results = [{ a: 1, b: 2, c: 3 }]
and so with the other objects that are inside data.
I'm not sure if the structure will ever vary, but I added a few extra test cases so you can see how this will behave in some additional scenarios.
const data = {
foo: {
bar: {
baz: [{ a: 1, b: 2, c: 3 }]
},
baz: {
bar: [{ a: 1, b: 2, c: 3 }]
},
foo: {
bar: [{ a: 1, b: 2, c: 3 }]
}
},
bar: {
baz: {
bar: [{ a: 1, b: 2, c: 3 }]
}
},
baz: {
foo: {
bar: [{ a: 1, b: 2, c: 3 }]
}
},
a1: {
a2: [{ a: 1, b: 2, c: 3 }]
},
b1: [{ a: 1, b: 2, c: 3 }],
c1: {
c2: {
c3: {
c4: [{ a: 1, b: 2, c: 3 }]
}
},
c5: [{ a: 1, b: 2, c: 3 }]
},
d1: {
d2: {
d3: undefined
}
},
e1: {
e2: {
e3: null
}
},
f1: {
f2: {
// Ignored
}
}
};
function transformObject(object, name) {
if (!name) {
name = "";
}
return _.flatten(_.map(object, function(value, key) {
if (typeof value === "undefined"
|| value === null
|| _.isArray(value)) {
return {
name: name,
results: value
}
}
var objectName = key + name;
return transformObject(value, objectName);
}));
}
transformObject(data);
I have javascript objects which looks similar to this:
{
id: 43,
name: 'ajajaj'
nestedObj1: {
id: 53,
name: 'ababab'
foo: 'xxxx'
nestedObj2: {
id: 90,
name: 'akakaka'
foo2: 'sdsddd'
surname: 'sdadasd'
nestedObj3: {
id: ..
name: ..
...
},
objectsArr: [
{id: .., name: ..., nestedOb4: {...} },
{id: .., name: ..., nestedOb4: {...} }
]
},
foo0: ...
}
name: 'blabla'
}
Each objects which I have, has property id and name. My goal is pick all properties in first level, so properties of object with id 43 I pick all, and from every nested objects, I just pick properties id and name, and another will be thrown away.
I am using in project lodash, so I found function pick, but I don't know how to use this function for nested objects. Also note that nested objects could be also in array. Exist some elegant way for that please? Thanks in advice.
pick function of lodash is able to fetch nested property like so:
_.pick(obj, [
'id',
'name',
'nestedObj1.id',
'nestedObj1.name',
'nestedObj1.nestedObj2.id',
'nestedObj1.nestedObj2.name',
'nestedObj1.nestedObj2.nestedObj3.id',
'nestedObj1.nestedObj2.nestedObj3.name',
'nestedObj1.nestedObj2.objectsArr[0].id',
'nestedObj1.nestedObj2.objectsArr[0].name',
'nestedObj1.nestedObj2.objectsArr[0].nestedObj4.id',
'nestedObj1.nestedObj2.objectsArr[0].nestedObj4.name',
'nestedObj1.nestedObj2.objectsArr[1].id',
'nestedObj1.nestedObj2.objectsArr[1].name',
'nestedObj1.nestedObj2.objectsArr[1].nestedObj4.id',
'nestedObj1.nestedObj2.objectsArr[1].nestedObj4.name',
])
The problem is, you must know the array length to pick all objects in them.
To mitigate this, you can use this function to iterate over arrays:
const _ = require('lodash')
const pickExtended = (object, paths) => {
return paths.reduce((result, path) => {
if (path.includes("[].")) {
const [collectionPath, itemPath] = path.split(/\[]\.(.+)/);
const collection = _.get(object, collectionPath);
if (!_.isArray(collection)) {
return result;
}
const partialResult = {};
_.set(
partialResult,
collectionPath,
_.map(collection, item => pickExtended(item, [itemPath]))
);
return _.merge(result, partialResult);
}
return _.merge(result, _.pick(object, [path]));
}, {});
};
You can then get the result you want with:
pickExtended(obj, [
'id',
'name',
'nestedObj1.id',
'nestedObj1.name',
'nestedObj1.nestedObj2.id',
'nestedObj1.nestedObj2.name',
'nestedObj1.nestedObj2.nestedObj3.id',
'nestedObj1.nestedObj2.nestedObj3.name',
'nestedObj1.nestedObj2.objectsArr[].id',
'nestedObj1.nestedObj2.objectsArr[].name',
'nestedObj1.nestedObj2.objectsArr[].nestedObj4.id',
'nestedObj1.nestedObj2.objectsArr[].nestedObj4.name',
])
Here is the associated mocha test:
describe("pickExtended", () => {
const object = {
a: 1,
b: "2",
c: { d: 3, e: 4 },
f: [
{ a: 11, b: 12, c: [{ d: 21, e: 22 }, { d: 31, e: 32 }] },
{ a: 13, b: 14, c: [{ d: 23, e: 24 }, { d: 33, e: 34 }] }
],
g: [],
h: [{ a: 41 }, { a: 42 }, { a: 43 }]
};
it("should pick properties in nested collection", () => {
expect(
pickExtended(object, ["a", "c.d", "f[].c[].d", "g[].a", "b[].a", "h[]"])
).to.deep.equal({
a: 1,
c: { d: 3 },
f: [{ c: [{ d: 21 }, { d: 31 }] }, { c: [{ d: 23 }, { d: 33 }] }],
g: []
});
});
});
Keep in mind that no performance test was done on this. It might get slow for big object.
You can do this using recursion, as long as your objects do have an end to the levels of nesting. Otherwise, you run the risk of causing a stack limit error.
function pickProperties(obj) {
var retObj = {};
Object.keys(obj).forEach(function (key) {
if (key === 'id' || key === 'name') {
retObj[key] = obj[key];
} else if (_.isObject(obj[key])) {
retObj[key] = pickProperties(obj[key]);
} else if (_.isArray(obj[key])) {
retObj[key] = obj[key].map(function(arrayObj) {
return pickProperties(arrayObj);
});
}
});
return retObj;
}
What's an elegent way - purely functional, ideally - to transform (reduce?) this array:
var in = [
{ a: 1, b: 'x', c: 'foo' },
{ a: 1, b: 'y', c: 'goo' },
{ a: 2, b: 'x', c: 'hoo' },
{ a: 2, b: 'y', c: 'joo' }
]
Into this:
var out = [
{ a: 1, x: 'foo', y: 'goo' },
{ a: 2, x: 'hoo', y: 'joo' }
]
The logic is that all elements should be joined based on their a property, and all b and c properties denote key/value pairs respectively that should be merged into the single object based on their shared a value.
You can use a hash object, and reduce to wrap the hashing like this:
const arr = [
{ a: 1, b: 'x', c: 'foo' },
{ a: 1, b: 'y', c: 'goo' },
{ a: 2, b: 'x', c: 'hoo' },
{ a: 2, b: 'y', c: 'joo' }
];
let result = Object.values( // the result is the values of the hash object
arr.reduce((hash, o) => { // hash is a hash object that make it easier to group the result
hash[o.a] = hash[o.a] || {a: o.a}; // if there is no object in the hash that have the value of the key a equal to o.a, then create a new one
hash[o.a][o.b] = o.c; // set the value of the key stored in o.b to o.c
return hash;
}, {})
);
console.log(result);
You could use a closure with a Map
var input = [{ a: 1, b: 'x', c: 'foo' }, { a: 1, b: 'y', c: 'goo' }, { a: 2, b: 'x', c: 'hoo' }, { a: 2, b: 'y', c: 'joo' }],
output = input.reduce((map => (r, o) => (!map.has(o.a) && map.set(o.a, r[r.push({ a: o.a }) - 1]), map.get(o.a)[o.b] = o.c, r))(new Map), []);
console.log(output);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use forEach and Object.assign to group by a and then map to return object values.
var data = [
{ a: 1, b: 'x', c: 'foo' },
{ a: 1, b: 'y', c: 'goo' },
{ a: 2, b: 'x', c: 'hoo' },
{ a: 2, b: 'y', c: 'joo' }
]
var r = {}
data.forEach(e => r[e.a] = Object.assign((r[e.a] || {}), {a: e.a, [e.b]: e.c}))
r = Object.keys(r).map(e => r[e])
console.log(r)
I like provided answers, but here is my attempt. I believe it's more readable, but it uses Object.assign and Object.values
const input = [
{ a: 1, b: 'x', c: 'foo' },
{ a: 1, b: 'y', c: 'goo' },
{ a: 2, b: 'x', c: 'hoo' },
{ a: 2, b: 'y', c: 'joo' }
]
const map = input.reduce((acc, obj) => {
const [a, key, value] = Object.values(obj)
const newObj = {a, [key]: value}
if (acc[a]) {
Object.assign(acc[a], newObj)
} else {
acc[a] = newObj
}
return acc
}, {})
console.log(Object.values(map))
Not sure if approach is elegant or functional, though returns expected result using for..of loops, Array.prototype.some() and Object.assign()
function props(array, key, prop1, prop2) {
let arr = [];
for (let obj of array) {
let o = {};
for (let {[key]:_key, [prop1]:_prop1, [prop2]:_prop2} of [obj]) {
o[_prop1] = _prop2;
o[key] = _key;
}
if (!arr.some(p => p[key] === o[key])) arr.push(o);
for (let prop of arr) {
if (prop[key] == o[key]) {
prop = Object.assign(prop, o)
}
}
}
return arr
}
var _in = [
{ a: 1, b: 'x', c: 'foo' },
{ a: 1, b: 'y', c: 'goo' },
{ a: 2, b: 'x', c: 'hoo' },
{ a: 2, b: 'y', c: 'joo' }
];
console.log(props(_in, "a", "b", "c"));