Get values for a matching key recursively from object - javascript

I have this json object
{
"data": {
"user": {
"user_info": {
"id": "AoGC2HQ9vedHmzcMX"
},
"product": [
{
"node": {
"id": "NzcxNzU2ODU1ODM1",
"feedback": {
"raters": {
"nodes": [
{
"id": "1",
"name": "Dan"
},
{
"id": "2",
"name": "Allen"
},
{
"id": "3",
"name": "Williams"
}
]
},
"commentors": {
"nodes": [
{
"id": "001",
"name": "Kent"
},
{
"id": "002",
"name": "Jay"
}
]
}
}
}
}
]
}
}
}
So how do I make it to get values of id If the parent property matches the desired key name, In this example I want to get all id's from raters.nodes only.
so expected result is
[1,2,3]
I know can do obj.data.user.product[0].node.feedback.raters.nodes and loop through that, but that is not how I want and the object tree occasionally changes.
I have used this recursive function
const recursiveSearch = (obj, searchKey, results = []) => {
const r = results;
Object.keys(obj).forEach(key => {
const value = obj[key];
if(key === searchKey && typeof value !== 'object'){
r.push(value);
}else if(typeof value === 'object'){
recursiveSearch(value, searchKey, r);
}
});
return r;
};
//returns all id's
While it works, it returns all id values, so how do I improve it? If not, how do I make this possible?

I think you want to really do this in 2 steps,..
First make a function to get the root node your looking for, and then you can just use map like normal.
Below is an example.
var data = JSON.parse("{\"data\":{\"user\":{\"user_info\":{\"id\":\"AoGC2HQ9vedHmzcMX\"},\"product\":[{\"node\":{\"id\":\"NzcxNzU2ODU1ODM1\",\"feedback\":{\"raters\":{\"nodes\":[{\"id\":\"1\",\"name\":\"Dan\"},{\"id\":\"2\",\"name\":\"Allen\"},{\"id\":\"3\",\"name\":\"Williams\"}]},\"commentors\":{\"nodes\":[{\"id\":\"001\",\"name\":\"Kent\"},{\"id\":\"002\",\"name\":\"Jay\"}]}}}}]}}}");
function getRoot(data, search) {
function get(path, data) {
for (const [k, v] of Object.entries(data)) {
if (v instanceof Object) {
const pp = `${path}.${k}`;
if (pp.slice(-search.length) === search) {
return v;
}
const r = get(`${path}.${k}`, v);
if (r) return r;
}
}
}
return get('', data);
}
const r = getRoot(data, 'raters.nodes');
console.log(r && r.map(i => i.id));

Related

JS: Check for existence of deep object key and replace value

I am wanting to develop a function to check for the existence of a key within a deep object and then replace the value of that key with a data set from another object.
E.g.
var obj = {
"id": 1,
"component": "Preset 1",
"priority": 1,
"header": {
"modeSet": 2
}
}
const modeSets = [
{
"id": 1
"name": "Mode Set 1"
},
{
"id": 2
"name": "Mode Set 2"
}
]
function obtainModeSets(obj){
//...
}
When the function obtainModeSets runs I'd like to mutate obj so that the value of modeSet within obj equals { "id": 2 "name": "Mode Set 2" }
Does anyone have any suggestions? Thanks
You can use recursion like this
const obj = {
"id": 1,
"component": "Preset 1",
"priority": 1,
"header": {
"modeSet": 2
}
}
const modeSets = [{
"id": 1,
"name": "Mode Set 1"
},
{
"id": 2,
"name": "Mode Set 2"
}
]
function obtainModeSets(obj) {
Object.entries(obj).forEach(([key, value]) => {
if (key === "modeSet") {
obj[key] = modeSets.find(set => set.id === value)
return
}
if (typeof value === "object") {
obtainModeSets(value)
}
})
}
obtainModeSets(obj)
console.log(obj)
I think something like the below code maybe solves your problem. I don't know what you mean exactly. But based on the showing example, I guess you need to replace your key with the id provided in the modeSets.
function obtainModeSets(obj, key, cb){
// loop through all possible keys
Object.keys(obj).forEach(k => {
if (key === k) {
obj[k] = cb(obj[k])
}
// if the value of the key is object, just go inside
if (typeof obj[k] === "object") {
obtainModeSets(obj[k], key, cb)
}
})
}
// you can call your function like this
obtainModeSets(obj, 'modeSet', (val) => {
return modeSets.find(mode => mode.id === val)
})

