Concatenate two json object - javascript

is there any way to Aggregation two json object together and assign the result to the first object
i have tow Json object and i want to Aggregation them into one object
<script type="text/javascript">
$(document).ready(function () {
var FirstObject= [
{ "label": "salem", "actor": "" },
{ "label": "Aragorn", "actor": "Viggo Mortensen" },
{ "label": "Arwen", "actor": "Liv Tyler" },
{ "label": "Bilbo Baggins", "actor": "Ian Holm" },
{ "label": "Boromir", "actor": "Sean Bean" },
{ "label": "Frodo Baggins", "actor": "Elijah Wood" },
{ "label": "Gandalf", "actor": "Ian McKellen" },
{ "label": "Gimli", "actor": "John Rhys-Davies" },
{ "label": "Gollum", "actor": "Andy Serkis" },
{ "label": "Legolas", "actor": "Orlando Bloom" },
{ "label": "Meriadoc Merry Brandybuck", "actor": "Dominic Monaghan" },
{ "label": "Peregrin Pippin Took", "actor": "Billy Boyd" },
{ "label": "Samwise Gamgee", "actor": "Sean Astin" }
];
$("#search").keyup(function () {
$.ajax({
type: "POST",
dataType: "json",
contentType: "application/json",
url: "WebService1.asmx/GetDocumentNames",
data: '{ }',
success: function (data) {
**FirstObject =data.d**
},
error: function () { aler("Salem Error"); }
}
);
});
});
</script>
so on the statement FirstObject =data.d i want the Aggregation

Use jQuery extend like this:
// first argument tells jQuery's extend to deep copy the properties
$.extend( true, FirstObject, data.d );
quote from jQuery documentation:
Merge two objects recursively, modifying the first.

Your variable FirstObject is actually an array, and assuming what you receive is not an Array but a JSON Object to append or aggregate it to the array, you just need to call the Array's method push.
FirstObject.push(data.d);
If what you're receiving is a JSON Array rather that a JSON Object, you could use the concat method in the array.
FirstObject.concat(data.d);

Take a look in below example:
var jsonArray1 = [{'name': "doug", 'id':5}, {'name': "dofug", 'id':23}];
var jsonArray2 = [{'name': "goud", 'id':1}, {'name': "doaaug", 'id':52}];
jsonArray1 = jsonArray1.concat(jsonArray2);

If you want to merge the JSON objects and maintain the common keys.
var object1 = {
'key1': 10,
'key2': 'Hello ',
'key3': [
'I',
'am'
],
'key4': {
'key5': 'Hi',
'key6': 11
}
};
var object2 = {
'key1': 11,
'key2': 'World',
'key3': [
'an',
'Array'
],
'key4': {
'key5': ' there',
'key6': '#SomeRandomString'
}
};
function isArray(value) {
return Object.prototype.toString.call(value) === '[object Array]';
}
function isObject(value) {
return Object.prototype.toString.call(value) === '[object Object]';
}
var result = {};
for (var key in object1) {
if (object1.hasOwnProperty(key) && object2.hasOwnProperty(key)) {
//check if the value is of type array (works for key 3)
if (isArray(object1[key]) && isArray(object2[key])) {
result[key] = [];
for (var i in object1[key]) {
result[key].push(object1[key][i])
}
for (var i in object2[key]) {
result[key].push(object2[key][i])
}
}
//check if the value is of type object (works for key 4)
else if (isObject(object1[key])) {
result[key] = {};
for (var key_inner in object1[key]) {
if (object1[key].hasOwnProperty(key_inner) && object2[key].hasOwnProperty(key_inner)) {
result[key][key_inner] = object1[key][key_inner] + object2[key][key_inner];
}
}
} else {
result[key] = object1[key] + object2[key];
}
}
}
//console.log(JSON.stringify(result));
console.log(result);

You can use Object.assign() method to concatenate a json object. The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object.[1]
var o1 = { a: 1 }, o2 = { b: 2 }, o3 = { c: 3 };
var obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }

Related

Find a full object path to a given value with JavaScript

