11cb0ef41Sopenharmony_ci/*
21cb0ef41Sopenharmony_ci  const obj1 = {a: 4, b: 5};
31cb0ef41Sopenharmony_ci  const obj2 = {a: 3, b: 5};
41cb0ef41Sopenharmony_ci  const obj3 = {a: 4, c: 5};
51cb0ef41Sopenharmony_ci
61cb0ef41Sopenharmony_ci  diff(obj1, obj2);
71cb0ef41Sopenharmony_ci  [
81cb0ef41Sopenharmony_ci    { "op": "replace", "path": ['a'], "value": 3 }
91cb0ef41Sopenharmony_ci  ]
101cb0ef41Sopenharmony_ci
111cb0ef41Sopenharmony_ci  diff(obj2, obj3);
121cb0ef41Sopenharmony_ci  [
131cb0ef41Sopenharmony_ci    { "op": "remove", "path": ['b'] },
141cb0ef41Sopenharmony_ci    { "op": "replace", "path": ['a'], "value": 4 }
151cb0ef41Sopenharmony_ci    { "op": "add", "path": ['c'], "value": 5 }
161cb0ef41Sopenharmony_ci  ]
171cb0ef41Sopenharmony_ci
181cb0ef41Sopenharmony_ci  // using converter to generate jsPatch standard paths
191cb0ef41Sopenharmony_ci  // see http://jsonpatch.com
201cb0ef41Sopenharmony_ci  import {diff, jsonPatchPathConverter} from 'just-diff'
211cb0ef41Sopenharmony_ci  diff(obj1, obj2, jsonPatchPathConverter);
221cb0ef41Sopenharmony_ci  [
231cb0ef41Sopenharmony_ci    { "op": "replace", "path": '/a', "value": 3 }
241cb0ef41Sopenharmony_ci  ]
251cb0ef41Sopenharmony_ci
261cb0ef41Sopenharmony_ci  diff(obj2, obj3, jsonPatchPathConverter);
271cb0ef41Sopenharmony_ci  [
281cb0ef41Sopenharmony_ci    { "op": "remove", "path": '/b' },
291cb0ef41Sopenharmony_ci    { "op": "replace", "path": '/a', "value": 4 }
301cb0ef41Sopenharmony_ci    { "op": "add", "path": '/c', "value": 5 }
311cb0ef41Sopenharmony_ci  ]
321cb0ef41Sopenharmony_ci
331cb0ef41Sopenharmony_ci  // arrays
341cb0ef41Sopenharmony_ci  const obj4 = {a: 4, b: [1, 2, 3]};
351cb0ef41Sopenharmony_ci  const obj5 = {a: 3, b: [1, 2, 4]};
361cb0ef41Sopenharmony_ci  const obj6 = {a: 3, b: [1, 2, 4, 5]};
371cb0ef41Sopenharmony_ci
381cb0ef41Sopenharmony_ci  diff(obj4, obj5);
391cb0ef41Sopenharmony_ci  [
401cb0ef41Sopenharmony_ci    { "op": "replace", "path": ['a'], "value": 3 }
411cb0ef41Sopenharmony_ci    { "op": "replace", "path": ['b', 2], "value": 4 }
421cb0ef41Sopenharmony_ci  ]
431cb0ef41Sopenharmony_ci
441cb0ef41Sopenharmony_ci  diff(obj5, obj6);
451cb0ef41Sopenharmony_ci  [
461cb0ef41Sopenharmony_ci    { "op": "add", "path": ['b', 3], "value": 5 }
471cb0ef41Sopenharmony_ci  ]
481cb0ef41Sopenharmony_ci
491cb0ef41Sopenharmony_ci  // nested paths
501cb0ef41Sopenharmony_ci  const obj7 = {a: 4, b: {c: 3}};
511cb0ef41Sopenharmony_ci  const obj8 = {a: 4, b: {c: 4}};
521cb0ef41Sopenharmony_ci  const obj9 = {a: 5, b: {d: 4}};
531cb0ef41Sopenharmony_ci
541cb0ef41Sopenharmony_ci  diff(obj7, obj8);
551cb0ef41Sopenharmony_ci  [
561cb0ef41Sopenharmony_ci    { "op": "replace", "path": ['b', 'c'], "value": 4 }
571cb0ef41Sopenharmony_ci  ]
581cb0ef41Sopenharmony_ci
591cb0ef41Sopenharmony_ci  diff(obj8, obj9);
601cb0ef41Sopenharmony_ci  [
611cb0ef41Sopenharmony_ci    { "op": "replace", "path": ['a'], "value": 5 }
621cb0ef41Sopenharmony_ci    { "op": "remove", "path": ['b', 'c']}
631cb0ef41Sopenharmony_ci    { "op": "add", "path": ['b', 'd'], "value": 4 }
641cb0ef41Sopenharmony_ci  ]
651cb0ef41Sopenharmony_ci*/
661cb0ef41Sopenharmony_ci
671cb0ef41Sopenharmony_cifunction diff(obj1, obj2, pathConverter) {
681cb0ef41Sopenharmony_ci  if (!obj1 || typeof obj1 != 'object' || !obj2 || typeof obj2 != 'object') {
691cb0ef41Sopenharmony_ci    throw new Error('both arguments must be objects or arrays');
701cb0ef41Sopenharmony_ci  }
711cb0ef41Sopenharmony_ci
721cb0ef41Sopenharmony_ci  pathConverter ||
731cb0ef41Sopenharmony_ci    (pathConverter = function(arr) {
741cb0ef41Sopenharmony_ci      return arr;
751cb0ef41Sopenharmony_ci    });
761cb0ef41Sopenharmony_ci
771cb0ef41Sopenharmony_ci  function getDiff({obj1, obj2, basePath, basePathForRemoves, diffs}) {
781cb0ef41Sopenharmony_ci    var obj1Keys = Object.keys(obj1);
791cb0ef41Sopenharmony_ci    var obj1KeysLength = obj1Keys.length;
801cb0ef41Sopenharmony_ci    var obj2Keys = Object.keys(obj2);
811cb0ef41Sopenharmony_ci    var obj2KeysLength = obj2Keys.length;
821cb0ef41Sopenharmony_ci    var path;
831cb0ef41Sopenharmony_ci
841cb0ef41Sopenharmony_ci    var lengthDelta = obj1.length - obj2.length;
851cb0ef41Sopenharmony_ci
861cb0ef41Sopenharmony_ci    if (trimFromRight(obj1, obj2)) {
871cb0ef41Sopenharmony_ci      for (var i = 0; i < obj1KeysLength; i++) {
881cb0ef41Sopenharmony_ci        var key = Array.isArray(obj1) ? Number(obj1Keys[i]) : obj1Keys[i];
891cb0ef41Sopenharmony_ci        if (!(key in obj2)) {
901cb0ef41Sopenharmony_ci          path = basePathForRemoves.concat(key);
911cb0ef41Sopenharmony_ci          diffs.remove.push({
921cb0ef41Sopenharmony_ci            op: 'remove',
931cb0ef41Sopenharmony_ci            path: pathConverter(path),
941cb0ef41Sopenharmony_ci          });
951cb0ef41Sopenharmony_ci        }
961cb0ef41Sopenharmony_ci      }
971cb0ef41Sopenharmony_ci
981cb0ef41Sopenharmony_ci      for (var i = 0; i < obj2KeysLength; i++) {
991cb0ef41Sopenharmony_ci        var key = Array.isArray(obj2) ? Number(obj2Keys[i]) : obj2Keys[i];
1001cb0ef41Sopenharmony_ci        pushReplaces({
1011cb0ef41Sopenharmony_ci          key,
1021cb0ef41Sopenharmony_ci          obj1,
1031cb0ef41Sopenharmony_ci          obj2,
1041cb0ef41Sopenharmony_ci          path: basePath.concat(key),
1051cb0ef41Sopenharmony_ci          pathForRemoves: basePath.concat(key),
1061cb0ef41Sopenharmony_ci          diffs,
1071cb0ef41Sopenharmony_ci        });
1081cb0ef41Sopenharmony_ci      }
1091cb0ef41Sopenharmony_ci    } else {
1101cb0ef41Sopenharmony_ci      // trim from left, objects are both arrays
1111cb0ef41Sopenharmony_ci      for (var i = 0; i < lengthDelta; i++) {
1121cb0ef41Sopenharmony_ci        path = basePathForRemoves.concat(i);
1131cb0ef41Sopenharmony_ci        diffs.remove.push({
1141cb0ef41Sopenharmony_ci          op: 'remove',
1151cb0ef41Sopenharmony_ci          path: pathConverter(path),
1161cb0ef41Sopenharmony_ci        });
1171cb0ef41Sopenharmony_ci      }
1181cb0ef41Sopenharmony_ci
1191cb0ef41Sopenharmony_ci      // now make a copy of obj1 with excess elements left trimmed and see if there any replaces
1201cb0ef41Sopenharmony_ci      var obj1Trimmed = obj1.slice(lengthDelta);;
1211cb0ef41Sopenharmony_ci      for (var i = 0; i < obj2KeysLength; i++) {
1221cb0ef41Sopenharmony_ci        pushReplaces({
1231cb0ef41Sopenharmony_ci          key: i,
1241cb0ef41Sopenharmony_ci          obj1: obj1Trimmed,
1251cb0ef41Sopenharmony_ci          obj2,
1261cb0ef41Sopenharmony_ci          path: basePath.concat(i),
1271cb0ef41Sopenharmony_ci          // since list of removes are reversed before presenting result,
1281cb0ef41Sopenharmony_ci          // we need to ignore existing parent removes when doing nested removes
1291cb0ef41Sopenharmony_ci          pathForRemoves: basePath.concat(i + lengthDelta),
1301cb0ef41Sopenharmony_ci          diffs,
1311cb0ef41Sopenharmony_ci        });
1321cb0ef41Sopenharmony_ci      }
1331cb0ef41Sopenharmony_ci    }
1341cb0ef41Sopenharmony_ci  }
1351cb0ef41Sopenharmony_ci
1361cb0ef41Sopenharmony_ci  var diffs = {remove: [], replace: [], add: []};
1371cb0ef41Sopenharmony_ci  getDiff({
1381cb0ef41Sopenharmony_ci    obj1,
1391cb0ef41Sopenharmony_ci    obj2,
1401cb0ef41Sopenharmony_ci    basePath: [],
1411cb0ef41Sopenharmony_ci    basePathForRemoves: [],
1421cb0ef41Sopenharmony_ci    diffs,
1431cb0ef41Sopenharmony_ci  });
1441cb0ef41Sopenharmony_ci
1451cb0ef41Sopenharmony_ci  // reverse removes since we want to maintain indexes
1461cb0ef41Sopenharmony_ci  return diffs.remove
1471cb0ef41Sopenharmony_ci    .reverse()
1481cb0ef41Sopenharmony_ci    .concat(diffs.replace)
1491cb0ef41Sopenharmony_ci    .concat(diffs.add);
1501cb0ef41Sopenharmony_ci
1511cb0ef41Sopenharmony_ci  function pushReplaces({key, obj1, obj2, path, pathForRemoves, diffs}) {
1521cb0ef41Sopenharmony_ci    var obj1AtKey = obj1[key];
1531cb0ef41Sopenharmony_ci    var obj2AtKey = obj2[key];
1541cb0ef41Sopenharmony_ci
1551cb0ef41Sopenharmony_ci    if(!(key in obj1) && (key in obj2)) {
1561cb0ef41Sopenharmony_ci      var obj2Value = obj2AtKey;
1571cb0ef41Sopenharmony_ci      diffs.add.push({
1581cb0ef41Sopenharmony_ci        op: 'add',
1591cb0ef41Sopenharmony_ci        path: pathConverter(path),
1601cb0ef41Sopenharmony_ci        value: obj2Value,
1611cb0ef41Sopenharmony_ci      });
1621cb0ef41Sopenharmony_ci    } else if(obj1AtKey !== obj2AtKey) {
1631cb0ef41Sopenharmony_ci      if(Object(obj1AtKey) !== obj1AtKey ||
1641cb0ef41Sopenharmony_ci        Object(obj2AtKey) !== obj2AtKey || differentTypes(obj1AtKey, obj2AtKey)
1651cb0ef41Sopenharmony_ci      ) {
1661cb0ef41Sopenharmony_ci        pushReplace(path, diffs, obj2AtKey);
1671cb0ef41Sopenharmony_ci      } else {
1681cb0ef41Sopenharmony_ci        if(!Object.keys(obj1AtKey).length &&
1691cb0ef41Sopenharmony_ci          !Object.keys(obj2AtKey).length &&
1701cb0ef41Sopenharmony_ci          String(obj1AtKey) != String(obj2AtKey)) {
1711cb0ef41Sopenharmony_ci          pushReplace(path, diffs, obj2AtKey);
1721cb0ef41Sopenharmony_ci        } else {
1731cb0ef41Sopenharmony_ci          getDiff({
1741cb0ef41Sopenharmony_ci            obj1: obj1[key],
1751cb0ef41Sopenharmony_ci            obj2: obj2[key],
1761cb0ef41Sopenharmony_ci            basePath: path,
1771cb0ef41Sopenharmony_ci            basePathForRemoves: pathForRemoves,
1781cb0ef41Sopenharmony_ci            diffs});
1791cb0ef41Sopenharmony_ci        }
1801cb0ef41Sopenharmony_ci      }
1811cb0ef41Sopenharmony_ci    }
1821cb0ef41Sopenharmony_ci  }
1831cb0ef41Sopenharmony_ci
1841cb0ef41Sopenharmony_ci  function pushReplace(path, diffs, newValue) {
1851cb0ef41Sopenharmony_ci    diffs.replace.push({
1861cb0ef41Sopenharmony_ci      op: 'replace',
1871cb0ef41Sopenharmony_ci      path: pathConverter(path),
1881cb0ef41Sopenharmony_ci      value: newValue,
1891cb0ef41Sopenharmony_ci    });
1901cb0ef41Sopenharmony_ci  }
1911cb0ef41Sopenharmony_ci}
1921cb0ef41Sopenharmony_ci
1931cb0ef41Sopenharmony_cifunction jsonPatchPathConverter(arrayPath) {
1941cb0ef41Sopenharmony_ci  return [''].concat(arrayPath).join('/');
1951cb0ef41Sopenharmony_ci}
1961cb0ef41Sopenharmony_ci
1971cb0ef41Sopenharmony_cifunction differentTypes(a, b) {
1981cb0ef41Sopenharmony_ci  return Object.prototype.toString.call(a) != Object.prototype.toString.call(b);
1991cb0ef41Sopenharmony_ci}
2001cb0ef41Sopenharmony_ci
2011cb0ef41Sopenharmony_cifunction trimFromRight(obj1, obj2) {
2021cb0ef41Sopenharmony_ci  var lengthDelta = obj1.length - obj2.length;
2031cb0ef41Sopenharmony_ci  if (Array.isArray(obj1) && Array.isArray(obj2) && lengthDelta > 0) {
2041cb0ef41Sopenharmony_ci    var leftMatches = 0;
2051cb0ef41Sopenharmony_ci    var rightMatches = 0;
2061cb0ef41Sopenharmony_ci    for (var i = 0; i < obj2.length; i++) {
2071cb0ef41Sopenharmony_ci      if (String(obj1[i]) === String(obj2[i])) {
2081cb0ef41Sopenharmony_ci        leftMatches++;
2091cb0ef41Sopenharmony_ci      } else {
2101cb0ef41Sopenharmony_ci        break;
2111cb0ef41Sopenharmony_ci      }
2121cb0ef41Sopenharmony_ci    }
2131cb0ef41Sopenharmony_ci    for (var j = obj2.length; j > 0; j--) {
2141cb0ef41Sopenharmony_ci      if (String(obj1[j + lengthDelta]) === String(obj2[j])) {
2151cb0ef41Sopenharmony_ci        rightMatches++;
2161cb0ef41Sopenharmony_ci      } else {
2171cb0ef41Sopenharmony_ci        break;
2181cb0ef41Sopenharmony_ci      }
2191cb0ef41Sopenharmony_ci    }
2201cb0ef41Sopenharmony_ci
2211cb0ef41Sopenharmony_ci    // bias to trim right becase it requires less index shifting
2221cb0ef41Sopenharmony_ci    return leftMatches >= rightMatches;
2231cb0ef41Sopenharmony_ci  }
2241cb0ef41Sopenharmony_ci  return true;
2251cb0ef41Sopenharmony_ci}
2261cb0ef41Sopenharmony_ci
2271cb0ef41Sopenharmony_ciexport {diff, jsonPatchPathConverter};
228