how to filter object from nested objects by specific unique key

This is input data, where i want filter a object by ID key
let myData = {
"nodeInfo":
{
"9":
{
"1": { "ID": "14835", "name": "Binod" },
"2": { "ID": "14836", "name": "Rahul" },
"3": { "ID": "14837", "name": "Sunil" },
},
"10":
{
"4": { "ID": "14839", "name": "Shikhar" },
"5": { "ID": "14840", "name": "Hari" },
"6": { "ID": "14841", "name": "Harsh" },
}
}
};
i want which object who have ID value 14835
so my result would be:: { "ID": "14835", "name": "Binod" }
This supports many levels of nested objects
function isObject(possibleObject) {
return typeof possibleObject === 'object' && possibleObject !== null
}
function find(data, key) {
for (const element of Object.values(data)) {
if (element.ID) {
if (element.ID === key) {
return element;
}
continue;
}
if (isObject(element)) {
const foundElement = find(element, key);
if (foundElement !== null) {
return foundElement;
}
}
}
// Not found
return null;
}
``
#Gluey1017 has provided a very good answer that will return the first occurrence of an object with the given ID key. In the following snippet I modified/extended his script to also collect multiple results should they exist:
const myData = {
"nodeInfo":
{
"9":
{
"1": { "ID": "14835", "name": "Binod" },
"2": { "ID": "14836", "name": "Rahul" },
"3": { "ID": "14837", "name": "Sunil" },
},
"10":
{
"4": { "ID": "14839", "name": "Shikhar" },
"5": { "ID": "14840", "name": "Hari" },
"6": { "ID": "14841", "name": "Harsh" },
},
"15":
{ "7": {
"8": { "ID": "14835" , "name": "Carsten" },
"9": { "ID": "14842" , "name": "someone" }
} }
}
};
function find(data,key){
const found=[]; // results array
function fnd(da) { // recursive function
for (const el of Object.values(da)){
if (el?.ID === key) found.push(el);
if (typeof el==='object' && el !== null) fnd(el);
}
}
fnd(data);
return found;
}
console.log(find(myData,"14835"));
Using an arrow function expression, what about
getter = ID => Object.values(
myData.nodeInfo
).map(
o => Object.values(o).filter(
v => v.ID === ID
)[0]
)
and then
> getter("14835")
< (2) [{…}, {…}]
And, depending on whether your identifiers are unique or not, you can actually do
> getter("14835")[0]
< {ID: '14835', name: 'Binod'}
Using spread operator to find item from a flat array of objects:
// get nodeInfo values
Object.values(myData.nodeInfo)
// reduce to flat array of objects
.reduce((acc,v) => [...acc, ...Object.values(v)],[])
// assuming ID is unique find object
.find(o => o.ID === "14835")
If the level is unknown, with data structured as above, recursion can be used to get to the level where objects have the desired properties.
Please notice that the function expects an array as its first parameter.
// check for the presence of the relevant keys
const findByID = (vals, id) => vals.some(o => 'ID' in o && 'name' in o)
// if present find the value
? vals.find(o => o.ID === id)
// else repeat with level below
: findByID(vals.reduce((acc,v) => [...acc, ...Object.values(v)],[]), id);
findByID(Object.values(myData), "14835");

Pick one field from Array of Objects recursively