I have an array of objects with items (only have name property) and groups (with a children property, they may contain items or other groups) and I need to get a full path to needle value, so in this case it'd be myObj[2]["children"][0]["children"][1]["children"][0], plus I'm limited to quite old JS version ECMA 262 (I'm using it inside Photoshop)
var myObj = [
{
"name": "group1",
"children": [
{
"name": "group2",
"children": [
{
"name": "item0"
}]
}]
},
{
"name": "item1"
},
{
"name": "needleGroup",
"children": [
{
"name": "needleNestedGroup",
"children": [
{
"name": "item3"
},
{
"name": "needleNestedDeeperGroup",
"children": [
{
"name": "needle"
}]
}]
}]
}];
My first idea was to transform object to array or arrays so it'd be easier to process, so my object became
[
[
[
"item0"
]
],
"item1",
[
[
"item3",
[
"needle"
]
]
]
];
But.. that's it. I can't figure out hot to track down only the indexes I need. Could you please point out a correct direction.
Use a recursive function to look for the item you want. Once the function find it, it will return an array. Each step back of the recursion will unshift the object key of this step:
function find(obj, item) {
for(var key in obj) { // for each key in obj (obj is either an object or an array)
if(obj[key] && typeof obj[key] === "object") { // if the current property (value of obj[key]) is also an object/array
var result = find(obj[key], item); // try finding item in that object
if(result) { // if we find it
result.unshift(key); // we shift the current key to the path array (result will be an array of keys)
return result; // and we return it to our caller
}
} else if(obj[key] === item) { // otherwise (if obj[key] is not an object or array) we check if it is the item we're looking for
return [key]; // if it is then we return the path array (this is where the path array get constructed)
}
}
}
The output of this function will be an array of keys leading to item. You can easily transform it to the format in the question:
function findFormatted(obj, item) {
var path = find(obj, item); // first let's get the path array to item (if it exists)
if(path == null) { // if it doesn't exist
return ""; // return something to signal its inexistance
}
return 'myObj["' + path.join('"]["') + '"]'; // otherwise format the path array into a string and return it
}
Example:
function find(obj, item) {
for(var key in obj) {
if(obj[key] && typeof obj[key] === "object") {
var result = find(obj[key], item);
if(result) {
result.unshift(key);
return result;
}
} else if(obj[key] === item) {
return [key];
}
}
}
function findFormatted(obj, item) {
var path = find(obj, item);
if(path == null) {
return "";
}
return 'myObj["' + path.join('"]["') + '"]';
}
var myObj = [{"name":"group1","children":[{"name":"group2","children":[{"name":"item0"}]}]},{"name":"item1"},{"name":"needleGroup","children":[{"name":"needleNestedGroup","children":[{"name":"item3"},{"name":"needleNestedDeeperGroup","children":[{"name":"needle"}]}]}]}];
console.log("find(myObj, \"needle\"): " + JSON.stringify(find(myObj, "needle")));
console.log("findFormatted(myObj, \"needle\"): " + findFormatted(myObj, "needle"));
Note: The indexes for the arrays are also formatted as strings, but that won't be a problem as someArray["2"] is equivalent to someArray[2].
I've created something you might use. The code below returns an Array of paths to keys, values, objects you are looking for.
See snippet and example to see what you can do.
To make it work you have to pass key and/or value you want to find in element and element which is an Array or Object.
It's written in newer JS standard but it shouldn't be a problem to compile it to older standard.
function findKeyValuePairsPath(keyToFind, valueToFind, element) {
if ((keyToFind === undefined || keyToFind === null) &&
(valueToFind === undefined || valueToFind === null)) {
console.error('You have to pass key and/or value to find in element!');
return [];
}
const parsedElement = JSON.parse(JSON.stringify(element));
const paths = [];
if (this.isObject(parsedElement) || this.isArray(parsedElement)) {
checkObjOrArr(parsedElement, keyToFind, valueToFind, 'baseElement', paths);
} else {
console.error('Element must be an Object or Array type!', parsedElement);
}
console.warn('Main element', parsedElement);
return paths;
}
function isObject(elem) {
return elem && typeof elem === 'object' && elem.constructor === Object;
}
function isArray(elem) {
return Array.isArray(elem);
}
function checkObj(obj, keyToFind, valueToFind, path, paths) {
Object.entries(obj).forEach(([key, value]) => {
if (!keyToFind && valueToFind === value) {
// we are looking for value only
paths.push(`${path}.${key}`);
} else if (!valueToFind && keyToFind === key) {
// we are looking for key only
paths.push(path);
} else if (key === keyToFind && value === valueToFind) {
// we ale looking for key: value pair
paths.push(path);
}
checkObjOrArr(value, keyToFind, valueToFind, `${path}.${key}`, paths);
});
}
function checkArr(array, keyToFind, valueToFind, path, paths) {
array.forEach((elem, i) => {
if (!keyToFind && valueToFind === elem) {
// we are looking for value only
paths.push(`${path}[${i}]`);
}
checkObjOrArr(elem, keyToFind, valueToFind, `${path}[${i}]`, paths);
});
}
function checkObjOrArr(elem, keyToFind, valueToFind, path, paths) {
if (this.isObject(elem)) {
checkObj(elem, keyToFind, valueToFind, path, paths);
} else if (this.isArray(elem)) {
checkArr(elem, keyToFind, valueToFind, path, paths);
}
}
const example = [
{
exampleArr: ['lol', 'test', 'rotfl', 'yolo'],
key: 'lol',
},
{
exampleArr: [],
key: 'lol',
},
{
anotherKey: {
nestedKey: {
anotherArr: ['yolo'],
},
anotherNestedKey: 'yolo',
},
emptyKey: null,
key: 'yolo',
},
];
console.log(findKeyValuePairsPath('key', 'lol', example)); // ["baseElement[0]", "baseElement[1]"]
console.log(findKeyValuePairsPath(null, 'yolo', example)); // ["baseElement[0].exampleArr[3]", "baseElement[2].anotherKey.nestedKey.anotherArr[0]", "baseElement[2].anotherKey.anotherNestedKey", "baseElement[2].key"]
console.log(findKeyValuePairsPath('anotherNestedKey', null, example)); //["baseElement[2].anotherKey"]
I came accross this issue and took the chance to create find-object-paths, which solves this problem: Finding paths in an object by either keys, values or key/value combinations.
NPM: find-object-paths
Github: getPaths
Example object:
{
"company": {
"name": "ACME INC",
"address": "1st Street, Toontown, TT",
"founded": "December 31st 1969",
"hasStocks": true,
"numberOfEmployees": 2,
"numberOfActors": 3
},
"employees": [
{
"employeeNumber": 1,
"name": "Hugo Boss",
"age": 65,
"isCEO": true
},
{
"employeeNumber": 2,
"name": "Herbert Assistant",
"age": 32,
"isCEO": false
}
],
"actors": [
{
"actorId": 1,
"name": "Bugs Bunny",
"retired": false,
"playedIn": [
{
"movie": "Who Framed Roger Rabbit",
"started": 1988
},
{
"movie": "Space Jam",
"started": 1996
},
{
"movie": "Looney Tunes: Back in Action",
"started": 2003
}
]
},
{
"actorId": 2,
"name": "Pepé le Pew",
"retired": true,
"playedIn": [
{
"movie": "For Scent-imental Reasons",
"started": 1949
}
]
},
{
"actorId": 3,
"name": "Marvin the Martian",
"retired": true,
"playedIn": [
{
"movie": "Marvin the Martian in the Third Dimension",
"started": 1996
},
{
"movie": "Duck Dodgers and the Return of the 24½th Century",
"started": 1980
},
{
"movie": "Hare-Way to the Stars",
"started": 1958
}
]
},
{
"actorId": 4,
"name": "Yosemite Sam",
"retired": false,
"playedIn": [
{
"movie": "Who Framed Roger Rabbit",
"started": 1988
},
{
"movie": "Space Jam",
"started": 1996
},
{
"movie": "Looney Tunes: Back in Action",
"started": 2003
}
]
}
],
"distributionChannels": [
"Celluloyd",
[
"VHS",
"Betamax",
"DVD",
"Blueray"
],
[
"channel",
12,
true
]
]
}
So, the basic usage could be:
import { findObjectPaths } from 'findObjectPaths';
class TestMe {
static async main() {
let acmeInc = {};
rawFileContent = fs.readFileSync(p.resolve(__dirname, 'acmeInc.json'), 'utf-8');
acmeInc = JSON.parse(rawFileContent);
let path = findObjectPaths(acmeInc, {key: 'founded'});
// company.founded
path = findObjectPaths(acmeInc, {value: 'December 31st 1969'});
// company.founded
const allPaths: string[] = findObjectPaths(acmeInc, {key: 'actorId'}) as string[];
/* [ 'actors[0].actorId',
'actors[1].actorId',
'actors[2].actorId',
'actors[3].actorId' ]
*/
const ceoPath = findObjectPaths(acmeInc, {key: 'isCEO', value: true});
// employees[0].isCEO
}
}
TestMe.main();
See the full documentation here: https://github.com/maugenst/getPaths#readme
BR
Assuming that you have a nested and repetitive pattern of objects in your data-set, the following solution would do the trick for you.
const nodePath = { value: [] };
function findTreeNodeAndPath(
targetNodeKey,
targetNodeValue,
nodeChildrenKey,
tree
) {
if (tree[targetNodeKey] === targetNodeValue) {
nodePath.value.push(tree);
return;
}
if (tree[nodeChildrenKey].length > 0) {
tree[nodeChildrenKey].forEach(children => {
if (nodePath.value.length < 1) {
findTreeNodeAndPath(
targetNodeKey,
targetNodeValue,
nodeChildrenKey,
children
);
}
});
} else if (tree[nodeChildrenKey].length === 0) {
return;
}
if (nodePath.value.length > 0) {
nodePath.value.push(tree);
}
}
const exampleData = {
name: "Root",
children: [
{
name: "A2",
children: [
{
name: "AC1",
children: [
{
name: "ACE1",
children: []
}
]
},
{
name: "AC2",
children: [
{
name: "ACF1",
children: []
},
{
name: "ACF2",
children: [
{
name: "ACFG1",
children: []
}
]
},
{
name: "ACF3",
children: [
{
name: "ACFH1",
children: []
}
]
}
]
}
]
}
]
};
findTreeNodeAndPath("name", "ACFG1", "children", exampleData);
console.log(nodePath.value)
The recursive part of the code will iterate on the children of the current node. The existing strategy here is depending on the nodePath.value having at least one element, which indicates that it found the targetted node. Later on, it'll skip the remaining nodes and would break out of recursion.
The nodePath.value variable will give you the node-to-root path.

Single variable to stand in for multiple key names - javascript

I have an array, something like this:
array =
[
{
"type": "apple",
"color": "red",
"id": "redApple"
},
{
"type": "grape",
"color": "green",
"id": "greenGrape",
"options": [
{
"bunchName": "bunch1",
"size": "8"
},
{
"bunchName": "bunch2",
"size": "10"
},
{
"bunchName": "bunch3",
"size": "5"
}
]
}
]
I have a function that searches for values in the array.
function findValue (index, key) {
return array[index][key];
}
var value = findValue(0, "id");
// returns redApple
Is there a way I could pass a single argument to the function if I wanted to find something deeper in the array? For example, if I wanted to find "bunchName" could I pass it something like 1, "options[0].bunchName" and get back "bunch1"?
I want a function that can handle multiple keys. In my real project sometimes I'm looking for something on the first level, sometimes I'm looking on the second level, sometimes the third level, etc.
jQuery can be used if for some reason that would help.
You could take the string, replace the brackets, split the string and reduce the path for the result. The function uses a default object for missing or not given properties.
function getValue(object, path) {
return path
.replace(/\[/g, '.')
.replace(/\]/g, '')
.split('.')
.reduce(function (o, k) { return (o || {})[k]; }, object);
}
function findValue(index, path) {
return getValue(array[index], path);
}
var array = [{ type: "apple", color: "red", id: "redApple" }, { type: "grape", color: "green", id: "greenGrape", options: [{ bunchName: "bunch1", size: "8" }, { bunchName: "bunch2", size: "10" }, { bunchName: "bunch3", size: "5" }] }];
console.log(findValue(1, "options[0].bunchName"));
From what I understand, output of findValue(object, "bunchName"); should be "bunch3", where object is array in OP's example.
var object =
[
{
"type": "apple",
"color": "red",
"id": "redApple"
},
{
"type": "grape",
"color": "green",
"id": "greenGrape",
"options": [
{
"bunchName": "bunch1",
"size": "8"
},
{
"bunchName": "bunch2",
"size": "10"
},
{
"bunchName": "bunch3",
"size": "5"
}
]
}
]
var findValue = (object, key) => {
var resultValue;
var rec = (currentObj) => {
if(currentObj && typeof currentObj === "object"){
for(let curKey in currentObj){
if (curKey === key){
resultValue = currentObj[curKey];
}else{
rec(currentObj[curKey]);
}
}
}
}
rec(object);
return resultValue;
}
console.log(findValue(object, "bunchName"));
You could add a function that takes an object and a key and returns object[key] and then split your key string into a list of individual keys by the dot. Then you could traverse the list of keys and use the function to get the value for each level in your object:
Totally untested code I just whipped up:
function valueByKey(obj, key) {
if (obj) {
return obj[key];
}
}
function findValue(index, key) {
const keys = key.split('.');
let value = array[index];
for (let i = 0; i < keys.length; i++) {
value = valueByKey(value, keys[i]);
}
return value;
}
Non-recurrent solution:
var array = [
{
'a': {
'b': 1
}
}
];
function findValue(index, key) {
var keys = key.split('.');
var tmp = array[index];
for (var i = 0; i < keys.length; i++) {
if (!tmp.hasOwnProperty(keys[i]) || typeof tmp !== 'object') {
// throw an exception, or return default value – property not found.
}
tmp = tmp[keys[i]];
}
return tmp;
}
findValue(0, 'a.b');

Push object keys and its values to array

I have an object like this:
{
"id": 23,
"name": "Jacob",
"link": {
"rel": "self",
"link": "www.abc.com"
},
"company":{
"data":{
"id": 1,
"ref": 324
}
}
I want to store each key with its value to an array in javascript or typescript like this
[["id":23], ["name":"Jacob"], ["link":{......, ......}]] and so on
I am doing this so that I can append an ID for each.
My best guess I would loop through the array and append an ID/a flag for each element, which I don't know how to do as well.... how to address this issue ? thanks
var arr = [];
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
var innerObj = {};
innerObj[prop] = obj[prop];
arr.push(innerObj)
}
}
console.log(arr);
here is demo https://plnkr.co/edit/9PxisCVrhxlurHJYyeIB?p=preview
p.forEach( function (country) {
country.forEach( function (entry) {
entry.push( {"value" : 'Greece', "synonyms" : 'GR'});
});
});
you can try to use experimental Object.entries:
let obj = {
"id": 23,
"name": "Jacob",
"link": {
"rel": "self",
"link": "www.abc.com"
},
"company":{
"data":{
"id": 1,
"ref": 324
}
}};
console.log(Object.entries(obj).map(item => ({[item[0]]:item[1]})));
for unsupported browsers you can use polyfill: https://github.com/es-shims/Object.entries
You could use an iterative/recursive approach with the object and their nested parts. It works for any depths.
function getKeyValue(object) {
return Object.keys(object).reduce(function (result, key) {
return result.concat(
object[key] && typeof object[key] === 'object' ?
getKeyValue(object[key]) :
[[key, object[key]]]
);
}, []);
}
var data = { id: 23, name: "Jacob", link: { rel: "self", link: "www.abc.com" }, company: { data: { id: 1, ref: 324 } } };
console.log(getKeyValue(data));
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use the Object.keys method to get an array of the keys, then use the Array#map method to return a new array containing individual objects for each property.
This ES6 one-liner should do it:
const splitObject = o => Object.keys(o).map(e => ({ [e]: o[e] }));
Or in ES5:
function splitObject(o) {
return Object.keys(o).map(function(e) {
return Object.defineProperty({}, e, {
value: o[e],
enumerable: true
});
});
}
var res = [];
_.transform( {
"id": 23,
"name": "Jacob",
"link": {
"rel": "self",
"link": "www.abc.com"
},
"company": {
"data": {
"id": 1,
"ref": 324
}
}
}, function(result, value, key) {
res.push(key +':'+value);
}, {});
You can use underscore
Supported in all major browser, including IE11
Object.entries() gives you exactly this.
const obj = {
id: 23,
name: 'Jacob',
link: {
rel: 'self',
link: 'www.abc.com'
},
company: {
data: {
id: 1,
ref: 324
}
}
};
Object.entries(obj);
// output:
[
[
"id",
23
],
[
"name",
"Jacob"
],
[
"link",
{
"rel": "self",
"link": "www.abc.com"
}
],
[
"company",
{
"data": {
"id": 1,
"ref": 324
}
}
]
]
var obj=[{"Name":ABC,"Count":123},{"Name":XYZ,"Count":456}];
var arr = [];
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
var innerObj = {};
innerObj[0] = obj[prop];
arr.push(innerObj[0]);
}
}
/* Here above exmple innerobj index set to 0 then we will get same data into arr if u not menstion then arr will conatins arr[0] our result.
then we need to call first record obj arr[0][0] like this*/
const foo = { "bar": "foobar", "foo": "foobar" }
Object.entries(foo)
should result in:
[["bar", "foobar"], ["foo", "foobar"]]
maybe there's a function to pass to convert all commas to colons
Here's the documentation
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries

