2

I have three objects I need to iterate to check for duplicates to end up with a single object. If there are duplicates, I'd like to put them into an array so the user can select the best value so they all ultimately end up as strings or integers (or objects with strings/integers). They might look like this:

const a = {
  first_name: 'Tom',
  height: {
    feet: 5,
    inches: 0
  }
}
const b = {
  first_name: 'Thomas',
  last_name: 'Walsh',
  email: '[email protected]',
  height: {
    feet: 6,
    inches: 0
  }
}
const c = {
  email: '[email protected]'
}

The result would look like this:

const result = {
  first_name: ['Tom', 'Thomas'],
  last_name: 'Walsh',
  email: ['[email protected]', '[email protected]'],
  height: {
    feet: [5, 6],
    inches: 0
  }
}

I have no idea if a, b, or c would be the authority on keys so I have to assume an unknown set of key/value pairs, but it's definitely small (no more than 20 pairs, only 2-3 would be more than 3 levels deep, and each of those levels would be a maximum of 4-5 key/value pairs.

I can create a new object, iterate each of the three recursively, and dump the value or create an array of values, but that seems inefficient. Any suggestions?

I've got lodash in the project if needed. Thanks!

6 Answers 6

1

Since there are nesting keys that might be missing in some of the objects, you can merge them via lodash's _.mergeWith(), and collect duplicates into arrays:

const a = {"first_name":"Tom","height":{"feet":5,"inches":0}}
const b = {"first_name":"Thomas","last_name":"Walsh","email":"[email protected]","height":{"feet":6,"inches":0}}
const c = {"email":"[email protected]"}

const shouldCollect = (s) => _.negate(_.overSome([
  _.isUndefined,
  _.isObject,
  _.partial(_.eq, s)
]))


const mergeDupes = (...args) => _.mergeWith({}, ...args, (o, s) => {
  if(_.isArray(o)) return _.uniq([...o, s]);
  if(shouldCollect(s)(o)) return [o, s];
})

const result = mergeDupes(a, b, c)

console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>

If you want to remove non duplicate properties, you can then clean the object via a recursive _.transform():

const a = {"first_name":"Tom","height":{"feet":5,"inches":0}}
const b = {"first_name":"Thomas","last_name":"Walsh","email":"[email protected]","height":{"feet":6,"inches":0}}
const c = {"email":"[email protected]"}

const shouldCollect = (s) => _.negate(_.overSome([
  _.isUndefined,
  _.isObject,
  _.partial(_.eq, s)
]))

const omitNonDuplicates = obj =>
  _.transform(obj, (a, v, k) => {
    if (_.isArray(v)) a[k] = v;
    else if (_.isObject(v)) {
      const clean = omitNonDuplicates(v);
      if(!_.isEmpty(clean)) a[k] = clean;
      return;
    }
  });
  
const mergeDupes = (...args) => omitNonDuplicates(_.mergeWith({}, ...args, (o, s) => {
  if(_.isArray(o)) return _.uniq([...o, s]);
  if(shouldCollect(s)(o)) return [o, s];
}))

const result = mergeDupes(a, b, c)

console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>

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

1 Comment

I have to accept this as the best answer. Not only does it solve my needs, it kinda blows my mind. Thanks!
0

You will need a recursive function that dives into the object properties in tandem:

function mergeObjects(objs) {
    objs = objs.filter(obj => obj !== undefined);
    // return value or array when at least one of the values is a primitive
    if (objs.some(obj => Object(obj) !== obj)) { 
        objs = Array.from(new Set(objs)); // Get unique values
        return objs.length === 1 ? objs[0] : objs;
    }
    // Get all the keys
    const keys = Array.from(new Set([].concat(...objs.map(obj => Object.keys(obj))))); 
    return Object.assign({}, ...keys.map(key => 
        ({ [key]: mergeObjects(objs.map(obj => obj[key])) })));
}

// Demo:
const a = {first_name: 'Tom', height: {feet: 5,inches: 0}};
const b = {first_name: 'Thomas',last_name: 'Walsh',email: '[email protected]', height: {feet: 6,inches: 0}};
const c = {email: '[email protected]'};

const result = mergeObjects([a,b, c]);
console.log(result);

Comments

0

I think there is not function to do that, you have to iterate over their properties.

const a = { first_name: 'Tom', height: {feet: 5, inches: 0 } };
const b = { first_name: 'Thomas', last_name: 'Walsh', email:'[email protected]',  height: { feet: 6, inches: 0} };
const c = { email: '[email protected]' };

let newObj = {};

[a, b, c].forEach(obj => {
    Object.entries(obj).forEach(([key, value]) => {
        if (newObj.hasOwnProperty(key)) { // if the property exist
            if (Array.isArray(newObj[key])) {
                newObj[key].push(value);
            } else {
                let temporalVal = newObj[key];
                newObj[key] = [temporalVal, value];
            }
        } else { // if the property does not exist, create it.
            newObj[key] = value;
        }
    })
});

console.log(newObj)

Comments

0

You could take a recursive approach by checking the values and take the nested object for inner values.

const
    merge = (a, b) => {
        Object.entries(b).forEach(([k, v]) => {
            if (v && typeof v === 'object') {
                return merge(a[k] = a[k] || {}, v);
            }
            if (!(k in a)) {
                return a[k] = v;
            }
            if ([].concat(a[k]).includes(v)) {
                return;
            }
            a[k] = [].concat(a[k], v); 
        });
        return a;
    },
    a = { first_name: 'Tom', height: { feet: 5, inches: 0 } },
    b = { first_name: 'Thomas', last_name: 'Walsh', email: '[email protected]', height: { feet: 6, inches: 0 } },
    c = { email: '[email protected]', height: { feet: 15 } },
    result = [a, b, c].reduce(merge, {});

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Comments

0

Here is a concise recursive solution via Array.reduce and Array.forEach:

const a = { first_name: 'Tom', height: { feet: 5, inches: 0 } },
      b = { first_name: 'Thomas', last_name: 'Walsh', email: '[email protected]', height: { feet: 6, inches: 0 } },
      c = { email: '[email protected]'},
      d = { first_name: 'Tomy', height: { feet: 15, inches: 10 } }

const mergeKeys = (a,b) => {
  if(a != undefined) {
    if(a === b) return a
    if(typeof b == 'object') return merge([a,b])      
    else return [...(Array.isArray(a) ? a : [a]), b]
  } else return b
}

const merge = arr => arr.reduce((r,c) => 
  (Object.entries(c).forEach(([k,v]) => r[k] = mergeKeys(r[k],c[k])), r) ,{})

console.log(merge([a,b,c,d]))

Comments

0

How about Lodash + Deepdash:

let merged = _.cloneDeep(objects.shift()); // clone to keep source untouched
objects.forEach((obj) => {
  _.eachDeep(obj, (value, key, parent, ctx) => {
    if (_.isObject(value)) return;
    let exists = _.get(merged, ctx.path);
    if (exists == undefined) {
      exists = value;
    } else {
      exists = _.uniq([].concat(exists, value));
      if (exists.length == 1) exists = exists[0];
    }
    _.set(merged, ctx.path, exists);
  });
});

full test for your case

Comments

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.