0

I have two JSON data with multi-stage nesting. I want compare it by following conditions: 1) if name in the first object equal to name in the second object compare them prop arrays, else if nothing equal names in two object return empty array; 2) compare objects into two prop arrays and find difference; 3) return new object with difference from first and second arrays.

const p1 = [{
  name: 'B [1]', // name equals and prop differnce, then comparing it
  prop: [{ 
    A: { A: 1, B: 2 }, 
    B: { A: 1, B: 2 }, 
    C: { C: 78, D: 4, T: 7, } }],
  }, {
  name: 'B [2]', // name equals, then skiping it
  prop: [{ 
    A: { A: 1, B: 2 }, 
    B: { A: 1, B: 2 }, 
    D: { C: 3,  D: 4, Y: 13 } }],
  }, {
  name: 'B [3]', // name equals and prop differnce, then comparing it
  prop: [{ 
    E: { A: 1, B: 2 }, 
    R: { A: 1, B: 2 }, 
    T: { C: 3,  D: 4, } }],
  }, {
  name: 'B [4]', // name and prop equals, then skiping it 
  prop: [{ 
    A: { A: 1, B: 2 }, 
    S: { A: 1, B: 2 }, 
    D: { C: 3,  D: 4, } }],
}]

const p2 = [{
  name: 'B [1]', // name equals and prop differnce, then comparing it 
  prop: [{ 
    A: { A: 1, B: 8 }, 
    B: { A: 1, B: 2 }, 
    C: { C: 3, T: 7, O: 9 } }],
  }, {
  name: 'B [6]', // name not equals, then skiping it
  prop: [{ 
    A: { A: 1, B: 2 }, 
    B: { A: 1, B: 2 }, 
    D: { C: 3,  D: 4 } }],
  }, {
  name: 'B [3]', // name equals and prop differnce, then comparing it
  prop: [{ 
    E: { A: 1, B: 2 }, 
    R: { A: 1, B: 2, U: 150 }, 
    T: { C: 3,  D: 4, } }],
  }, {
  name: 'B [4]', // name and prop equals, then skiping it 
  prop: [{ 
    A: { A: 1, B: 2 }, 
    S: { A: 1, B: 2 }, 
    D: { C: 3,  D: 4, } }],
}]

The result should look like this:

const result = [{
  name: 'B [1]',
  propOne: [{
    A: { B: 2 },
    C: { C: 78, D: 4, O: 'Missing' }
  }],
  propTwo: [{
    A: { B: 8 },
    C: { C: 3, D: 'Missing', O: 9 }
  }],
  },{
  name: 'B [3]',
  propOne: [{
    R: { U: 'Missing' }
    }],
  propTwo: [{
    R: { U: 150 }
    }]
}]

I also bitterly attach my worthless code here, which does nothing.

const compare = (p1, p2) => {
  return p1.reduce((acc, curr) => {
    p2.reduce((acc2, curr2) => {
      if (curr.name === curr2.name) {
        const keys1 = R.fromPairs(Object.keys(curr.prop[0]).map(x => ([x, curr.prop[0][x]])));
        const keys2 = R.fromPairs(Object.keys(curr2.prop[0]).map(x => ([x, curr2.prop[0][x]])));
      }
      return acc;
    }, [])

    return acc;
  }, [])
}

I would be extremely grateful for any help and advice.

1
  • I suggest reformatting the data to leverage JavaScript's objects being native dictionary structures instead of needing to manually check and combine based on name. Like this: p1.forEach(b => result[b.name] = b.prop) and then p2.forEach(b => /* decide how to merge into result based on result[p2.name] */) Commented Nov 7, 2019 at 19:35

2 Answers 2

3

All the difficulty resides in specing the expected behaviour of the comparison function:

for two objects (that I refer as values) a and b: {A:1,B:2} and {A:1,B:3,C:4} the output of cmp(a,b) shall be:

  foreach key of a:
    if a[key] != b[key] (or b does not have k prop)
      diff[key] = a[key]
    else (value is equal, no diff)
  foreach key of b not in a
    diff[key] = Missing

hence (e.g) {B:2, C:'Missing'}

when comparing the values, if diff is empty, you can skip the current prop and when comparing props if the diff is empty skip the record (as if names were different)