combining my objects and getting expected results

I am trying to merge my objects and get a result like below
{
"sports": {
"basketball": "kobe",
"swimming": {
},
"football": "ronaldo",
"running": "",
"highJump": ""
},
"calendar": ["21", "25", "30"]
}
Somewhere I am doing wrong in my logic can you help me out
but if I alternate my sportA and sportsB value I am getting expected
results...not sure what problem in my current scenario
providing my code below.
fiddle
https://jsfiddle.net/tjLk0frq/3/
var sportsA ={
"sports": {
"basketball": "kobe",
"football": "ronaldo"
}
};
var sportsB ={
"sports": {
"basketball": "",
"swimming": {
},
"football": "",
"running": "",
"highJump": ""
},
"calendar": ["21", "25", "30"]
};
function merge(sportsA, sportsB) {
for( var p in sportsB )
if( sportsA.hasOwnProperty(p) )
sportsA[p] = typeof sportsB[p] === 'object' ? merge(sportsA[p], sportsB[p]) : sportsB[p];
return sportsA;
}
merge(sportsA, sportsB );
console.log("unexpected result" + sportsA );
console.log( sportsA );
//expected
/*
{
"sports": {
"basketball": "kobe",
"swimming": {
},
"football": "ronaldo",
"running": "",
"highJump": ""
},
"calendar": ["21", "25", "30"]
}
*/
You can use jQuery's extend method, with deep merge enabled, to do this:
var output = $.extend(true, sportsB, sportsA)
outputs:
{
"sports": {
"basketball": "kobe",
"swimming": {},
"football": "ronaldo",
"running": "",
"highJump": ""
},
"calendar": ["21", "25", "30"]
}
You have mistake when you check sportsA.hasOwnProperty(p) in your case you only update properties that are in sportsA, but not add new from sportsB.
Also if sportsB[p] has falsy value you don't want to update it for that I've used (sportsB[p] || sportsA[p]).
Check this code.
var sportsA ={
"sports": {
"basketball": "kobe",
"football": "ronaldo"
}
};
var sportsB ={
"sports": {
"basketball": "",
"swimming": {
},
"football": "",
"running": "",
"highJump": ""
},
"calendar": ["21", "25", "30"]
};
function merge(sportsA, sportsB) {
for( var p in sportsB )
if( sportsA.hasOwnProperty(p) ) {
sportsA[p] = typeof sportsB[p] === 'object' ? merge(sportsA[p], sportsB[p]) : (sportsB[p] || sportsA[p]);
} else {
sportsA[p] = sportsB[p];
}
return sportsA;
}
merge(sportsA, sportsB );
console.log("unexpected result" + sportsA );
console.log( sportsA );
Here you go (pure JS):
function merge(obj1, obj2) {
var result = {};
for (var prop in obj1) {
if (typeof obj1[prop] === "object" && typeof obj2[prop] === "object")
result[prop] = merge(obj1[prop], obj2[prop]);
else
result[prop] = obj1[prop];
}
for (var prop in obj2) {
result[prop] = (result[prop]? result[prop]: obj2[prop]);
}
return result;
}
console.log(merge(sportsA, sportsB));
This returns a new object, rather than modify an existing one, however.
In the first for..in loop, we check if we need to recurse first, otherwise set the property of result.
In the second for..in loop, we check if the property was already defined or if it's empty, and set the property accordingly.
Output:
{
"sports": {
"basketball": "kobe",
"football": "ronaldo",
"swimming": {},
"running": "",
"highJump": ""
},
"calendar": ["21", "25", "30"]
}
JSFiddle demo
The logic is breaking because when you only loop the property keys in one of the objects, you won't see the property keys that only exist in the other object.
You can get the root level keys of an object using Object.keys() which returns an array of the property names. Then you can merge the 2 sets of keys at same level and know all the final output properties needed
Then iterate those to get final results