I am looking for a way using JavaScript / Lodash to retrieve the same hirearchy as the input array of objects but want to retain only selected fields.
I could also formulate this question as doing a deep copy of an array of objects retaining only certain fields.
For example, given the following array:
[
{
"id": "q1",
"text": "Q1 text",
"children": [
{
"id": "q11",
"text": "t",
"children": [
{
"id": "q111",
"text": "t"
},
{
"id": "q112",
"text": "t"
}
]
}
]
},
{
"id": "q2",
"text": "e",
"children": [
{
"id": "q22",
"text": "e"
}
]
},
{
"id": "q3",
"text": "e"
}
]
The output should be as below. This is exactly the same as array of objects above but keeps only id and children's ids. The children can be any level deep.
[
{
"id": "q1",
"children": [
{
"id": "q11",
"children": [
{
"id": "q111",
},
{
"id": "q112"
}
]
}
]
},
{
"id": "q2",
"children": [
{
"id": "q22",
}
]
},
{
"id": "q3"
}
]
You can make a function that takes an array and maps it to objects with just the id and children. To set the id, just copy the id, to set the children on the returned object pass the children array back into the function recursively:
let arr = [{"id": "q1","text": "Q1 text","children": [{"id": "q11","text": "t","children": [{"id": "q111","text": "t"},{"id": "q112","text": "t"}]}]},{"id": "q2","text": "e","children": [{"id": "q22","text": "e"}]},{"id": "q3","text": "e"}]
const justIDs = (arr) => arr.map(({id, children}) => {
let ret = {id}
if(children) ret.children = justIDs(children)
return ret
})
let filtered = justIDs(arr)
console.log(filtered)
and lodash, love lodash, learn lodash...
function omitKeysDeep(input, keys) {
if(!_.isArray(keys)) throw new Error('omitKeys expected an array');
return _.map(input, (elem) => {
if(elem.children) elem.children = omitKeysDeep(elem.children, keys);
return _.omit(elem, keys);
});
}
omitKeysDeep(a, ['text']);
OR... instead of _.omit(..) to remove unwanted keys you could use _.pick(...) to specify only wanted keys:
function pickKeysDeep(input, keys) {
if(!_.isArray(keys)) throw new Error('pickKeys expected an array');
return _.map(input, (elem) => {
if(elem.children) elem.children = pickKeysDeep(elem.children, keys);
return _.pick(elem, keys);
});
}
pickKeysDeep(a, ['id', 'children']);
Here's a non-recursive approach that uses an explicit stack and a set for fast lookup in cases when you have many keys to prune out. This is a general solution that should work on any keys you throw at it and doesn't mutate the original array.
const data = [
{
"id": "q1",
"text": "Q1 text",
"children": [
{
"id": "q11",
"text": "t",
"children": [
{
"id": "q111",
"text": "t"
},
{
"id": "q112",
"text": "t"
}
]
}
]
},
{
"id": "q2",
"text": "e",
"children": [
{
"id": "q22",
"text": "e"
}
]
},
{
"id": "q3",
"text": "e"
}
];
const removeKeys = (arr, keys) => {
const keep = new Set(keys);
const res = [];
const stack = [[arr, res]];
while (stack.length) {
const [curr, cpy] = stack.pop();
if (Array.isArray(curr)) {
curr.forEach((e, i) => {
cpy[i] = {};
for (const k in e) {
if (keep.has(k)) {
cpy[i][k] = e[k];
stack.push([e[k], cpy[i][k]]);
}
}
});
}
}
return res;
};
console.log(JSON.stringify(removeKeys(data, ["id", "children"]), null, 4));
Here's my version which does work recursively.
/**
* Like _.pick() but will also map over arrays implicitly.
* ie. path 'a.b.c' will transform {a:[{b:{c:1,d:2}}]} => {a:[{b:{c:1}}]}
*
* #param {object} o - Object to copy.
* #param {string[]} paths - List of paths to include.
* #returns {mixed} - Copied object.
*/
Utils.pickDeep = (o, paths) => {
if (Array.isArray(o)) {
return _.map(o, v=>
Utils.pickDeep(v, paths));
}
else if (null != o && 'object' === typeof o) {
const result = {};
for (const path of paths) {
const parts = path.split('.');
const part = parts.shift();
result[part] = o[part];
if (parts.length < 1) {
// do not recurse
}
else {
// recurse
result[part] = Utils.pickDeep(_.get(o, [part]), [parts.join('.')]);
}
}
return result;
}
else {
return o;
}
};
and
/**
* Like _.omit() but will also map over arrays implicitly.
* ie. path 'a.b.c' will transform {a:[{b:{c:1,d:2}}],e:4} => {a:[{b:{d:2}}],e:4}
*
* #param {object} o - Object to copy.
* #param {string[]} paths - List of paths to exclude.
* #returns {mixed} - Copied object.
*/
Utils.omitDeep = (o, paths) => {
if (Array.isArray(o)) {
return _.map(o, v=>
Utils.omitDeep(v, paths));
}
else if (null != o && 'object' === typeof o) {
const result = { ...o };
for (const path of paths) {
const parts = path.split('.');
const part = parts.shift();
delete result[part];
if (parts.length < 1) {
// do not recurse
}
else {
// recurse
result[part] = Utils.omitDeep(_.get(o, [part]), [parts.join('.')]);
}
}
return result;
}
else {
return o;
}
};

