Рекурсивно сравнивать объекты и помещать повторяющиеся значения ключей в массив - PullRequest
0 голосов
/ 10 декабря 2018

У меня есть три объекта, которые нужно перебрать, чтобы проверить наличие дубликатов, чтобы в итоге получился один объект.Если есть дубликаты, я бы хотел поместить их в массив, чтобы пользователь мог выбрать лучшее значение, чтобы все они в конечном итоге оказались в виде строк или целых чисел (или объектов со строками / целыми числами).Они могут выглядеть так:

const a = {
  first_name: 'Tom',
  height: {
    feet: 5,
    inches: 0
  }
}
const b = {
  first_name: 'Thomas',
  last_name: 'Walsh',
  email: 'tomwalsh@domain.com',
  height: {
    feet: 6,
    inches: 0
  }
}
const c = {
  email: 'tomwalsh@sample.edu'
}

Результат будет выглядеть следующим образом:

const result = {
  first_name: ['Tom', 'Thomas'],
  last_name: 'Walsh',
  email: ['tomwalsh@domain.com', 'tomwalsh@sample.edu'],
  height: {
    feet: [5, 6],
    inches: 0
  }
}

Я понятия не имею, будет ли a, b или cполномочия на ключи, поэтому я должен принять неизвестный набор пар ключ / значение, но он определенно невелик (не более 20 пар, только 2-3 будут глубиной более 3-х уровней, и каждый из этих уровней будет максимальнымиз 4-5 пар ключ / значение.

Я могу создать новый объект, рекурсивно выполнить итерацию каждого из трех и вывести значение или создать массив значений, но это кажется неэффективным. Любые предложения?

У меня есть lodash в проекте, если нужно. Спасибо!

Ответы [ 6 ]

0 голосов
/ 21 января 2019

Как насчет 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 для вашего случая

0 голосов
/ 10 декабря 2018

Вот краткое рекурсивное решение через Array.reduce и Array.forEach:

const a = { first_name: 'Tom', height: { feet: 5, inches: 0 } },
      b = { first_name: 'Thomas', last_name: 'Walsh', email: 'tomwalsh@domain.com', height: { feet: 6, inches: 0 } },
      c = { email: 'tomwalsh@sample.edu'},
      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]))
0 голосов
/ 10 декабря 2018

Вы можете использовать рекурсивный подход, проверив значения и взяв вложенный объект для внутренних значений.

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: 'tomwalsh@domain.com', height: { feet: 6, inches: 0 } },
    c = { email: 'tomwalsh@sample.edu', height: { feet: 15 } },
    result = [a, b, c].reduce(merge, {});

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
0 голосов
/ 10 декабря 2018

Поскольку в некоторых объектах могут отсутствовать вложенные ключи, вы можете объединить их с помощью lodash _.mergeWith() и собрать дубликаты в массивы:

const a = {"first_name":"Tom","height":{"feet":5,"inches":0}}
const b = {"first_name":"Thomas","last_name":"Walsh","email":"tomwalsh@domain.com","height":{"feet":6,"inches":0}}
const c = {"email":"tomwalsh@sample.edu"}

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>

Если вы хотите удалить не повторяющиеся свойства, вы можете затем очистить объект с помощью рекурсивного _.transform():

const a = {"first_name":"Tom","height":{"feet":5,"inches":0}}
const b = {"first_name":"Thomas","last_name":"Walsh","email":"tomwalsh@domain.com","height":{"feet":6,"inches":0}}
const c = {"email":"tomwalsh@sample.edu"}

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>
0 голосов
/ 10 декабря 2018

Вам понадобится рекурсивная функция, которая погружается в свойства объекта в тандеме:

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: 'tomwalsh@domain.com', height: {feet: 6,inches: 0}};
const c = {email: 'tomwalsh@sample.edu'};

const result = mergeObjects([a,b, c]);
console.log(result);
0 голосов
/ 10 декабря 2018

Я думаю, что нет функции для этого, вы должны перебирать их свойства.

const a = { first_name: 'Tom', height: {feet: 5, inches: 0 } };
const b = { first_name: 'Thomas', last_name: 'Walsh', email:'tomwalsh@domain.com',  height: { feet: 6, inches: 0} };
const c = { email: 'tomwalsh@sample.edu' };

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