Сравните два вложенных массива и объекты, чтобы найти разницу - PullRequest
0 голосов
/ 07 ноября 2019

У меня есть два JSON-данных с многоступенчатым вложением. Я хочу сравнить его по следующим условиям: 1) если name в первом объекте равно name во втором объекте, сравните их массивы prop, иначе, если ничто не совпадает с именами в двух объектах, вернет пустой массив;2) сравнить объекты в два массива реквизитов и найти разницу;3) вернуть новый объект с отличием от первого и второго массивов.

const p1 = [{
  name: 'B [1]', // name equals and prop differnce, then comparing it
  prop: [{ 
    A: { A: 1, B: 2 }, 
    B: { A: 1, B: 2 }, 
    C: { C: 78, D: 4, T: 7, } }],
  }, {
  name: 'B [2]', // name equals, then skiping it
  prop: [{ 
    A: { A: 1, B: 2 }, 
    B: { A: 1, B: 2 }, 
    D: { C: 3,  D: 4, Y: 13 } }],
  }, {
  name: 'B [3]', // name equals and prop differnce, then comparing it
  prop: [{ 
    E: { A: 1, B: 2 }, 
    R: { A: 1, B: 2 }, 
    T: { C: 3,  D: 4, } }],
  }, {
  name: 'B [4]', // name and prop equals, then skiping it 
  prop: [{ 
    A: { A: 1, B: 2 }, 
    S: { A: 1, B: 2 }, 
    D: { C: 3,  D: 4, } }],
}]

const p2 = [{
  name: 'B [1]', // name equals and prop differnce, then comparing it 
  prop: [{ 
    A: { A: 1, B: 8 }, 
    B: { A: 1, B: 2 }, 
    C: { C: 3, T: 7, O: 9 } }],
  }, {
  name: 'B [6]', // name not equals, then skiping it
  prop: [{ 
    A: { A: 1, B: 2 }, 
    B: { A: 1, B: 2 }, 
    D: { C: 3,  D: 4 } }],
  }, {
  name: 'B [3]', // name equals and prop differnce, then comparing it
  prop: [{ 
    E: { A: 1, B: 2 }, 
    R: { A: 1, B: 2, U: 150 }, 
    T: { C: 3,  D: 4, } }],
  }, {
  name: 'B [4]', // name and prop equals, then skiping it 
  prop: [{ 
    A: { A: 1, B: 2 }, 
    S: { A: 1, B: 2 }, 
    D: { C: 3,  D: 4, } }],
}]

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

const result = [{
  name: 'B [1]',
  propOne: [{
    A: { B: 2 },
    C: { C: 78, D: 4, O: 'Missing' }
  }],
  propTwo: [{
    A: { B: 8 },
    C: { C: 3, D: 'Missing', O: 9 }
  }],
  },{
  name: 'B [3]',
  propOne: [{
    R: { U: 'Missing' }
    }],
  propTwo: [{
    R: { U: 150 }
    }]
}]

Я также горько прикрепляю свой бесполезный код, который ничего не делает.

const compare = (p1, p2) => {
  return p1.reduce((acc, curr) => {
    p2.reduce((acc2, curr2) => {
      if (curr.name === curr2.name) {
        const keys1 = R.fromPairs(Object.keys(curr.prop[0]).map(x => ([x, curr.prop[0][x]])));
        const keys2 = R.fromPairs(Object.keys(curr2.prop[0]).map(x => ([x, curr2.prop[0][x]])));
      }
      return acc;
    }, [])

    return acc;
  }, [])
}

Буду очень признателен за любую помощь и совет.

Ответы [ 2 ]

2 голосов
/ 08 ноября 2019

Вся сложность заключается в определении ожидаемого поведения функции сравнения:

для двух объектов (которые я называю значениями) a и b: {A:1,B:2} и {A:1,B:3,C:4} выходных данныхиз cmp(a,b) должно быть:

  foreach key of a:
    if a[key] != b[key] (or b does not have k prop)
      diff[key] = a[key]
    else (value is equal, no diff)
  foreach key of b not in a
    diff[key] = Missing

отсюда (например) {B:2, C:'Missing'}

при сравнении значений, если diff пуст, вы можете пропустить текущую подпорку, а при сравнении подпорок, еслиразница пуста пропустить запись (как если бы имена были разными)

