Key value pair intersection of an array of objects

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);

Answers:

Answer

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

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us Javascript

©2020 All rights reserved.