I have below two objects which I want to merge.
[
{
"response_code": 1,
"response_message": [{
"a": 1000,
"b": 1000001,
"c": 10000002
}]
}]
[
{
"response_code": 1,
"response_message": [{
"a": 2000,
"b": 2000001,
"c": 20000002
}]
}
]
I want to merge them like below by having only one value of response code and merge values of response message like below way.
{
"response_code": 1,
"response_message": [
{
"a": 1000,
"b": 1000001,
"c": 10000002
},
{
"a": 2000,
"b": 2000001,
"c": 20000002
}]
}
Really stuck with such complicated merging where I want only once the value of response code and merge the values of response message.
Here I want to remove response code value from other array and merge/group the response message value with fist array.
I whipped up a little function for you:
And in accordance with the concerns raised by you in the comments, along with the test case where response_message was a string, I have edited the code snippet to include multiple cases to test.
const inputs = [
[{
"response_code": 1,
"response_message": [{
"a": 1000,
"b": 1000001,
"c": 10000002
}]
},
{
"response_code": 1,
"response_message": [{
"p": 1000,
"q": 1000001,
"r": 10000002
}]
}
],
[{
"response_code": 1,
"response_message": [{
"a": 1000,
"b": 1000001,
"c": 10000002
}]
},
{
"response_code": 1,
"response_message": 'No data'
}
],
[{
"response_code": 1,
"response_message": 'No data'
},
{
"response_code": 1,
"response_message": 'No data'
}
]
]
const getGroupedArr = (arr) => {
const codeMap = arr.reduce((cMap,obj) => {
let existingMessageArr = cMap.get(obj.response_code);
let arrayToAdd = Array.isArray(obj.response_message) ? obj.response_message : [];
if(existingMessageArr){
existingMessageArr.push(...arrayToAdd);
} else {
cMap.set(obj.response_code,arrayToAdd);
}
return cMap;
},new Map());
const iterator = codeMap[Symbol.iterator]();
const resultArr = [];
for (let item of iterator) {
resultArr.push({
response_code: item[0],
response_message: item[1]
})
}
return resultArr;
}
inputs.forEach((inputArr,index) => {
console.log(`Result for input ${index+1}`,getGroupedArr(inputArr));
})
Notice that I used Map where in JS most people prefer objects because maps in JS are iterable, but with an object I would've had to do an extra Object.keys() step, so this makes is slightly more efficient than the object approach, though a little more verbose.
Also note that in the third case, when no object with a particular response_code has any data, the result would be an empty array rather than a string. In weakly typed environments like JS, it is always a good practice to maintain some type consistency (which actually makes the input value of 'No data' in response_code not ideal), otherwise you may need to put type checks everywhere (like in the edited funciton in the above snippet).
Same function can be used in a contraint you mentioned in the comments, when the objects with same response_code exist in two different arrays (the two input arrays can simply be merged into one):
const inputArr1 = [{
"response_code": 1,
"response_message": [{
"a": 1000,
"b": 1000001,
"c": 10000002
}]
}]
const inputArr2 = [{
"response_code": 1,
"response_message": [{
"p": 1000,
"q": 1000001,
"r": 10000002
}]
}]
const getGroupedArr = (arr) => {
const codeMap = arr.reduce((cMap,obj) => {
let existingMessageArr = cMap.get(obj.response_code);
let arrayToAdd = Array.isArray(obj.response_message) ? obj.response_message : [];
if(existingMessageArr){
existingMessageArr.push(...arrayToAdd);
} else {
cMap.set(obj.response_code,arrayToAdd);
}
return cMap;
},new Map());
const iterator = codeMap[Symbol.iterator]();
const resultArr = [];
for (let item of iterator) {
resultArr.push({
response_code: item[0],
response_message: item[1]
})
}
return resultArr;
}
console.log(getGroupedArr([...inputArr1,...inputArr2]));
I think you're looking for a groupby.
Check this good old post from stackoverflow and be aware of the different answers/ implementaions:
Most efficient method to groupby on an array of objects
const arrayOf0 = yourArray.filter(item => item.response_code===0)
const arrayOf1 = yourArray.filter(item => item.response_code===1)
const merged0 = {response_code: 0, response_message: []};
const merged1 = {response_code: 1, response_message: []};
arrayOf0.forEach(item => {
merged0.response_message.push(item.response_message[0]
})
arrayOf1.forEach(item => {
merged1.response_message.push(item.response_message[0]
})
Something like this?
I have resolved the array merging by below code.
const mergedArray = [];
myarray.forEach((obj, index) => {
var newObj = {}
if(index > 0){
if(mergedArray.length > 0){
for(let i=0; i<obj.response_message.length;i++){
mergedArray[0].response_message.push(obj.response_message[i]);
}
}
}else{
newObj["response_code"] = obj.response_code ;
newObj["response_message"] = obj.response_message;
mergedArray.push(newObj);
}
})
Related
Which is the best way to "convert" associative array to standard (0 based indexes) array. Is there any other way than iteration and rewriting each item?
I have an array which was created by counting appearances of some properties and it is needed that way. The output array lookes like:
let ASSARR = {
"abc": { "c": 5, "p": 3 },
"def": { "c": 1, "p": 10 },
"ghi": { "c": 15, "p": 7 }
};
...and so.
I need to filter and sort it though then I need to "convert" it to standard array so it looked more like this:
let STARR = [
{ "i": "abc", "c": 5, "p": 3 },
{ "i": "def", "c": 1, "p": 10 },
{ "i": "ghi", "c": 15, "p": 7 }
];
Do I need to iterate by for or similar loop or maybe there is more effective way to do this?
Is there any other way than iteration and rewriting each item?
No, you'll need a loop (either in your code, or in code in the standard library that you call such as looping through the result of Object.entries).
Since it's a loop either way, I'd probably just write the loop (especially as doing so, you can loop once rather than multiple times). Here I'm assuming you want to create new objects rather than just adding an i property to the objects you have (but keep reading):
const result = [];
for (const i in ASSARR) {
result.push({
i,
...ASSARR[i],
});
}
let ASSARR = {
"abc": { "c": 5, "p": 3 },
"def": { "c": 1, "p": 10 },
"ghi": { "c": 15, "p": 7 }
};
const result = [];
for (const i in ASSARR) {
result.push({
i,
...ASSARR[i],
});
}
console.log(result);
.as-console-wrapper {
max-height: 100% !important;
}
...but if you want to modify the existing objects instead:
const result = [];
for (const i in ASSARR) {
const object = ASSARR[i];
object.i = i;
result.push(object);
}
let ASSARR = {
"abc": { "c": 5, "p": 3 },
"def": { "c": 1, "p": 10 },
"ghi": { "c": 15, "p": 7 }
};
const result = [];
for (const i in ASSARR) {
const object = ASSARR[i];
object.i = i;
result.push(object);
}
console.log(result);
.as-console-wrapper {
max-height: 100% !important;
}
Note: In the above, I'm assuming there are no inherited but enumerable properties you want left out. If there are, wrap the push calls in if (Object.hasOwn(object, i)) to skip inherited properties.
You could get the entries and map the objects with key.
const
object = { abc: { c: 5, p: 3 }, def: { c: 1, p: 10 }, ghi: { c: 15, p: 7 } },
array = Object
.entries(object)
.map(([i, o]) => ({ i, ...o }));
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can just use Object.entries, and Array.map..
eg..
let ASSARR = {
"abc": { "c": 5, "p": 3 },
"def": { "c": 1, "p": 10 },
"ghi": { "c": 15, "p": 7 }
};
let STARR = Object.entries(ASSARR).map(([k,v]) => {
return {i: k, ...v};
});
console.log(STARR);
Say I have an array of object::
const banana = [{"a":"ann","b":"bann","det":[{"c":"cat","d":"dog"},{"c":"conn","d":"donn"}]}, {"a":"auu","b":"buu","det":[{"c":"camel","d":"damel"},{"c":"coww","d":"doww"}]}]
I want to transform this array of object in this form::
const banana = [{"a":"ann","b":"bann","c":"cat","d":"dog"}, {"a":"ann","b":"bann","c":"conn","d":"donn"}, {"a":"auu","b":"buu","c":"camel","d":"damel"}, {"a":"auu","b":"buu","c":"coww","d":"doww"}]
As you can see array of object inside array of object have merged and duplicated.
I tried as:
const apple = []
for(let i = 0; i<banana.length;i++){
for(let j = 0;j<banana[i].det.length;j++{
apple.push(banana[i].det[j])
}
}
console.log(apple)
**OUTPUT: [{c: "cat", d: "dog"},{c: "conn", d: "donn"},{c: "camel", d: "damel"},{c: "coww", d: "doww"}]**
But I'm looking for the O/P as:
[{"a":"ann","b":"bann","c":"cat","d":"dog"}, {"a":"ann","b":"bann","c":"conn","d":"donn"},
{"a":"auu","b":"buu","c":"camel","d":"damel"}, {"a":"auu","b":"buu","c":"coww","d":"doww"}]
But I'm unable to form logic. I'm still trying but if i could get some guidance that would be really helpful.
**EDIT:**So I've come up with an idea using spread operator:
let enamel = {}
for(let i = 0; i<banana.length;i++){
for(let j = 0;j<banana[i].det.length;j++){
employee = {
...banana[j],
...banana[i].det[j]
};
}
}
It gives the output as:
console.log(enamel)
{a: "auu", b: "buu", det: Array(2), c: "coww", d: "doww"}
But I want to have all the objects in an array as previously stated.
You can use this logic, which copies over initial object, adds extra properties, drops the det array, and flatten the result
function extras(obj) {
// create a copy of the current context (initial obj)
// and add all properties from the extra object
obj = Object.assign({}, this, obj);
// but delete the `det` from the copy
delete obj.det;
// and return the object
return obj;
}
// per each array object ...
banana
.map(
// and per each det ...
obj => obj.det.map(extras, obj)
)
// flatten the final array of objects
.flat();
You just have to extract a and b from object in banana. I have used destructuring to extract it.
const banana = [{ "a": "ann", "b": "bann", "det": [{ "c": "cat", "d": "dog" }, { "c": "conn", "d": "donn" }] }, { "a": "auu", "b": "buu", "det": [{ "c": "camel", "d": "damel" }, { "c": "coww", "d": "doww" }] }]
const apple = []
for (let i = 0; i < banana.length; i++) {
for (let j = 0; j < banana[i].det.length; j++) {
const {a,b} = banana[i];
const {c,d} = banana[i].det[j];
apple.push({a,b,c,d});
}
}
console.log(apple)
You can do this:
const banana = [
{
"a": "ann",
"b": "bann",
"det": [{ "c": "cat", "d": "dog" }, { "c": "conn", "d": "donn" }]
},
{
"a": "auu",
"b": "buu",
"det": [
{ "c": "camel", "d": "damel" },
{ "c": "coww", "d": "doww" }
]
}
]
const result = [];
banana.forEach( b =>{
b.det.forEach(d =>{
result.push({
a: b.a,
b: b.b,
c: d.c,
d: d.d
});
});
});
console.log(result);
Try this
const banana = [{"a":"ann","b":"bann","det":[{"c":"cat","d":"dog"},{"c":"conn","d":"donn"}]}, {"a":"auu","b":"buu","det":[{"c":"camel","d":"damel"},{"c":"coww","d":"doww"}]}]
const output = []
for (const { a, b, det } of banana) {
for (const animal of det) {
output.push({a, b, ...animal })
}
}
console.log(output)
I think you want to do it like this in case you want to avoid manually take a and b and other property except 'det' properties
function getResult(banana) {
const answer = [];
banana.forEach(element => {
const arrayData = element['det'];
delete element['det'];
// remove the 'del' property temporarily
arrayData.forEach(subElement => {
answer.push({
...element, // basically spread operator to make copy of all properties each time
...subElement
});
});
// restore the 'del' proprty
element['det'] = arrayData;
});
console.log("the result is : ", answer);
return answer;
}
Just to clarify this is what I mean by "inverted map":
const foo =
{ "a": 10
, "b": 20
};
const foo_inverted =
{ "10": "a"
, "20": "b"
};
I have this object representing a file:
const file =
{ id: 100
, tags: [20, 30]
};
Given a list of files I need to build a map that allows me to find all files with a given tag.
From this:
const files =
[ { id: 100
, tags: [20, 30]
}
, { id: 200
, tags: [20, 40]
}
];
To that:
{ "20": { "100": 1, "200": 1 }
, "30": { "100": 1 }
, "40": { "200": 1 }
}
I ended up with this code which does the job:
const tag_file = (tag_id, file_id) => ({[tag_id]: {[file_id]: 1}});
const mergeDeepAll = reduce(mergeDeepRight, {});
const tag_map = compose(mergeDeepAll, lift(tag_file));
const tags_map = compose(mergeDeepAll, map(({id, tags}) => tag_map(tags, [id])));
tags_map(files);
//=> { "20": { "100": 1, "200": 1 }
//=> , "30": { "100": 1 }
//=> , "40": { "200": 1 }
//=> }
Question: am I missing any functional programming concepts that would have allowed me to express this better?
Create an a function that generates pairs [tag, id] for each object, using a Array.map() (idByTags). Using R.chain convert all objects to such pairs and flatten them. Group by the tag (R.head), and then map the object (R.mapObjIndexed) and count by the id (R.last):
const { pipe, chain, groupBy, head, mapObjIndexed, countBy, last } = R
const idByTags = ({ id, tags }) => tags.map(tag => [tag, id])
const fn = pipe(
chain(idByTags),
groupBy(head),
mapObjIndexed(countBy(last))
)
const files = [{"id":100,"tags":[20,30]},{"id":200,"tags":[20,40]}]
const result = fn(files)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
not sure why you would need ramda, can do it with reduce and forEach
const files = [{
id: 100,
tags: [20, 30]
}, {
id: 200,
tags: [20, 40]
}];
// loop over the array to make an object
const result = files.reduce((obj, file) => {
// loop over the tags
file.tags.forEach(
tag =>
obj[tag] ? // have we seen the tag?
obj[tag].push(file.id) : // yes
obj[tag] = [file.id] // no
)
return obj // return the object for reduce
}, {})
console.log(result)
AFTER YOUR EDIT
const files = [{
id: 100,
tags: [20, 30]
}, {
id: 200,
tags: [20, 40]
}];
// loop over the array to make an object
const result = files.reduce((obj, file) => {
// loop over the tags
file.tags.forEach(
tag => {
obj[tag] = obj[tag] || {} // have we seen the tag?
obj[tag][file.id] = 1 //
})
return obj // return the object for reduce
}, {})
console.log(result)
I have an Array of Objects which should all have the same keys, but some of the keys are missing. I would like to fill in the missing keys with a generic value.
I am looking for a simple way to do that (natively or via a library), the code below I use now works, bit looks to my untrained eyes quite heavy and I am sure I reinvented the tedious way to do something while there is a simple one.
var arr = [{
"a": 1,
"b": 2,
"c": 3
},
{
"a": 10,
"c": 30
},
{
"b": 200,
"c": 300
},
]
// get the list of all keys
var allkeys = []
arr.forEach((objInArr) => {
allkeys = allkeys.concat(Object.keys(objInArr))
})
// check all arr entries for missing keys
arr.forEach((objInArr, i) => {
allkeys.forEach((key) => {
if (objInArr[key] === undefined) {
// the generic value, in this case 0
arr[i][key] = 0
}
})
})
console.log(arr)
Here is a version using property spread in object literals, although this will have very limited browser support:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator
var arr = [{
"a": 1,
"b": 2,
"c": 3
},
{
"a": 10,
"c": 30
},
{
"b": 200,
"c": 300
},
]
// Create an object with all the keys in it
// This will return one object containing all keys the items
let obj = arr.reduce((res, item) => ({...res, ...item}));
// Get those keys as an array
let keys = Object.keys(obj);
// Create an object with all keys set to the default value (0)
let def = keys.reduce((result, key) => {
result[key] = 0
return result;
}, {});
// Use object destrucuring to replace all default values with the ones we have
let result = arr.map((item) => ({...def, ...item}));
// Log result
console.log(result);
Your version is fine, although I would probably avoid all those array concat calls by just building up an object (or Set) with the keys. It's also a bit less clunky with for-of:
var arr = [{
"a": 1,
"b": 2,
"c": 3
},
{
"a": 10,
"c": 30
},
{
"b": 200,
"c": 300
},
];
// Get all the keys
const keyObj = Object.create(null);
for (const entry of arr) {
for (const key of Object.keys(entry)) {
keyObj[key] = true;
}
}
const allkeys = Object.keys(keyObj);
// Check all arr entries for missing keys
for (const entry of arr) {
for (const key of allkeys) {
if (entry[key] === undefined) { // ***I'd change this
entry[key] = 0;
}
}
}
console.log(arr);
.as-console-wrapper {
max-height: 100% !important;
}
Re *** I'd change this: Note that there's a difference between a property that exists and has the value undefined and a property that doesn't exist at all. Your code is treating them as the same thing. Of course, if you know they won't have the value undefined (for instance, because of the API you're getting them from)...
You can use Object.assign to merge each element with an object holding default key-values:
var arr = [{
"a": 1,
"b": 2,
"c": 3
},
{
"a": 10,
"c": 30
},
{
"b": 200,
"c": 300
},
];
var defaultObj = arr.reduce((m, o) => (Object.keys(o).forEach(key => m[key] = 0), m), {});
arr = arr.map(e => Object.assign({}, defaultObj, e));
console.log(arr);
I am trying do combine the nested objects inside items with the same key.
Find 'top level' values that are duplicated,
Combine the duplicated 'top level' items into one object (including their children.
There should be no duplicate values inside the 'type' arrays
I tried it here https://jsfiddle.net/Lpq6huvw/410/
Input data:
[{
"a": "Mon",
"type": [{
"b": 1
}, {
"b": 3
}]
}, {
"a": "Mon",
"type": [{
"b": 2
}]
}, {
"a": "Tue",
"type": [{
"b": 40
}]
}, {
"a": "Tue",
"type": [{
"b": 50
}]
}, {
"a": "Wed",
"type": [{
"b": 30
}]
}]
Into this array:
[{
"a": "Mon",
"type": [{
"b": 1
}, {
"b": 3
},
{
"b": 2
}]
},
{
"a": "Tue",
"type": [{
"b": 40
},
{
"b": 50
}]
}, {
"a": "Wed",
"type": [{
"b": 30
}]
}]
I attempted this below, which maps all the duplicated items as ONE object. However, I want it to map each under its' 'top level' predecessor.
const z = _.uniqBy(_.filter(data.map(e=>e.a), v => _.filter(data.map(e=>e.a), v1 => v1 === v).length > 1))
const dupes = data.filter(itm => z.includes(itm.a))
const flat = _.flatMap(dupes, item =>
_(item.type)
.map(v => ({b: v.b}))
.value()
)
I personally find Javascript's built in functions work nice, and seem easier to follow than some of lodash functions.
eg.
var data = [{"a":"Mon","type":[{"b":1},{"b":3}]},{"a":"Mon","type":[{"b":2},{"b":3}]},{"a":"Tue","type":[{"b":40}]},{"a":"Tue","type":[{"b":50}]},{"a":"Wed","type":[{"b":30}]}];
var result = data.reduce((acc, val) => {
var found = acc.find((findval) => val.a === findval.a);
if (!found) acc.push(val)
else found.type = found.type.concat(
val.type.filter((f) => !found.type.find((findval) => f.b === findval.b)));
return acc;
}, []);
console.log(result);
Here's a answer w/o lodash:
function combine (input) {
const hash = input.reduce((result, current) => {
if (result[current['a']]) {
result[current['a']] = result[current['a']].concat(current['type'])
} else {
result[current['a']] = current['type']
}
return result
}, {})
return Object.keys(hash).map(key => {
return {
a: key,
type: hash[key]
}
})
}
ES6: you can iterate with Array#reduce, collect the items into a Map, and then convert back to an array with the spread syntax and Map#values:
const data = [{"a":"Mon","type":[{"b":1},{"b":3}]},{"a":"Mon","type":[{"b":2}]},{"a":"Tue","type":[{"b":40}]},{"a":"Tue","type":[{"b":50}]},{"a":"Wed","type":[{"b":30}]}];
const result = [...data.reduce((m, { a, type }) => {
const item = m.get(a) || { a, type: [] }; // use a Set to maintain uniqueness
item.type.push(...type);
return m.set(a, item);
}, new Map).values()]
.map(({ a, type }) => ({ // make types unique again
a,
type: [...type.reduce((m, o) => m.has(o.b) ? m : m.set(o.b, o), new Map).values()]
}));
console.log(result);
Lodash: Use _.groupBy() to get all objects with the same a property in one group. Map the groups, and merge each group using _.mergeWith(), and concat all type arrays.
Make another pass with map to make all items in type arrays unique.
const data = [{"a":"Mon","type":[{"b":1},{"b":3}]},{"a":"Mon","type":[{"b":2}]},{"a":"Tue","type":[{"b":40}]},{"a":"Tue","type":[{"b":50}]},{"a":"Wed","type":[{"b":30}]}];
const result = _(data)
.groupBy('a')
.map((group) => _.mergeWith({}, ...group, ((objValue, srcValue, key) =>
key === 'type' ? (objValue || []).concat(srcValue) : undefined
)))
.map((obj) => Object.assign(obj, { type: _.uniq(obj.type) }))
.value();
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>