Grouping objects by multiple columns with Lodash or Underscore

I have following object records:
{
"notes":[
{
"id":1,
"description":"hey",
"userId":2,
"replyToId":null,
"postId":2,
"parentId":null
},
{
"id":5,
"description":"hey test",
"userId":3,
"replyToId":null,
"postId":2,
"parentId":null
},
{
"id":2,
"description":"how are you",
"userId":null,
"replyToId":2,
"postId":2,
"parentId":null,
"user":null
}
]
}
I want to output it as:
2
object with id 1
object with id 2 (because replyToId value is same as userId
3
object with id 5
So basically I want to consider UserId and replyToId value under the same group.
I have build my own mixin under lodash, wrapping groupBy method as:
mixin({
splitGroupBy: function(list, groupByIter){
if (_.isArray(groupByIter)) {
function groupBy(obj) {
return _.forEach(groupByIter, function (key){
if ( !!obj[key] ) return obj[key]
});
}
} else {
var groupBy = groupByIter;
}
debugger;
var groups = _.groupBy(list, groupBy);
return groups;
}
});
Call looks like this:
_.splitGroupBy(data.notes,['userId', 'replyToId']);
The output is coming without group. Even when I have tried with _.map instead _.forEach the split is not happening correctly.
A solution using underscore:
var props = ['userId', 'replyToId'];
var notNull = _.negate(_.isNull);
var groups = _.groupBy(record.notes, function(note){
return _.find(_.pick(note, props), notNull);
});
This can probably done much prettier, but it should work:
lodash.mixin({
splitGroupBy: function(list, groupByIter) {
var _ = this, groupBy;
if (lodash.isArray(groupByIter)) {
groupBy = function(obj) {
return _(obj) .pick(groupByIter)
.values()
.without(null, undefined)
.first();
};
} else {
groupBy = groupByIter;
}
var groups = _.groupBy(list, groupBy);
return groups;
}
});
You can use stringify object as key.
_.groupBy(notes, ({ userId, replyToId }) => JSON.stringify({ userId, replyToId }));
output:
{
"{\"userId\":2,\"replyToId\":null}": [
{
"id": 1,
"description": "hey",
"userId": 2,
"replyToId": null,
"postId": 2,
"parentId": null
}
],
"{\"userId\":3,\"replyToId\":null}": [
{
"id": 5,
"description": "hey test",
"userId": 3,
"replyToId": null,
"postId": 2,
"parentId": null
}
],
"{\"userId\":null,\"replyToId\":2}": [
{
"id": 2,
"description": "how are you",
"userId": null,
"replyToId": 2,
"postId": 2,
"parentId": null,
"user": null
}
]
}
You could map your list of attributes to their respective values and pick the first non falsy value as your group key:
_.mixin({
splitGroupBy: function(list, groupByIter){
if (!_.isArray(groupByIter))
return _.groupBy(list, groupByIter);
return _.groupBy(list, function(o) {
var values = _.map(groupByIter, function(k) {
return o[k];
});
return _.find(values);
});
}
});
var data = {
"notes":[
{
"id":1,
"userId":2,
"replyToId":null
},
{
"id":5,
"userId":3,
"replyToId":null
},
{
"id":2,
"userId":null,
"replyToId":2
}
]
};
_.mixin({
splitGroupBy: function(list, groupByIter){
if (!_.isArray(groupByIter))
return _.groupBy(list, groupByIter);
return _.groupBy(list, function(o) {
var values = _.map(groupByIter, function(k) {
return o[k];
});
return _.find(values);
});
}
});
snippet.log(JSON.stringify(_.splitGroupBy(data.notes,['userId', 'replyToId'])));
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<!-- Provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>
Assuming userId and replyToId are mutually exclusive (i.e. you either have a userId or a replyToId, but never both) as they are in the sample data, then specifying a custom grouping function works:
_.groupBy(data.notes, function(note) {
return note.userId || note.replyToId;
});

Categories