JSONDiff

Benchmark created by noneven on


Preparation HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore-min.js"></script>
<script src="https://unpkg.com/immer@1.7.2/dist/immer.umd.js"></script>

Setup

var objA = {"a":0.282,"z":{"a":{"a":"string 0.485","z":"string 0.926","c":"2014-12-17T16:35:40.641Z","d":4864,"e":"string 0.476","f":"string 0.881","g":1424,"h":"2006-11-24T13:56:33.666Z","i":0.486,"j":0.77},"z":{"a":829,"z":0.725}},"c":{"a":{"a":0.0621,"z":"string 0.512","c":0.154,"d":"1978-06-21T17:39:46.527Z","e":0.507,"f":null,"g":"1984-02-04T18:08:54.747Z","h":6444,"i":5151,"j":0.911,"k":"string 0.396","l":"string 0.345","m":8456,"b":0.814,"o":0.806,"p":0.33,"q":"1991-07-02T03:27:55.140Z","r":"1972-01-31T15:41:06.854Z","s":"1975-03-24T22:09:41.932Z","t":"string 0.774","u":"2013-08-20T08:41:23.297Z","v":7374,"w":null,"x":null,"y":0.93},"z":"string 0.180","c":"1973-01-26T19:55:14.208Z","d":"string 0.940","e":0.734,"f":0.569,"g":{"a":0.565,"z":0.114,"c":6911,"d":"string 0.0894","e":0.9,"f":8149,"g":"string 0.777","h":5352,"i":0.0555,"j":3497,"k":8192,"l":7546,"m":"1972-03-29T21:43:52.317Z","b":"1980-08-17T06:41:23.247Z","o":4047},"h":{"a":"2003-09-03T11:19:09.234Z","z":7333,"c":0.686,"d":2299,"e":2725,"f":9354,"g":0.249,"h":"string 0.385","i":3316,"j":null,"k":"2003-03-19T06:40:54.047Z","l":4642,"m":3240,"b":"2005-08-29T01:41:37.533Z","o":2718,"p":null,"q":"string 0.666","r":0.742,"s":"string 0.806","t":3896,"u":"1976-10-08T20:09:01.796Z","v":0.271,"w":5055,"x":2309,"y":null},"i":0.98,"j":{"a":0.226,"z":0.000491,"c":null},"k":"string 0.405","l":1816,"m":"1994-03-01T22:00:50.119Z"},"d":{"a":null,"z":"1985-09-06T03:06:54.808Z","c":64,"d":{"a":0.531,"z":0.377,"c":4587,"d":0.856,"e":0.539,"f":null,"g":"1984-07-06T19:32:18.650Z"},"e":"2009-04-27T14:54:04.938Z","f":"1999-07-29T04:24:42.978Z","g":7506,"h":{"a":"1982-05-22T04:38:05.190Z","z":8755,"c":"1979-06-02T19:59:20.129Z","d":0.0384,"e":8975,"f":2354,"g":4375,"h":8017,"i":5169,"j":0.465,"k":"string 0.663","l":0.467,"m":"string 0.0202","b":"string 0.436","o":7865,"p":9629,"q":2752,"r":null,"s":"1979-03-06T10:32:29.609Z","t":null},"i":2881,"j":"1970-09-20T18:03:24.404Z","k":"2010-05-31T10:09:26.766Z","l":"string 0.497","m":"1996-05-31T19:15:31.919Z","b":0.235,"o":"string 0.435","p":2774,"q":null,"r":{},"s":7788,"t":0.64},"e":{"a":"1974-08-17T04:54:55.936Z","z":9950,"c":0.834,"d":null,"e":1560,"f":{"a":5615,"z":"string 0.118","c":"2004-03-26T18:06:55.340Z","d":"string 0.226","e":4641,"f":0.313,"g":6474,"h":"2004-06-25T16:39:24.453Z","i":"2001-09-01T11:59:12.854Z","j":"1981-11-20T15:15:28.548Z","k":6240,"l":9246,"m":"1980-09-07T06:33:27.022Z","b":"string 0.177","o":"2010-04-21T18:46:52.602Z","p":"string 0.146","q":0.582},"g":{"a":"1995-02-26T16:26:01.082Z","z":"string 0.631","c":null,"d":"string 0.604","e":0.187,"f":null,"g":"1997-03-26T13:58:36.099Z","h":4705,"i":"string 0.846","j":"2006-09-15T20:28:54.014Z","k":null,"l":0.189},"h":4345,"i":"2006-10-22T20:50:47.528Z","j":"1977-02-27T01:39:23.588Z","k":{"a":8883,"z":0.149,"c":0.985},"l":0.679,"m":0.454,"b":8707},"f":null,"g":"string 0.753","h":0.912,"i":{"a":{"a":"string 0.0138","z":"1989-05-11T05:19:21.223Z","c":null,"d":"string 0.219","e":0.905,"f":1020,"g":7579,"h":2220,"i":9706,"j":"string 0.0511","k":"2007-03-05T17:31:56.981Z","l":"string 0.137"},"z":{"a":null,"z":"1983-11-14T18:01:13.237Z","c":6719,"d":0.972},"c":2650,"d":"string 0.167","e":0.825,"f":{"a":6305,"z":7918,"c":3888,"d":"1991-11-20T21:38:07.258Z","e":8280,"f":8533,"g":null,"h":"string 0.906","i":null,"j":"2004-01-08T02:22:04.633Z","k":0.386,"l":"string 0.108","m":0.253,"b":"1974-06-22T10:52:07.627Z","o":0.353,"p":6243,"q":6584},"g":0.542,"h":8161,"i":"2010-10-05T19:33:07.930Z","j":{"a":8592,"z":"string 0.699","c":6818},"k":0.359,"l":"2000-07-27T07:52:37.702Z","m":{"a":"2005-08-09T18:35:32.950Z","z":0.0427,"c":4048,"d":0.525,"e":0.319,"f":"string 0.917","g":"string 0.990","h":446,"i":null,"j":null},"b":{"a":5924,"z":0.00489,"c":"2014-05-12T01:40:46.595Z","d":"1987-10-12T06:50:26.836Z","e":"string 0.455","f":3730,"g":8717,"h":null,"i":"2010-02-24T03:47:10.056Z","j":"string 0.720","k":"string 0.112","l":211},"o":"2000-09-03T13:19:43.114Z","p":"1989-12-03T09:32:10.660Z"},"j":3672,"k":8634};
    var objB = {"a":0.282,"z":{"a":{"a":"string 0.485","z":"string 0.926","c":"2014-12-17T16:35:40.641Z","d":4864,"e":"string 0.476","f":"string 0.881","g":1424,"h":"2006-11-24T13:56:33.666Z","i":0.486,"j":0.77},"z":{"a":829,"z":0.725}},"c":{"a":{"a":0.0621,"z":"string 0.512","c":0.154,"d":"1978-06-21T17:39:46.527Z","e":0.507,"f":null,"g":"1984-02-04T18:08:54.747Z","h":6444,"i":5151,"j":0.911,"k":"string 0.396","l":"string 0.345","m":8456,"b":0.814,"o":0.806,"p":0.33,"q":"1991-07-02T03:27:55.140Z","r":"1972-01-31T15:41:06.854Z","s":"1975-03-24T22:09:41.932Z","t":"string 0.774","u":"2013-08-20T08:41:23.297Z","v":7374,"w":null,"x":null,"y":0.93},"z":"string 0.180","c":"1973-01-26T19:55:14.208Z","d":"string 0.940","e":0.734,"f":0.569,"g":{"a":0.565,"z":0.114,"c":6911,"d":"string 0.0894","e":0.9,"f":8149,"g":"string 0.777","h":5352,"i":0.0555,"j":3497,"k":8192,"l":7546,"m":"1972-03-29T21:43:52.317Z","b":"1980-08-17T06:41:23.247Z","o":4047},"h":{"a":"2003-09-03T11:19:09.234Z","z":7333,"c":0.686,"d":2299,"e":2725,"f":9354,"g":0.249,"h":"string 0.385","i":3316,"j":null,"k":"2003-03-19T06:40:54.047Z","l":4642,"m":3240,"b":"2005-08-29T01:41:37.533Z","o":2718,"p":null,"q":"string 0.666","r":0.742,"s":"string 0.806","t":3896,"u":"1976-10-08T20:09:01.796Z","v":0.271,"w":5055,"x":2309,"y":null},"i":0.98,"j":{"a":0.226,"z":0.000491,"c":null},"k":"string 0.405","l":1816,"m":"1994-03-01T22:00:50.119Z"},"d":{"a":null,"z":"1985-09-06T03:06:54.808Z","c":64,"d":{"a":0.531,"z":0.377,"c":4587,"d":0.856,"e":0.539,"f":null,"g":"1984-07-06T19:32:18.650Z"},"e":"2009-04-27T14:54:04.938Z","f":"1999-07-29T04:24:42.978Z","g":7506,"h":{"a":"1982-05-22T04:38:05.190Z","z":8755,"c":"1979-06-02T19:59:20.129Z","d":0.0384,"e":8975,"f":2354,"g":4375,"h":8017,"i":5169,"j":0.465,"k":"string 0.663","l":0.467,"m":"string 0.0202","b":"string 0.436","o":7865,"p":9629,"q":2752,"r":null,"s":"1979-03-06T10:32:29.609Z","t":null},"i":2881,"j":"1970-09-20T18:03:24.404Z","k":"2010-05-31T10:09:26.766Z","l":"string 0.497","m":"1996-05-31T19:15:31.919Z","b":0.235,"o":"string 0.435","p":2774,"q":null,"r":{},"s":7788,"t":0.64},"e":{"a":"1974-08-17T04:54:55.936Z","z":9950,"c":0.834,"d":null,"e":1560,"f":{"a":5615,"z":"string 0.118","c":"2004-03-26T18:06:55.340Z","d":"string 0.226","e":4641,"f":0.313,"g":6474,"h":"2004-06-25T16:39:24.453Z","i":"2001-09-01T11:59:12.854Z","j":"1981-11-20T15:15:28.548Z","k":6240,"l":9246,"m":"1980-09-07T06:33:27.022Z","b":"string 0.177","o":"2010-04-21T18:46:52.602Z","p":"string 0.146","q":0.582},"g":{"a":"1995-02-26T16:26:01.082Z","z":"string 0.631","c":null,"d":"string 0.604","e":0.187,"f":null,"g":"1997-03-26T13:58:36.099Z","h":4705,"i":"string 0.846","j":"2006-09-15T20:28:54.014Z","k":null,"l":0.189},"h":4345,"i":"2006-10-22T20:50:47.528Z","j":"1977-02-27T01:39:23.588Z","k":{"a":8883,"z":0.149,"c":0.985},"l":0.679,"m":0.454,"b":8707},"f":null,"g":"string 0.753","h":0.912,"i":{"a":{"a":"string 0.0138","z":"1989-05-11T05:19:21.223Z","c":null,"d":"string 0.219","e":0.905,"f":1020,"g":7579,"h":2220,"i":9706,"j":"string 0.0511","k":"2007-03-05T17:31:56.981Z","l":"string 0.137"},"z":{"a":null,"z":"1983-11-14T18:01:13.237Z","c":6719,"d":0.972},"c":2650,"d":"string 0.167","e":0.825,"f":{"a":6305,"z":7918,"c":3888,"d":"1991-11-20T21:38:07.258Z","e":8280,"f":8533,"g":null,"h":"string 0.906","i":null,"j":"2004-01-08T02:22:04.633Z","k":0.386,"l":"string 0.108","m":0.253,"b":"1974-06-22T10:52:07.627Z","o":0.353,"p":6243,"q":6584},"g":0.542,"h":8161,"i":"2010-10-05T19:33:07.930Z","j":{"a":8592,"z":"string 0.699","c":6818},"k":0.359,"l":"2000-07-27T07:52:37.702Z","m":{"a":"2005-08-09T18:35:32.950Z","z":0.0427,"c":4048,"d":0.525,"e":0.319,"f":"string 0.917","g":"string 0.990","h":446,"i":null,"j":null},"b":{"a":5924,"z":0.00489,"c":"2014-05-12T01:40:46.595Z","d":"1987-10-12T06:50:26.836Z","e":"string 0.455","f":3730,"g":8717,"h":null,"i":"2010-02-24T03:47:10.056Z","j":"string 0.720","k":"string 0.112","l":211},"o":"2000-09-03T13:19:43.114Z","p":"1989-12-03T09:32:10.660Z"},"j":3672,"k":8634};
  
  var objC = immer.produce(objA, function(draft) {
    draft.a = 2;
  });
  
  
  Object.equals = function( x, y ) {
      if ( x === y ) return true;
        // if both x and y are null or undefined and exactly the same
    
      if ( ! ( x instanceof Object ) || ! ( y instanceof Object ) ) return false;
        // if they are not strictly equal, they both need to be Objects
    
      if ( x.constructor !== y.constructor ) return false;
        // they must have the exact same prototype chain, the closest we can do is
        // test there constructor.
    
      for ( var p in x ) {
        if ( ! x.hasOwnProperty( p ) ) continue;
          // other properties were tested using x.constructor === y.constructor
    
        if ( ! y.hasOwnProperty( p ) ) return false;
          // allows to compare x[ p ] and y[ p ] when set to undefined
    
        if ( x[ p ] === y[ p ] ) continue;
          // if they have the same strict value or identity then they are equal
    
        if ( typeof( x[ p ] ) !== "object" ) return false;
          // Numbers, Strings, Functions, Booleans must be strictly equal
    
        if ( ! Object.equals( x[ p ],  y[ p ] ) ) return false;
          // Objects and Arrays must be tested recursively
      }
    
      for ( p in y ) {
        if ( y.hasOwnProperty( p ) && ! x.hasOwnProperty( p ) ) return false;
          // allows x[ p ] to be set to undefined
      }
      return true;
    }
  
  const hasOwn = Object.prototype.hasOwnProperty
  function is(x, y) {
    if (x === y) {
      return x !== 0 || y !== 0 || 1 / x === 1 / y
    } else {
      return x !== x && y !== y
    }
  }
  function shallowEqual(objA, objB) {
    if (is(objA, objB)) return true
  
    if (typeof objA !== 'object' || objA === null ||
        typeof objB !== 'object' || objB === null) {
      return false
    }
  
    const keysA = Object.keys(objA)
    const keysB = Object.keys(objB)
  
    if (keysA.length !== keysB.length) return false
  
    for (let i = 0; i < keysA.length; i++) {
      if (!hasOwn.call(objB, keysA[i]) ||
          !is(objA[keysA[i]], objB[keysA[i]])) {
        return false
      }
    }
  
    return true
  }
  
  
  
  const TS = Object.prototype.toString;
  const OA = '[object Array]';
  const OO = '[object Object]';
  
  function flatDeepDiff(prev, next) {
    if (TS.call(prev) !== OO || TS.call(next) !== OO) {
      throw new TypeError('parameter must be object');
    }
  
    const flatDiff = {};
    const initPath = '';
    // fillKeys(prev, next);
    deepDiff(prev, next, initPath, flatDiff);
  
    if (Object.keys(flatDiff).length === 0) {
      return null;
    }
    return flatDiff;
  }
  
  /**
   * 把 prevData 中存在但 nextData 中不存在的 key 赋值 null, 包括数组中的对象
   *
   * { a: 1, b: 2 }, { a: 1 } => { a: 1, b: 2 }, { a: 1, b: null }
   * [{ a: 1, b: 2 }], [{ a: 1 }] => [{ a: 1, b: 2 }], [{ a: 1, b: null }]
   *
   * nextData 的数组长度小于 prevData 的数组长度的时候,不做数组 deepDiff,这种情况尽量采用 $spliceData
   * 删除数组中一项:
    commit(type, payload, {
      meta: {
        splicePath: 'path', // eg. 'todos'
        spliceData: [start, deleteCount, ...items] // eg. [100, 1]
      }
    });
   */
  function fillKeys(prev, next) {
    if (prev === next) return;
    const ntype = TS.call(next);
    const ptype = TS.call(prev);
    const isOO = ntype === OO && ptype === OO;
    const isOA = ntype === OA && ptype === OA;
  
    if (isOO) {
      for (let key in prev) {
        if (!prev.hasOwnProperty(key)) return;
        if (next[key] === undefined) {
          next[key] = null;
        } else {
          fillKeys(prev[key], next[key]);
        }
      }
    } else if (isOA && next.length >= prev.length) {
      prev.forEach((item, index) => {
        fillKeys(next[index], item);
      });
    }
  }
  
  function deepDiff(prev, next, path, flatDiff) {
    if (prev === next) return;
    const ntype = TS.call(next);
    const ptype = TS.call(prev);
    const isOO = ntype === OO && ptype === OO;
    const isOA = ntype === OA && ptype === OA;
  
    if (isOO) {
      for (var key in next) {
        if (!next.hasOwnProperty(key)) return;
        const flatKey = path + (path.length ? '.' + key : key);
        const nValue = next[key];
        const pValue = prev[key];
        const nVtype = TS.call(nValue);
        const pVtype = TS.call(pValue);
        if (nVtype !== OO && nVtype !== OA) {
          if (nValue !== pValue) {
            flatDiff[flatKey] = nValue;
          }
        } else if (nVtype === OA) {
          if (pVtype !== OA || nValue.length < pValue.length) {
            flatDiff[flatKey] = nValue;
          } else {
            deepDiff(pValue, nValue, flatKey, flatDiff);
            // nValue.forEach((item, idx) => {
            //   deepDiff(pValue[idx], item, flatKey + '[' + idx + ']', flatDiff);
            // });
          }
        } else if (nVtype === OO) {
          if (pVtype !== OO) {
            flatDiff[flatKey] = nValue;
          } else {
            deepDiff(pValue, nValue, flatKey, flatDiff);
            // for (var sk in nValue) {
            //   deepDiff(pValue[sk], nValue[sk], flatKey + '.' + sk, flatDiff);
            // }
          }
        }
      }
    } else if (isOA && next.length >= prev.length) {
      next.forEach((item, idx) => {
        deepDiff(prev[idx], item, path + '[' + idx + ']', flatDiff);
      });
    } else {
      flatDiff[path] = next;
    }
  
    return flatDiff;
  }
  
  
  
  
  
  
  
  
  const ARRAYTYPE = '[object Array]'
  const OBJECTTYPE = '[object Object]'
  const FUNCTIONTYPE = '[object Function]'
  
  function westoreDiff(current, pre) {
      const result = {}
      syncKeys(current, pre)
      _diff(current, pre, '', result)
      return result
  }
  
  function syncKeys(current, pre) {
      if (current === pre) return
      const rootCurrentType = type(current)
      const rootPreType = type(pre)
      if (rootCurrentType == OBJECTTYPE && rootPreType == OBJECTTYPE) {
          if(Object.keys(current).length >= Object.keys(pre).length){
              for (let key in pre) {
                  const currentValue = current[key]
                  if (currentValue === undefined) {
                      current[key] = null
                  } else {
                      syncKeys(currentValue, pre[key])
                  }
              }
          }
      } else if (rootCurrentType == ARRAYTYPE && rootPreType == ARRAYTYPE) {
          if (current.length >= pre.length) {
              pre.forEach((item, index) => {
                  syncKeys(current[index], item)
              })
          }
      }
  }
  
  function _diff(current, pre, path, result) {
      if (current === pre) return
      const rootCurrentType = type(current)
      const rootPreType = type(pre)
      if (rootCurrentType == OBJECTTYPE) {
          if (rootPreType != OBJECTTYPE || Object.keys(current).length < Object.keys(pre).length) {
              setResult(result, path, current)
          } else {
              for (let key in current) {
                  const currentValue = current[key]
                  const preValue = pre[key]
                  const currentType = type(currentValue)
                  const preType = type(preValue)
                  if (currentType != ARRAYTYPE && currentType != OBJECTTYPE) {
                      if (currentValue != pre[key]) {
                          setResult(result, (path == '' ? '' : path + ".") + key, currentValue)
                      }
                  } else if (currentType == ARRAYTYPE) {
                      if (preType != ARRAYTYPE) {
                          setResult(result, (path == '' ? '' : path + ".") + key, currentValue)
                      } else {
                          if (currentValue.length < preValue.length) {
                              setResult(result, (path == '' ? '' : path + ".") + key, currentValue)
                          } else {
                              currentValue.forEach((item, index) => {
                                  _diff(item, preValue[index], (path == '' ? '' : path + ".") + key + '[' + index + ']', result)
                              })
                          }
                      }
                  } else if (currentType == OBJECTTYPE) {
                      if (preType != OBJECTTYPE || Object.keys(currentValue).length < Object.keys(preValue).length) {
                          setResult(result, (path == '' ? '' : path + ".") + key, currentValue)
                      } else {
                          for (let subKey in currentValue) {
                              _diff(currentValue[subKey], preValue[subKey], (path == '' ? '' : path + ".") + key + '.' + subKey, result)
                          }
                      }
                  }
              }
          }
      } else if (rootCurrentType == ARRAYTYPE) {
          if (rootPreType != ARRAYTYPE) {
              setResult(result, path, current)
          } else {
              if (current.length < pre.length) {
                  setResult(result, path, current)
              } else {
                  current.forEach((item, index) => {
                      _diff(item, pre[index], path + '[' + index + ']', result)
                  })
              }
          }
      } else {
          setResult(result, path, current)
      }
  }
  
  function setResult(result, k, v) {
      if (type(v) != FUNCTIONTYPE) {
          result[k] = v
      }
  }
  
  function type(obj) {
      return Object.prototype.toString.call(obj)
  }

Test runner

Ready to run.

Testing in
TestOps/sec
flatDeepDiff
var a = flatDeepDiff(objA, objB);
ready
JSON.stringify
var a = JSON.parse(JSON.stringify(objA));
ready
Object.equals
var a = Object.equals(objA, objB);
ready
_.isEqual
var a = _.isEqual(objA, objB);
ready
shallowEqual
var a = shallowEqual(objA, objB);
ready
flatDeepDiff-immer
/*
var objC = immer.produce(objA, function(draft) {
  draft.a = 2;
});
*/
var a = flatDeepDiff(objA, objC);
ready
westoreDiff
var a = westoreDiff(objA, objB);
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.

  • Revision 1: published by noneven on