function cmp(x,y){
  let a = x.prop[0];
  let b = y.prop[0];
  return Object.keys(a).reduce((o,k)=>{
    //compare the right value (such as { A: 1, B: 2 }). assumes primitive types
    let u = a[k];
    let v = b[k];
    
    let diff = Object.keys(u).reduce((o,k)=>{
      return u[k]==v[k]?o:(o[k] = u[k],o)
    },{})
    
    Object.keys(v).reduce((o,k)=>{
      return u.hasOwnProperty(k)?o:(o[k]='Missing',o);
    }, diff);
    
    if(Object.keys(diff).length){
      o[k] = diff;
    }
    
    return o;
  },{});
}
function diff(p1,p2){
  return p1.flatMap((o,i)=>{
    if(p2[i].name != p1[i].name){
      return []
    }

    let a = p1[i];
    let b = p2[i];
    let res = cmp(a,b);
    
    if(!Object.keys(res).length){
      return [];
    }
    
    return {name: a.name, propOne:res, propTwo:cmp(b,a)}
  })
};
const p1 = [{
  name: 'B [1]', // name equals and prop differnce, then comparing it
  prop: [{ 
    A: { A: 1, B: 2 }, 
    B: { A: 1, B: 2 }, 
    C: { C: 78, D: 4, T: 7, } }],
  }, {
  name: 'B [2]', // name equals, then skiping it
  prop: [{ 
    A: { A: 1, B: 2 }, 
    B: { A: 1, B: 2 }, 
    D: { C: 3,  D: 4, Y: 13 } }],
  }, {
  name: 'B [3]', // name equals and prop differnce, then comparing it
  prop: [{ 
    E: { A: 1, B: 2 }, 
    R: { A: 1, B: 2 }, 
    T: { C: 3,  D: 4, } }],
  }, {
  name: 'B [4]', // name and prop equals, then skiping it 
  prop: [{ 
    A: { A: 1, B: 2 }, 
    S: { A: 1, B: 2 }, 
    D: { C: 3,  D: 4, } }],
}]

const p2 = [{
  name: 'B [1]', // name equals and prop differnce, then comparing it 
  prop: [{ 
    A: { A: 1, B: 8 }, 
    B: { A: 1, B: 2 }, 
    C: { C: 3, T: 7, O: 9 } }],
  }, {
  name: 'B [6]', // name not equals, then skiping it
  prop: [{ 
    A: { A: 1, B: 2 }, 
    B: { A: 1, B: 2 }, 
    D: { C: 3,  D: 4 } }],
  }, {
  name: 'B [3]', // name equals and prop differnce, then comparing it
  prop: [{ 
    E: { A: 1, B: 2 }, 
    R: { A: 1, B: 2, U: 150 }, 
    T: { C: 3,  D: 4, } }],
  }, {
  name: 'B [4]', // name and prop equals, then skiping it 
  prop: [{ 
    A: { A: 1, B: 2 }, 
    S: { A: 1, B: 2 }, 
    D: { C: 3,  D: 4, } }],
}];

console.log('result', JSON.stringify(diff(p1,p2),null,2))
1 голос
/ 08 ноября 2019

Уже довольно поздно, и у меня больше нет времени на обширные тесты, так что могут возникнуть ошибки в неожиданных случаях (хотя, надеюсь, нет). Это слишком долго для комментария, и отбрасывать его было бы немного напрасно.

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

Обратите внимание, что в ваших структурах данных есть несколько "массивов из одного элемента". Если они могут содержать более одного элемента (для меня они не имеют особого смысла, это уже объекты внутри массивов внутри массивов, дающие достаточно места для дополнительных подпорок), необходимо сделать один или два дополнительных шага map,не большая проблема, хотя.

const p1 = [{
  name: 'B [1]', // name equals and prop differnce, then comparing it
  prop: [{
    A: { A: 1, B: 2 },
    B: { A: 1, B: 2 },
    C: { C: 78, D: 4, T: 7, }
  }],
}, {
  name: 'B [2]', // name equals, then skiping it
  prop: [{
    A: { A: 1, B: 2 },
    B: { A: 1, B: 2 },
    D: { C: 3, D: 4, Y: 13 }
  }],
}, {
  name: 'B [3]', // name equals and prop differnce, then comparing it
  prop: [{
    E: { A: 1, B: 2 },
    R: { A: 1, B: 2 },
    T: { C: 3, D: 4, }
  }],
}, {
  name: 'B [4]', // name and prop equals, then skiping it 
  prop: [{
    A: { A: 1, B: 2 },
    S: { A: 1, B: 2 },
    D: { C: 3, D: 4, }
  }],
}];