How to get the key value from nested object

I am having below object where I am trying to get all the id values.
[{
"type": "test",
"id": "100",
"values": {
"name": "Alpha"
},
"validations": []
}, {
"type": "services",
"validations": [{
"id": "200",
"name": "John",
"selection": [{
"id": "300",
"values": {
"name": "Blob"
}
}]
}]
}]
Using the below code, I am getting only the first id value. Is there any way to get all the id values from the nested object without using any external module.
for (var prop in obj) {
console.log(prop)
if (prop === key) {
set.push(prop);
}
}
Expected Output
[100,200,300] //all id values
You can use a JavaScript function like below to get the nested properties:
function findProp(obj, key, out) {
var i,
proto = Object.prototype,
ts = proto.toString,
hasOwn = proto.hasOwnProperty.bind(obj);
if ('[object Array]' !== ts.call(out)) out = [];
for (i in obj) {
if (hasOwn(i)) {
if (i === key) {
out.push(obj[i]);
} else if ('[object Array]' === ts.call(obj[i]) || '[object Object]' === ts.call(obj[i])) {
findProp(obj[i], key, out);
}
}
}
return out;
}
Check this Fiddle for a working solution.
Using Object.keys
function findProp(obj, prop) {
var result = [];
function recursivelyFindProp(o, keyToBeFound) {
Object.keys(o).forEach(function (key) {
if (typeof o[key] === 'object') {
recursivelyFindProp(o[key], keyToBeFound);
} else {
if (key === keyToBeFound) result.push(o[key]);
}
});
}
recursivelyFindProp(obj, prop);
return result;
}
// Testing:
var arr = [{
"type": "test",
"id": "100",
"values": {
"name": "Alpha"
},
"validations": []
}, {
"type": "services",
"validations": [{
"id": "200",
"name": "John",
"selection": [{
"id": "300",
"values": {
"name": "Blob"
}
}]
}]
}];
console.log(findProp(arr, "id"));
To get the keys from nested objects, you first need to put your code in a function, then for each of the top-level keys, check if it's an array or object. If it is, just call your function again from within that function (weird, I know.) Just make sure you don't skip the check of whether it's an object. You'll get stuck in an infinite loop. Something like this:
function parseObjectKeys(obj) {
for (var prop in obj) {
console.log(prop)
var sub = obj[prop]
if (typeof(sub) == "object") {
parseObjectKeys(sub);
}
}
}
Here's a more complex example:
https://jsfiddle.net/tfqLnzLm/1/
You can use a XPath styled json parser like JSONPath. The version I'm presenting here is a extended version I did here:
function jsonPath(obj,expr,arg){var P={resultType:arg&&arg.resultType||"VALUE",result:[],normalize:function(e){var t=[];return e.replace(/[\['](\??\(.*?\))[\]']/g,function(e,r){return"[#"+(t.push(r)-1)+"]"}).replace(/'?\.'?|\['?/g,";").replace(/;;;|;;/g,";..;").replace(/;$|'?\]|'$/g,"").replace(/#([0-9]+)/g,function(e,r){return t[r]})},asPath:function(e){for(var t=e.split(";"),r="$",a=1,n=t.length;n>a;a++)r+=/^[0-9*]+$/.test(t[a])?"["+t[a]+"]":"['"+t[a]+"']";return r},store:function(e,t){return e&&(P.result[P.result.length]="PATH"==P.resultType?P.asPath(e):t),!!e},trace:function(e,t,r){if(e){var a=e.split(";"),n=a.shift();if(a=a.join(";"),t&&t.hasOwnProperty(n))P.trace(a,t[n],r+";"+n);else if("*"===n)P.walk(n,a,t,r,function(e,t,r,a,n){P.trace(e+";"+r,a,n)});else if(".."===n)P.trace(a,t,r),P.walk(n,a,t,r,function(e,t,r,a,n){"object"==typeof a[e]&&P.trace("..;"+r,a[e],n+";"+e)});else if(/,/.test(n))for(var l=n.split(/'?,'?/),s=0,c=l.length;c>s;s++)P.trace(l[s]+";"+a,t,r);else/^\(.*?\)$/.test(n)?P.trace(P.eval(n,t,r.substr(r.lastIndexOf(";")+1))+";"+a,t,r):/^\?\(.*?\)$/.test(n)?P.walk(n,a,t,r,function(e,t,r,a,n){P.eval(t.replace(/^\?\((.*?)\)$/,"$1"),a[e],e)&&P.trace(e+";"+r,a,n)}):/^(-?[0-9]*):(-?[0-9]*):?([0-9]*)$/.test(n)&&P.slice(n,a,t,r)}else P.store(r,t)},walk:function(e,t,r,a,n){if(r instanceof Array)for(var l=0,s=r.length;s>l;l++)l in r&&n(l,e,t,r,a);else if("object"==typeof r)for(var c in r)r.hasOwnProperty(c)&&n(c,e,t,r,a)},slice:function(e,t,r,a){if(r instanceof Array){var n=r.length,l=0,s=n,c=1;e.replace(/^(-?[0-9]*):(-?[0-9]*):?(-?[0-9]*)$/g,function(e,t,r,a){l=parseInt(t||l),s=parseInt(r||s),c=parseInt(a||c)}),l=0>l?Math.max(0,l+n):Math.min(n,l),s=0>s?Math.max(0,s+n):Math.min(n,s);for(var o=l;s>o;o+=c)P.trace(o+";"+t,r,a)}},eval:function(x,_v,_vname){try{return $&&_v&&eval(x.replace(/#/g,"_v"))}catch(e){throw new SyntaxError("jsonPath: "+e.message+": "+x.replace(/#/g,"_v").replace(/\^/g,"_a"))}}},$=obj;return expr&&obj&&("VALUE"==P.resultType||"PATH"==P.resultType)?(P.trace(P.normalize(expr).replace(/^\$;/,""),obj,"$"),P.result.length?P.result:!1):void 0}
// some extensions I have added to JSONPath
var jsonPathStore = function(obj,path,values) {
var maps=jsonPath(obj, path,{resultType:"PATH"})
maps.map(function(item,index) {
return eval( '(' + item.replace(/\$/,"obj") + '="' + values[index] +'"' + ')' );
})
}
var jsonPathDelete = function(obj,path) {
var maps=jsonPath(obj, path,{resultType:"PATH"})
maps.map(function(item,index) {
return eval( '(' + 'delete ' + item.replace(/\$/,"obj") + ')' );
})
}
var jsonPathRead = function(obj,path) {
var maps=jsonPath(obj, path,{resultType:"PATH"})
return maps.map(function(item,index) {
return eval( '(' + item.replace(/\$/,"obj") + ')' );
})
}
var jsonObject = [{
"type": "test",
"id": "100",
"values": {
"name": "Alpha"
},
"validations": []
}, {
"type": "services",
"validations": [{
"id": "200",
"name": "John",
"selection": [{
"id": "300",
"values": {
"name": "Blob"
}
}]
}]
}]
// this XPath will read all the id properties starting from the root element
console.log( "jsonPathRead All Ids" + JSON.stringify(jsonPathRead(jsonObject,"$..id"), null, 2) )
function getIds(obj) {
for (var x in obj) {
if (typeof obj[x] === 'object') {
getIds(obj[x]);
} else if (x === 'id') {
console.log(obj.id);
}
}
}

Compare two arrays and update with the new values by keeping the existing objects using javascript

Below are my two arrays .I want to compare them and the resultant array should contain the updated values.Id's are common..
The arrays spans to n levels ie., there is no fixed levels..
The first array ie., the array before updation..
var parentArray1=[
{
"id": 1,
"name": "test",
"context": [
{
"id": 1.1,
"name": "test 1.1"
}
]
},
{
"id": 2,
"name": "test"
},
{
"id": 3,
"name": "test",
"context": [
{
"id": 3.1,
"name": "test 3.1"
}
]
},
{
"id": 4,
"name": "test"
}
]
The operations that i performed are
1.Adding a new Item
2.Updating an existing item
As a result of these two operations the changed values I will be getting in a different array..
ie.,
var changedArray=
[
{
"id": 1,
"name": "test1",
"context": [
{
"id": 1.1,
"name": "Changed test 1.1"
}
]
},
{
"id": 5,
"name": "test5"
}
]
Now I have written a generic function that loops through the parentArray1 and using the unique propertiesI need to either add a new item,if the item is there in the changedArray or update an existing item at any level
The resultant array should be ..
[
{
"id": 1,
"name": "test",
"context": [
{
"id": 1.1,
"name": "Changed test 1.1"
}
]
},
{
"id": 2,
"name": "test"
},
{
"id": 3,
"name": "test",
"context": [
{
"id": 3.1,
"name": "test 3.1"
}
]
},
{
"id": 4,
"name": "test"
},
{
"id": 5,
"name": "test5"
}
]
Generic function:
compareArray(parentArray1, changedArray, ["id"]);
function compareArray(array1, array2, propertyArray) {
var newItem = new Array();
array2.map(function(a1Item) {
array1.map(function(a2Item) {
/ If array loop again /
if (a2Item.constructor === Array) {
compareArray(a2Item, a1Item)
} else {
/ loop the property name to validate /
propertyArray.map(function(property) {
if (a2Item[property]) {
if (a2Item[property] === a1Item[property]) {
a2Item = a1Item
} else {
var isAvailable = _.find(newItem, function(item) {
return item[property] === a1Item[property]
})
if (!isAvailable) {
newItem.push(a1Item);
}
}
}
})
}
});
});
/ Insert the new item into the source array /
newItem.map(function(item) {
array1.push(item);
});
console.log("After Compare : " + array1);
}
I suggest to use a temporary object for the reference to the id and update if exist or push if not exist.
var parentArray1 = [{ "id": 1, "name": "test", "context": [{ "id": 1.1, "name": "test 1.1" }] }, { "id": 2, "name": "test" }, { "id": 3, "name": "test", "context": [{ "id": 3.1, "name": "test 3.1" }] }, { "id": 4, "name": "test" }],
changedArray = [{ "id": 1, "name": "test1", "context": [{ "id": 1.1, "name": "Changed test 1.1" }] }, { "id": 5, "name": "test5" }];
function insert(array, data) {
function iter(array) {
array.forEach(function (a) {
if (!('id' in a)) {
return;
}
if (o[a.id] !== a) {
o[a.id] = a;
}
Object.keys(a).forEach(function (k) {
Array.isArray(a[k]) && iter(a[k]);
});
});
}
var o = {};
iter(array);
data.forEach(function (a) {
if (o[a.id]) {
Object.keys(a).forEach(function (k) {
o[a.id][k] = a[k];
});
return;
}
array.push(a);
});
}
insert(parentArray1, changedArray);
document.write('<pre>' + JSON.stringify(parentArray1, 0, 4) + '</pre>');
This is what I came up with:
function sameKeys(o1, o2, keys) {
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (!o1.hasOwnProperty(key) || !o2.hasOwnProperty(key))
throw 'compared objects do not have the key ' + key;
if (o1[key] !== o2[key])
return false;
}
return true;
}
function isNothing(o) {
return typeof(o) === 'undefined' || o === null;
}
// this does not work if objects have functions as properties
function clone(o) {
if (isNothing(o))
return o;
return JSON.parse(JSON.stringify(o));
}
function extend(o1, o2, keys) {
if (isNothing(o2))
return;
if (isNothing(o1))
throw ('first parameter cannot be empty');
if (typeof(o1) != 'object' || typeof(o2) != 'object')
throw ('extend only works on objects');
Object.keys(o2).forEach(function (key) {
var newVal = o2[key];
if (o1.hasOwnProperty(key)) {
if (isNothing(newVal)) {
delete o1[key];
} else
if (Array.isArray(newVal)) {
compareArray(o1[key], newVal, keys);
} else {
switch (typeof(newVal)) {
case 'object':
extend(o1[key], newVal, keys);
break;
case 'boolean':
case 'number':
case 'string':
o1[key] = newVal;
break;
default:
throw 'not supported property type: ' + typeof(newVal);
}
}
} else {
o1[key] = clone(newVal);
}
});
}
function removeFromArray(arr, ids, keyArray) {
var indexes = [];
var it1s = arr.forEach(function (it, idx) {
if (sameKeys(ids, it, keyArray)) {
indexes.push(idx);
} else {
Object.keys(it).forEach(function (key) {
var newVal = it[key];
if (Array.isArray(newVal)) {
removeFromArray(it[key], ids, keyArray);
}
});
}
});
if (indexes.length) {
if (indexes.length > 1)
throw 'found multiple possible objects for the same key combination'
arr.splice(indexes[0], 1);
}
}
function compareArray(a1, a2, keyArray) {
a2.forEach(function (it2) {
var it1s = a1.filter(function (it) {
return sameKeys(it2, it, keyArray);
});
var it1;
if (!it1s.length) {
it1 = clone(it2);
a1.push(it1);
} else {
if (it1s.length > 1)
throw 'found multiple possible objects for the same key combination'
it1 = it1s[0];
extend(it1, it2, keyArray);
}
if (it2.removedIds) {
it2.removedIds.forEach(function (ids) {
removeFromArray(a1, ids, keyArray);
});
}
});
}
Use it with compareArray(parentArray1,changedArray,['id']);
Note that it would not work with objects that contain functions. Also, if the arrays would be large, perhaps a better solution is to sort both arrays by key, then always look from the last found object up. That's all I got for now.
Updated it with some concepts from Nina and some clearing of the code.
As I understood it, you only want to add properties. So extend({a: {b: 2}},{a:{c:3}}) will result in {a: {b:2,c:3}}. If this is not what you wanted, let me know.
I also added functionality for removing ids. If any of the objects in the array contains a removedIds array of the form [{id: 4},{id: 5}] then the items with those ids will be removed from the original array.
Slight modification on code, to satisfy your conditions. Try it!
function compareArray(originalArray, destinationArray, propertyArray) {
var newItem = new Array(), processedItem = new Array();
for (var i = 0; i < originalArray.length; i++) {
var sourceElement = originalArray[i];
for (var j = 0; j < destinationArray.length; j++) {
var destinationElement = destinationArray[j];
var isUpdated = false;
if (sourceElement.constructor === Array) {
compareArray(sourceElement, destinationElement, propertyArray);
} else {
/* loop the property name to validate */
propertyArray.map(function(property) {
if (sourceElement[property]) {
if (sourceElement[property] === destinationElement[property]) {
originalArray[i] = _.clone(destinationElement);
isUpdated = true;
return;
} else {
var isAvailable = _.find(newItem, function(item) {
return item[property] === destinationElement[property];
});
if (!isAvailable) {
var isAlreadyProcessed = _.find(processedItem, function(item) {
return item[property] === destinationElement[property];
});
if(!isAlreadyProcessed){
newItem.push(destinationElement);
}
}
}
}
});
}
if (isUpdated === true) {
break;
}
}
processedItem.push(sourceElement);
}
newItem.map(function(item) {
originalArray.push(item);
});
return originalArray;
}

Categories

Resources