Home > OS >  Compare JSON Keys to get missing elements
Compare JSON Keys to get missing elements

Time:03-30

I want to compare to Nested Objects and create a new Object with all the missing fields. I have a main JSON respone in a Javascript Object:

var MainFile = {
     "id": 0,
     "name": 'test',
     "info": {
           "data_11":0,
           "data_12":0,
           "data_13":{
                "data_131":0,
                "data_132":0,
                "data_133":0,
           },
     },
     "info2": {
          "data_21":0,
          "data_22":0,
          "data_23":0,
          }
     }
 

And now I have x amount of objects that I have to compare against the main and check that object has all the same keys.

var obj2 = {
     "id": 0,
     "info": {
         "data_11":0,
         "data_13":{
             "data_131":0,
             "data_133":0,
         },
     },
     "info2": {
         "data_22":0,
         "data_23":0,
         }  
     }
 }

So seeing both objects we can see the differences. I've tried recursive functions to check with Object.hasProperty, but I never get the result I'm looking for. that would be an object that would look like the following:

result = {
     "id": true,
     "name": false,
     "info": {
         "data_11":true,
         "data_12":false,
         "data_13":{
             "data_131":false,
             "data_132":true,
             "data_133":false,
         },
     },
     "info2": {
          "data_21":true,
          "data_22":false,
          "data_23":false,
          }
     }
 

Has anyone tried anything like this? I've looked everywhere, but everyone compares the value of the key, not if the actual key is missing in the nested array.

Any help would be appreciated

CodePudding user response:

Below is a very basic recursive solution. It does not account for null and does not handle scalar vs object comparison.

// compares keys of obj1 and obj2 recursively
// and appends the result to the res object
function diff(obj1, obj2, res) {
  Object.keys(obj1).forEach(function(key) {
    if (typeof obj1[key] === "object") {
      res[key] = {};
      diff(obj1[key], obj2 !== undefined ? obj2[key] : undefined, res[key]);
    } else {
      res[key] = obj2 !== undefined && key in obj2;
    }
  });
}
// the two objects to compare
let MainFile = {
  "id": 0,
  "name": 'test',
  "info": {
    "data_11": 0,
    "data_12": 0,
    "data_13": {
      "data_131": 0,
      "data_132": 0,
      "data_133": 0,
    },
  },
  "info2": {
    "data_21": 0,
    "data_22": 0,
    "data_23": 0,
  }
};
let testObject = {
  "id": 0,
  "info": {
    "data_11": 0,
    "data_13": {
      "data_131": 0,
      "data_133": 0,
    },
  },
  "info2": {
    "data_22": 0,
    "data_23": 0,
  }
}
// usage
let result = {};
diff(MainFile, testObject, result);
console.log(result);

CodePudding user response:

One advantage of keeping around a collection of useful utility functions is that we can combine them quickly to create new functionality. For me, this function is as simple as this:

const diffs = (o1, o2) => 
  hydrate (getPaths (o1) .map (p => [p, hasPath (p) (o2)]))

We first call getPaths (o1), which yields

[
  ["id"],
  ["name"],
  ["info", "data_11"],
  ["info", "data_12"],
  ["info", "data_13", "data_131"],
  ["info", "data_13", "data_132"],
  ["info", "data_13", "data_133"],
  ["info2", "data_21"],
  ["info2", "data_22"],
  ["info2", "data_23"]
]

Then we map these into path entry arrays, using hasPath against our test object, which yields:

[
  [["id"], true], 
  [["name"], false],
  [["info", "data_11"], true],
  [["info", "data_12"], false],
  [["info", "data_13", "data_131"], true],
  [["info", "data_13", "data_132"], false],
  [["info", "data_13", "data_133"], true],
  [["info2", "data_21"], false],
  [["info2", "data_22"], true],
  [["info2", "data_23"], true]
]

This is very similar to the format used by Object .fromEntries, except that the keys are arrays of values rather than single strings. (Those values could be strings for object keys or integers for array indices.) To turn this back into an object, we use hydrate, which in turn depends upon setPath. Again hydrate tis a generalization of Object .fromEntries, and setPath is a recursive version of setting a value.

Put together it looks like this:

// utility functions
const getPaths = (obj) =>
  Object (obj) === obj
    ? Object .entries (obj) .flatMap (
        ([k, v]) => getPaths (v) .map (p => [Array .isArray (obj) ? Number(k) : k, ... p])
      )
    : [[]]

const setPath = ([p, ...ps]) => (v) => (o) =>
  p == undefined ? v : Object .assign (
    Array .isArray (o) || Number .isInteger (p) ? [] : {},
    {...o, [p]: setPath (ps) (v) ((o || {}) [p])}
  )

const hydrate = (xs) =>
  xs .reduce ((a, [p, v]) => setPath (p) (v) (a), {})

const hasPath = ([p, ...ps]) => (obj) => 
  p == undefined ? true : p in obj ? (ps .length > 0 ?  hasPath (ps) (obj [p]) : true) : false

// main function
const diffs = (o1, o2) => 
  hydrate (getPaths (o1) .map (p => [p, hasPath (p) (o2)]))

// sample data
const MainFile = {id: 0, name: "test", info: {data_11: 0, data_12: 0, data_13: {data_131: 0, data_132: 0, data_133: 0}}, info2: {data_21: 0, data_22: 0, data_23: 0}}
const obj2 = {id: 0, info: {data_11: 0, data_13: {data_131: 0, data_133: 0}}, info2: {data_22: 0, data_23: 0}};

// demo
console .log (diffs (MainFile, obj2))
.as-console-wrapper {max-height: 100% !important; top: 0}

The functions that this is built upon are all generally useful for many problems, and I simply keep them in my back pocket. You may note that this also handles arrays; that just comes along with those functions

diffs ({foo: ['a', 'b', 'c']}, {foo: ['a', 'b']}) 
//=> {"foo": [true, true, false]}

I also want to note that if you're looking for a more complete diff function, an old answer by user Mulan is a great read.

  • Related