1

I would like to know if there is a way to find the intersection of a key value pair in an array of objects. Let's say you have an array of three objects which all have the same keys like this :

    arrayOfObj = [
    {
        "a": 1,
        "b": "stringB"
        "c": {"c1":1,
            "c2": "stringC2"
            }
    },
    {
        "a": 1,
        "b": "stringBdiff"
        "c": {"c1":1,
            "c2": "stringC2"
            }
    },
    {
        "a": 1,
        "b": "stringB"
        "c": {"c1":1,
            "c2": "stringC2"
            }
    }
  ]

I would like to find the common key value pairs of the three objects:

output= [
 {"a":1}, 
 {"c": {"c1":1, 
        "c2":"stringC2"
       }
 }
]

This is what I have done so far, it works but not on nested objects. I would like to know if there is a more elegant way to do it and one that could work on nested object as well.

    let properties;
    let commonFound = false;
    let notCommonFound = false;
   const commonValues = [];
   let value;
   const initialArray = [{
   "a": 2,
   "b": "stringB",
   "c": {
     "c1": 1,
     "c2": "stringC2"
   }
  },
  {
   "a": 1,
   "b": "stringB",
   "c": {
     "c1": 2,
     "c2": "stringC2"
   }
  },
  {
   "a": 1,
   "b": "stringB",
   "c": {
     "c1": 2,
     "c2": "stringC2"
   }
  }
  
  ];
   
   const commonStorage = [];
   const  reference = initialArray[0];
   properties = Object.keys(reference);
   properties.forEach((property) => {
       for (let i = 0; i < initialArray.length; i++) {
        commonFound = false;
        notCommonFound = false;
          for (let j = 0; j <i ; j++) {        
              if (initialArray[i][property] === initialArray[j][property]) {
                commonFound = true;
                value = initialArray[i][property];
                }
              else {
               notCommonFound = true;
               value = [];
               }         
          }
        }
       if (commonFound && !notCommonFound) {
          commonStorage.push({[property] : value});
       }
   });
     
  console.log(commonStorage);