function cmp(x,y){
  let a = x.prop[0];
  let b = y.prop[0];
  return Object.keys(a).reduce((o,k)=>{
    //compare the right value (such as { A: 1, B: 2 }). assumes primitive types
    let u = a[k];
    let v = b[k];
    
    let diff = Object.keys(u).reduce((o,k)=>{
      return u[k]==v[k]?o:(o[k] = u[k],o)
    },{})
    
    Object.keys(v).reduce((o,k)=>{
      return u.hasOwnProperty(k)?o:(o[k]='Missing',o);
    }, diff);
    
    if(Object.keys(diff).length){
      o[k] = diff;
    }
    
    return o;
  },{});
}
function diff(p1,p2){
  return p1.flatMap((o,i)=>{
    if(p2[i].name != p1[i].name){
      return []
    }

    let a = p1[i];
    let b = p2[i];
    let res = cmp(a,b);
    
    if(!Object.keys(res).length){
      return [];
    }
    
    return {name: a.name, propOne:res, propTwo:cmp(b,a)}
  })
};
const p1 = [{
  name: 'B [1]', // name equals and prop differnce, then comparing it
  prop: [{ 
    A: { A: 1, B: 2 }, 
    B: { A: 1, B: 2 }, 
    C: { C: 78, D: 4, T: 7, } }],
  }, {
  name: 'B [2]', // name equals, then skiping it
  prop: [{ 
    A: { A: 1, B: 2 }, 
    B: { A: 1, B: 2 }, 
    D: { C: 3,  D: 4, Y: 13 } }],
  }, {
  name: 'B [3]', // name equals and prop differnce, then comparing it
  prop: [{ 
    E: { A: 1, B: 2 }, 
    R: { A: 1, B: 2 }, 
    T: { C: 3,  D: 4, } }],
  }, {
  name: 'B [4]', // name and prop equals, then skiping it 
  prop: [{ 
    A: { A: 1, B: 2 }, 
    S: { A: 1, B: 2 }, 
    D: { C: 3,  D: 4, } }],
}]

const p2 = [{
  name: 'B [1]', // name equals and prop differnce, then comparing it 
  prop: [{ 
    A: { A: 1, B: 8 }, 
    B: { A: 1, B: 2 }, 
    C: { C: 3, T: 7, O: 9 } }],
  }, {
  name: 'B [6]', // name not equals, then skiping it
  prop: [{ 
    A: { A: 1, B: 2 }, 
    B: { A: 1, B: 2 }, 
    D: { C: 3,  D: 4 } }],
  }, {
  name: 'B [3]', // name equals and prop differnce, then comparing it
  prop: [{ 
    E: { A: 1, B: 2 }, 
    R: { A: 1, B: 2, U: 150 }, 
    T: { C: 3,  D: 4, } }],
  }, {
  name: 'B [4]', // name and prop equals, then skiping it 
  prop: [{ 
    A: { A: 1, B: 2 }, 
    S: { A: 1, B: 2 }, 
    D: { C: 3,  D: 4, } }],
}];

console.log('result', JSON.stringify(diff(p1,p2),null,2))

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you so much, I'm very grateful for your help, I hope it was very easy for your.
0

It's kinda late, and i don't have time anymore for extensive tests right now, so this may have some bugs for unexpected cases (i hope not though). It's way too long for a comment, and discarding it would be a bit of a waste.

The below code contains two ways: one constructive, starting with empty objects, building up, and one the other way around, starting with the full objects, and then deleting.

Note, that in your data structures, there are several "one element arrays". If these can contain more than one element (they don't really make much sense to me, it's already objects within arrays within arrays, giving plenty of room for additional props), there needs to be one or two extra map steps, no big issue though.

const p1 = [{
  name: 'B [1]', // name equals and prop differnce, then comparing it
  prop: [{
    A: { A: 1, B: 2 },
    B: { A: 1, B: 2 },
    C: { C: 78, D: 4, T: 7, }
  }],
}, {
  name: 'B [2]', // name equals, then skiping it
  prop: [{
    A: { A: 1, B: 2 },
    B: { A: 1, B: 2 },
    D: { C: 3, D: 4, Y: 13 }
  }],
}, {
  name: 'B [3]', // name equals and prop differnce, then comparing it
  prop: [{
    E: { A: 1, B: 2 },
    R: { A: 1, B: 2 },
    T: { C: 3, D: 4, }
  }],
}, {
  name: 'B [4]', // name and prop equals, then skiping it 
  prop: [{
    A: { A: 1, B: 2 },
    S: { A: 1, B: 2 },
    D: { C: 3, D: 4, }
  }],
}];