const p2 = [{
  name: 'B [1]', // name equals and prop differnce, then comparing it 
  prop: [{
    A: { A: 1, B: 8 },
    B: { A: 1, B: 2 },
    C: { C: 3, T: 7, O: 9 }
  }],
}, {
  name: 'B [6]', // name not equals, then skiping it
  prop: [{
    A: { A: 1, B: 2 },
    B: { A: 1, B: 2 },
    D: { C: 3, D: 4 }
  }],
}, {
  name: 'B [3]', // name equals and prop differnce, then comparing it
  prop: [{
    E: { A: 1, B: 2 },
    R: { A: 1, B: 2, U: 150 },
    T: { C: 3, D: 4, }
  }],
}, {
  name: 'B [4]', // name and prop equals, then skiping it 
  prop: [{
    A: { A: 1, B: 2 },
    S: { A: 1, B: 2 },
    D: { C: 3, D: 4, }
  }],
}];

const result = [{
  name: 'B [1]',
  propOne: [{
    A: { B: 2 },
    C: { C: 78, D: 4, O: 'Missing' }
  }],
  propTwo: [{
    A: { B: 8 },
    C: { C: 3, D: 'Missing', O: 9 }
  }],
  },{
  name: 'B [3]',
  propOne: [{
    R: { U: 'Missing' }
    }],
  propTwo: [{
    R: { U: 150 }
    }]
}]

const diffDestructive = (a, b) => {
  /**
   * Copy the objects, remove all identical properties recursively,
   * then add "Missing" properties for all properties from either side
   * that doesn't exist on the other.
   */

  const remove = (x, y) => {
    for (let key of Object.keys(x)) {
      // hasOwnProperty is only for the degenerate case { prop: undefined }
      if (x[key] === y[key] && y.hasOwnProperty(key)) {
        delete x[key];
        delete y[key];
      }     // typeof null === "object", therefore an additional check is needed
      else if (x[key] && typeof x[key] === "object" && y[key] && typeof y[key] === "object") {
        remove(x[key], y[key]);
        if ([x, y].every(e => Object.keys(e[key]).length === 0)) {
          delete x[key];
          delete y[key];
        }
      }
    }
  };

  const addMissingNotes = (x, y) => {
    for (let key of Object.keys(x)) {
      if (!(y.hasOwnProperty(key))) y[key] = "Missing";
      else if (x[key] && typeof x[key] === "object" && y[key] && typeof y[key] === "object")
        addMissingNotes(x[key], y[key]);
    }
  };

  // quick and dirty object deep-copy
  let [modA, modB] = [a, b].map(e => JSON.parse(JSON.stringify(e)));

  remove(modA, modB);
  addMissingNotes(modA, modB);
  addMissingNotes(modB, modA);

  return [modA, modB];
};

const diffConstructive = (a, b) => {
  /**
   * Add differing properties to the result step by step.
   * Nested objects are handled recursively.
   */

  let diffA = {}, diffB = {};

  for (let key of Object.keys(a)) {
    //properties that a and b share
    if (b.hasOwnProperty(key)) {
      if (a[key] && typeof a[key] === "object" && b[key] && typeof b[key] === "object") {
        let subDiffs = diffConstructive(a[key], b[key]);
        // The way the construction works, Object.keys(subDiffs[0]).length !== 0 would be enough.
        if (subDiffs.some(e => Object.keys(e).length !== 0)) {
          [diffA[key], diffB[key]] = subDiffs;
        }
      } else if (a[key] !== b[key]) {
        diffA[key] = a[key];
        diffB[key] = b[key];
      }
    } // properties that a has but b doesn't
    else {
      diffA[key] = a[key];
      diffB[key] = "Missing";
    }
  }

  // properties that b has but a doesn't
  for (let key of Object.keys(b)) {
    if (!a.hasOwnProperty(key)) {
      diffB[key] = b[key];
      diffA[key] = "Missing";
    }
  }

  return [diffA, diffB];
};

const compare = (a, b, method) => a
  .map((e, i) => [e, b[i]])
  //same name only
  .filter(([a, b]) => a.name === b.name)
  // formatting
  .map(([a, b]) => {
    const [diffA, diffB] = method(a.prop[0], b.prop[0]);
    return {
      name: a.name,
      propOne: [diffA],
      propTwo: [diffB]
    };
  })
  // There must be a difference
  .filter(e => [e.propOne[0], e.propTwo[0]].some(e => Object.keys(e).length !== 0));

const destructive = compare(p1, p2, diffDestructive);
const constructive = compare(p1, p2, diffConstructive);

console.log(`Constructive method gives the wanted result: ${_.isEqual(result, destructive)}`);
console.log(`Destructive method gives the wanted result: ${_.isEqual(result, constructive)}`);
<!--
this is only for a deepequals function, _.isEqual,
and only used for checking the results. I could have copied
one into the code, but why make this even longer...
-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.core.min.js"></script>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...