JordanA29
  • 45
  • 5
  • so basically you want to remove duplicates? – Pari Baker Oct 24 '18 at 17:23
  • Try reading [this Q&A](https://stackoverflow.com/a/33233053/633183) – I'll work on an adaptation that answers this question more directly when I get the time :D – Mulan Oct 24 '18 at 17:27
  • do all the objects have the same keys, or can some objects have different keys. If the keys are all the same you can use something like lodash to do the deep equal checking with `_.isEqual(obj1, obj2);`. – Abdul Ahmad Oct 24 '18 at 17:37
  • Actually I want to keep the duplicates and remove everything else. @duxfox-- all the objects have the same keys exactly and the same number of keys. I can not use deep equality because some values will be different between objects so objects won't be equals but some key value pair might be. – JordanA29 Oct 25 '18 at 08:03

1 Answers1

1

Before we implement intersect we'll first look at how we expect it to behave –

console.log
  ( intersect
      ( { a: 1, b: 2, d: 4 }
      , { a: 1, c: 3, d: 5 }
      )
      // { a: 1 }

  , intersect
      ( [ 1, 2, 3, 4, 6, 7 ]
      , [ 1, 2, 3, 5, 6 ]
      )
      // [ 1, 2, 3, <1 empty item>, 6 ]

  , intersect
      ( [ { a: 1 }, { a: 2 }, { a: 4, b: 5 }, ]
      , [ { a: 1 }, { a: 3 }, { a: 4, b: 6 }, ]
      )
      // [ { a: 1 }, <1 empty item>, { a: 4 } ]

  , intersect
      ( { a: { b: { c: { d: [ 1, 2 ]    } } } }
      , { a: { b: { c: { d: [ 1, 2, 3 ] } } } }
      )
      // { a: { b: { c: { d: [ 1, 2 ] } } } }
  )

Challenging problems like this one are made easier by breaking them down into smaller parts. To implement intersect we will plan to merge two calls to intersect1, each contributing one side of the computed result –

const intersect = (left = {}, right = {}) =>
  merge
    ( intersect1 (left, right)
    , intersect1 (right, left)
    )

Implementing intersect1 is remains relatively complex due to the need to support both objects and arrays – the sequence of map, filter, and reduce helps maintain a flow of the program

const intersect1 = (left = {}, right = {}) =>
  Object.entries (left)
    .map
      ( ([ k, v ]) =>
          // both values are objects
          isObject (v) && isObject (right[k])
            ? [ k, intersect (v, right[k]) ]
          // both values are "equal"
          : v === right[k]
            ? [ k, v ]
          // otherwise
          : [ k, {} ]
      )
    .filter
      ( ([ k, v ]) =>
          isObject (v)
            ? Object.keys (v) .length > 0
            : true
      )
    .reduce
      ( assign
      , isArray (left) && isArray (right) ? [] : {}
      )

Lastly we implement merge the same way we did in the other Q&A

const merge = (left = {}, right = {}) =>
  Object.entries (right)
    .map
      ( ([ k, v ]) =>
          isObject (v) && isObject (left [k])
            ? [ k, merge (left [k], v) ]
            : [ k, v ]
      )
    .reduce (assign, left)

The final dependencies –

const isObject = x =>
  Object (x) === x

const isArray =
  Array.isArray

const assign = (o, [ k, v ]) =>
  (o [k] = v, o)

Verify the complete program works in your browser below –

const isObject = x =>
  Object (x) === x

const isArray =
  Array.isArray

const assign = (o, [ k, v ]) =>
  (o [k] = v, o)

const merge = (left = {}, right = {}) =>
  Object.entries (right)
    .map
      ( ([ k, v ]) =>
          isObject (v) && isObject (left [k])
            ? [ k, merge (left [k], v) ]
            : [ k, v ]
      )
    .reduce (assign, left)

const intersect = (left = {}, right = {}) =>
  merge
    ( intersect1 (left, right)
    , intersect1 (right, left)
    )

const intersect1 = (left = {}, right = {}) =>
  Object.entries (left)
    .map
      ( ([ k, v ]) =>
          isObject (v) && isObject (right[k])
            ? [ k, intersect (v, right[k]) ]
            : v === right[k]
              ? [ k, v ]
              : [ k, {} ]
      )
    .filter
      ( ([ k, v ]) =>
          isObject (v)
            ? Object.keys (v) .length > 0
            : true
      )
    .reduce
      ( assign
      , isArray (left) && isArray (right) ? [] : {}
      )

console.log
  ( intersect
      ( { a: 1, b: 2, d: 4 }
      , { a: 1, c: 3, d: 5 }
      )
      // { a: 1 }

  , intersect
      ( [ 1, 2, 3, 4, 6, 7 ]
      , [ 1, 2, 3, 5, 6 ]
      )
      // [ 1, 2, 3, <1 empty item>, 6 ]

  , intersect
      ( [ { a: 1 }, { a: 2 }, { a: 4, b: 5 }, ]
      , [ { a: 1 }, { a: 3 }, { a: 4, b: 6 }, ]
      )
      // [ { a: 1 }, <1 empty item>, { a: 4 } ]

  , intersect
      ( { a: { b: { c: { d: [ 1, 2 ]    } } } }
      , { a: { b: { c: { d: [ 1, 2, 3 ] } } } }
      )
      // { a: { b: { c: { d: [ 1, 2 ] } } } }
  )

intersectAll

Above intersect only accepts two inputs and in your question you want to compute the intersect of 2+ objects. We implement intersectAll as follows -

const None =
  Symbol ()

const intersectAll = (x = None, ...xs) =>
  x === None
    ? {}
    : xs .reduce (intersect, x)

console.log
  ( intersectAll
      ( { a: 1, b: 2, c: { d: 3, e: 4 } }
      , { a: 1, b: 9, c: { d: 3, e: 4 } }
      , { a: 1, b: 2, c: { d: 3, e: 5 } }
      )
      // { a: 1, c: { d: 3 } }

  , intersectAll
      ( { a: 1 }
      , { b: 2 }
      , { c: 3 }
      )
      // {}

  , intersectAll
      ()
      // {}
  )

Verify the results in your browser –

const isObject = x =>
  Object (x) === x

const isArray =
  Array.isArray

const assign = (o, [ k, v ]) =>
  (o [k] = v, o)

const merge = (left = {}, right = {}) =>
  Object.entries (right)
    .map
      ( ([ k, v ]) =>
          isObject (v) && isObject (left [k])
            ? [ k, merge (left [k], v) ]
            : [ k, v ]
      )
    .reduce (assign, left)

const intersect = (left = {}, right = {}) =>
  merge
    ( intersect1 (left, right)
    , intersect1 (right, left)
    )

const intersect1 = (left = {}, right = {}) =>
  Object.entries (left)
    .map
      ( ([ k, v ]) =>
          isObject (v) && isObject (right[k])
            ? [ k, intersect (v, right[k]) ]
            : v === right[k]
              ? [ k, v ]
              : [ k, {} ]
      )
    .filter
      ( ([ k, v ]) =>
          isObject (v)
            ? Object.keys (v) .length > 0
            : true
      )
    .reduce
      ( assign
      , isArray (left) && isArray (right) ? [] : {}
      )

const None =
  Symbol ()

const intersectAll = (x = None, ...xs) =>
  x === None
    ? {}
    : xs .reduce (intersect, x)
    
console.log
  ( intersectAll
      ( { a: 1, b: 2, c: { d: 3, e: 4 } }
      , { a: 1, b: 9, c: { d: 3, e: 4 } }
      , { a: 1, b: 2, c: { d: 3, e: 5 } }
      )
      // { a: 1, c: { d: 3 } }

  , intersectAll
      ( { a: 1 }
      , { b: 2 }
      , { c: 3 }
      )
      // {}
      
  , intersectAll
      ()
      // {}
  )

remarks

You'll want to consider some things like –

intersect
  ( { a: someFunc, b: x => x * 2, c: /foo/, d: 1 }
  , { a: someFunc, b: x => x * 3, c: /foo/, d: 1 }
  )
  // { d: 1 }                          (actual)
  // { a: someFunc, c: /foo/, d: 1 }   (expected)

We're testing for what's considered equal here in intersect1

const intersect1 = (left = {}, right = {}) =>
  Object.entries (left)
    .map
      ( ([ k, v ]) =>
          isObject (v) && isObject (right[k])
            ? [ k, intersect (v, right[k]) ]
            : v === right[k] // <-- equality?
              ? [ k, v ]
              : [ k, {} ]
      )
    .filter
      ( ...

If we want to support things like checking for equality of Functions, RegExps, or other objects, this is where we would make the necessary modifications


recursive diff

In this related Q&A we compute the recursive diff of two objects

Mulan
  • 129,518
  • 31
  • 228
  • 259
  • Thank you for your answer @user633183! However would it be possible to modify unionAll to accept an array of objects instead of objects or arrays? In my situation the input is a dynamically generated array containing the objects. – JordanA29 Oct 25 '18 at 12:27
  • @JordanA29 sure, simply update `const unionAll = (x = None, ...xs) =>` to `const unionAll = ([ x = None, ...xs ]) =>` – Mulan Oct 25 '18 at 15:18