const p2 = [{
  name: 'B [1]', // name equals and prop differnce, then comparing it 
  prop: [{
    A: { A: 1, B: 8 },
    B: { A: 1, B: 2 },
    C: { C: 3, T: 7, O: 9 }
  }],
}, {
  name: 'B [6]', // name not equals, then skiping it
  prop: [{
    A: { A: 1, B: 2 },
    B: { A: 1, B: 2 },
    D: { C: 3, D: 4 }
  }],
}, {
  name: 'B [3]', // name equals and prop differnce, then comparing it
  prop: [{
    E: { A: 1, B: 2 },
    R: { A: 1, B: 2, U: 150 },
    T: { C: 3, D: 4, }
  }],
}, {
  name: 'B [4]', // name and prop equals, then skiping it 
  prop: [{
    A: { A: 1, B: 2 },
    S: { A: 1, B: 2 },
    D: { C: 3, D: 4, }
  }],
}];

const result = [{
  name: 'B [1]',
  propOne: [{
    A: { B: 2 },
    C: { C: 78, D: 4, O: 'Missing' }
  }],
  propTwo: [{
    A: { B: 8 },
    C: { C: 3, D: 'Missing', O: 9 }
  }],
  },{
  name: 'B [3]',
  propOne: [{
    R: { U: 'Missing' }
    }],
  propTwo: [{
    R: { U: 150 }
    }]
}]

const diffDestructive = (a, b) => {
  /**
   * Copy the objects, remove all identical properties recursively,
   * then add "Missing" properties for all properties from either side
   * that doesn't exist on the other.
   */

  const remove = (x, y) => {
    for (let key of Object.keys(x)) {
      // hasOwnProperty is only for the degenerate case { prop: undefined }
      if (x[key] === y[key] && y.hasOwnProperty(key)) {
        delete x[key];
        delete y[key];
      }     // typeof null === "object", therefore an additional check is needed
      else if (x[key] && typeof x[key] === "object" && y[key] && typeof y[key] === "object") {
        remove(x[key], y[key]);
        if ([x, y].every(e => Object.keys(e[key]).length === 0)) {
          delete x[key];
          delete y[key];
        }
      }
    }
  };

  const addMissingNotes = (x, y) => {
    for (let key of Object.keys(x)) {
      if (!(y.hasOwnProperty(key))) y[key] = "Missing";
      else if (x[key] && typeof x[key] === "object" && y[key] && typeof y[key] === "object")
        addMissingNotes(x[key], y[key]);
    }
  };

  // quick and dirty object deep-copy
  let [modA, modB] = [a, b].map(e => JSON.parse(JSON.stringify(e)));

  remove(modA, modB);
  addMissingNotes(modA, modB);
  addMissingNotes(modB, modA);

  return [modA, modB];
};

const diffConstructive = (a, b) => {
  /**
   * Add differing properties to the result step by step.
   * Nested objects are handled recursively.
   */

  let diffA = {}, diffB = {};

  for (let key of Object.keys(a)) {
    //properties that a and b share
    if (b.hasOwnProperty(key)) {
      if (a[key] && typeof a[key] === "object" && b[key] && typeof b[key] === "object") {
        let subDiffs = diffConstructive(a[key], b[key]);
        // The way the construction works, Object.keys(subDiffs[0]).length !== 0 would be enough.
        if (subDiffs.some(e => Object.keys(e).length !== 0)) {
          [diffA[key], diffB[key]] = subDiffs;
        }
      } else if (a[key] !== b[key]) {
        diffA[key] = a[key];
        diffB[key] = b[key];
      }
    } // properties that a has but b doesn't
    else {
      diffA[key] = a[key];
      diffB[key] = "Missing";
    }
  }

  // properties that b has but a doesn't
  for (let key of Object.keys(b)) {
    if (!a.hasOwnProperty(key)) {
      diffB[key] = b[key];
      diffA[key] = "Missing";
    }
  }

  return [diffA, diffB];
};

const compare = (a, b, method) => a
  .map((e, i) => [e, b[i]])
  //same name only
  .filter(([a, b]) => a.name === b.name)
  // formatting
  .map(([a, b]) => {
    const [diffA, diffB] = method(a.prop[0], b.prop[0]);
    return {
      name: a.name,
      propOne: [diffA],
      propTwo: [diffB]
    };
  })
  // There must be a difference
  .filter(e => [e.propOne[0], e.propTwo[0]].some(e => Object.keys(e).length !== 0));

const destructive = compare(p1, p2, diffDestructive);
const constructive = compare(p1, p2, diffConstructive);

console.log(`Constructive method gives the wanted result: ${_.isEqual(result, destructive)}`);
console.log(`Destructive method gives the wanted result: ${_.isEqual(result, constructive)}`);
<!--
this is only for a deepequals function, _.isEqual,
and only used for checking the results. I could have copied
one into the code, but why make this even longer...
-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.core.min.js"></script>

1 Comment

Thank you so much, I'm very grateful. Your solution working and in case when I comparing objects don't matching and it cool.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.