2014-03-01 37 views
5

我有两个大的嵌套javascript对象,我想比较它们并创建一个仅表示差异的对象。我打算用这个来创建一个PATCH请求。获取两个JavaScript对象的增量

鉴于oldObjnewObj

  • 属性是只对newObj应当在DIFF
  • 属性是只对oldObj应当在DIFF
  • 属性是两个对象应如果值为数组,字符串或数字,则使用newObj的值
  • 应递归比较对象
  • 无需fancily合并阵列,完全替代是确定

这可能看起来像一个重复的,但我不认为它是。 This solution (1)只有一个深度(下面的答案是非递归的,在阵列上爆炸,而不是双向的)。 this solution (2)返回不变的属性不是双向的。

目标输入/输出:

diff({a:1},{a:0}); // {a:0} 

diff({a:1},{b:1}); // {a:1,b:1} 

diff({ 
    a: { x: 1 }, 
    b: 1 
}, 
{ 
    a: { x: 0 }, 
    b: 1 
}) // {a:{x:0}} 

diff({a:[1,3,5,7]},{a:[1,3,7]}); // {a:[1,3,7]} 

我使用以下方法,该方法从溶液1.改性它满足所有条件,除了diff({a:1},{b:1}) // {a:1,b:1},因为它仅在一个方向上进行比较。

jsonDiff = function(oldObject, newObject) { 
    var diff, i, innerDiff; 
    diff = {}; 
    for (i in newObject) { 
    innerDiff = {}; 
    if (_.isArray(newObject[i])) { 
     if (!_.isEqual(newObject[i], oldObject[i])) { 
     diff[i] = newObject[i]; 
     } 
    } else if (typeof newObject[i] === 'object') { 
     innerDiff = jsonDiff(oldObject[i], newObject[i]); 
     if (!_.isEmpty(innerDiff)) { 
     diff[i] = innerDiff; 
     } 
    } else if (!oldObject) { 
     diff[i] = newObject[i]; 
    } else if (!oldObject.hasOwnProperty(i)) { 
     diff[i] = newObject[i]; 
    } else if (oldObject[i] !== newObject[i]) { 
     diff[i] = newObject[i]; 
    } 
    } 
    return diff; 
}; 

我已经看到了jsonDiffPatch库,但我并不需要它创建的所有元数据,只是原始的差异对象。有没有一个小型图书馆可以做到这一点?似乎有点必要实施PATCH很好,但我找不到一个。任何人都有这个小要点?

回答

0

这是一个答案有点长,但我没有出版呢。

function monitor(obj, callBack){ 


var api={ 
     patch: patchObjectWithDiff, 
     init: init, 
     resolve: resolve, 
     snapshot: snapshot, 
     diff: diff, 
     update: changeMonitor 
    }; 



function merge2(o, ob) { 
    for (var z in ob) { 
     if (ob.hasOwnProperty(z)) { 
      if(typeof ob[z]=="object"){ 
       if(ob[z]==null){ 
        delete o[z]; 
       }else{ 
        merge2(o[z] || {}, ob[z]); 
       } 

      }else{ 
       o[z] = ob[z]; 
      } 

     } 
    } 
    return o; 
} 






function snapshot(obj) { 
    var out = []; 
    function merge3(ob, path) { 
     path = path || []; 
       var tp; 
     for(var z in ob) { 
      if(ob.hasOwnProperty(z)) { 
       if(ob[z] && typeof ob[z] == "object" && [Date, RegExp].indexOf(ob[z].constructor) == -1) { 

             tp=path.concat(z); 
        out.push({ 
               path: tp.join("`"), 
               path2: tp, 
               dt: "set", 
               date: +new Date, 
               v: Array.isArray(ob[z]) ? "[]" : "{}" 
             }); 

        merge3(ob[z], path.concat(z)); 
       } else { 
             tp=path.concat(z); 
        out.push({ 
               path: tp.join("`"), 
               path2: tp, 
               type: "set", 
               dt: +new Date, 
               v: JSON.stringify(ob[z]) 
             }); 
       } 
      } 
     } 
    } 

    merge3(obj); 
    return out; 
}; 



function diff(d1, d2){ 

    var out=d2.filter(function(a,b,c){ 
    var ov=JSON.stringify(a.v); 
    return d1.some(function(aa,bb){ return aa.path==a.path && JSON.stringify(aa.v) != ov; }); 
    }), 

    // find deletions 
    dels=d1.filter(function(a,b,c){ 
    return !d2.some(function(aa,bb){ if(aa.path==a.path){ return true; }; }); 
    }), 

    allPaths=dels.map(function(a){return a.path}).sort(), 

    dels2=dels.filter(function eliminateUnneededSubBranches(a){ 

     var pos=allPaths.indexOf(a.path2.slice(0,-1).join("`")); 

     return pos==-1 || pos >= allPaths.indexOf(a.path); 

    }).map(function(a){a.type="del"; delete a.v; return a;}); 


    [].push.apply(out, dels2); 


//find inserts 


var outNew=d2.filter(function(a,b,c){ 
    var ov=JSON.stringify(a.v); 
    return !d1.some(function(aa,bb){ return aa.path==a.path }); 
    }); 

[].push.apply(out, outNew); 



    return out.map(function(a){ 
     var x= { 
     dt: a.dt, 
     k: a.path2 
     }; 

     if(a.hasOwnProperty("v")){ x.v=a.v; } 

     return x; 

      a.k=a.path2; 
      delete a.path; 
      delete a.path2; 
      delete a.type; 
     return a; 
    }); 
} 



function resolve(path, object){ 
    var tob=object; 
    path.map(function(a){ return (tob=tob[a])||tob; }) 
return tob; 
} 








function patchObjectWithDiff(diff, object){ 

    diff.forEach(function(a,b,c){ 
     var p= resolve(a.k.slice(0,-1), object), 
      k= a.k.slice(-1)[0]; 

     if(a.hasOwnProperty("v")){ //set: 
       p[k]=JSON.parse(a.v); 
      if(String(p[k]).match(/Z$/)){ p[k]=new Date(''+p[k]) || p[k]; } 
     }else{ // del: 
      if(Array.isArray(p)){ p.splice(k,1); }else{ delete p[k]; } 
     } 
    }); 

    return object; 
} 











    var init=snapshot(JSON.parse(JSON.stringify(obj))), 
      id=Math.random()+ Number(new Date()); 


    var init=snapshot(obj); 

    function changeMonitor(){ 
     var thisTime=snapshot(obj), 
       diffs=diff(init, thisTime); 
     if(diffs.length){ 
      api.diffs=diffs; 
      (callBack||console.log.bind(console))("objectUpdate", diffs); 
      init=thisTime; 
     }//end if change? 
    } 

    setInterval(changeMonitor, 2500); 

return api; 

} 

演示/示例用法:

var obj={a:1, b:[1,2,3], c: false}; // a model object 
var dupe=JSON.parse(JSON.stringify(obj)); // a cheap clone of the data for demo use 

//subscribe this object to updates  
var mon=monitor(obj, function(type, changes){console.log(type, changes); }); 

// make some changes to the object: 
obj.e="cool!"; 
obj.b.push(5); 
obj.a=7; 

// manually call update instead of waiting for the bundler: 
mon.update(); 

// now apply stored changes to the clone of the orig data: 
var updatedDupe= mon.patch(mon.diffs, dupe); 

// use a cheap and easy but not production-reliable to compare the objects: 
JSON.stringify(updatedDupe)==JSON.stringify(obj); // should be true 

在铬和Firefox测试。

请注意,此特定演示对JSON的使用取决于一些运气和一致的按键顺序,这不受JS规范保证。按键顺序并不重要,但它可能会导致JSON.stringify()==比较失败,即使该对象的属性确实同步了。这仅仅是用于演示的缘故得到一个真/假答案,如果一切正常,不要打我......

在变化列表中的所有的diff配有三个按键:

{"dt":1392348959730,"k":["b","3"],"v":"5"} 
dt: a timestamp of when the change was discovered 
k: the key path where the change was detected 
v: what the discovered changed value is as of dt 

这个脚本新闻发布很热,我没有时间写出适当的文档,但我认为这可能会有所帮助,或者至少可以激发出适合您的解决方案。

1

下面是应为你工作的功能,比代码更多的评论:

// diffObjs: return differences between JavaScript values 
// 
// Function: 
// 
// Compare two JavaScript values, and return a two-element 
// array that contains a minimal representation of the difference 
// between the two. 
// 
// Values may be scalar (e.g., string, integer, boolean) or objects, 
// including arrays. When the two values match exactly, that is, 
// if the '===' operator between the two would return 'true', we return NULL. 
//  
// When the result contains an object or array, only data, not references, 
// are copied from the arguments. This makes for a large size result 
// but one whose manipulation will not affect the original arguments. 
// 
// Args: 
// v1, v2: values to compare 
// 
// Specific behaviors: 
// 
// *Return NULL if v1 === v2* 
// 
// This happens when two scalar (non-object) values match, or when the same 
// object or array is passed in both arguments. 
// e.g., 
//   
//  var my_obj = { member1: 0, member1: 'dog' }; 
//  var my_array = [ 1, 'cat' ]; 
//  var my_int = 7; 
//  var no_val = null; 
// 
//  diffObjs(my_int, my_int)  ==> NULL 
//  diffObjs(1, 1)     ==> NULL 
//  diffObjs(my_obj, my_obj)  ==> NULL 
//  diffObjs({x:1,y:2}, {x:1,y:2}) ==> NULL 
//  diffObjs(my_array, my_array) ==> NULL 
//  diffObjs([1,'a'], [1,'1'])  ==> NULL 
//  diffObjs(null, null)   ==> NULL 
//  diffObjs(no_val, null)   ==> NULL 
// 
// *Return copies of v1 and v2 on type mismatch*: 
// 
// When type of v1 and v2 are different or one is an array and the other 
// is an object, the result array will contain exect copies of both 
// v1 and v2. 
// 
// *Return minimal representation of differences among non-array objects*: 
// 
// Otherwise, when two objects are passed in, element 0 
// in the result array contains the members and their values 
// that exist in v1 but not v2, or members that exist in both 
// v1 and v2 that have different values. Element 1 contains 
// the same but with respect to v2, that is members and their 
// values that exist in v2 but not v1, or members that exist in 
// both v1 and v2 that have different values. 
//  
// Note: The members are represented in the result objects only when 
// they are specific to the object of the corresponding value argument 
// or when the members exist in both and have different values. The 
// caller therefore can tell whether the object mismatch exists 
// because of specificity of a member to one object vs. a mismatch 
// in values where one is null and the other is not. 
// 
// Examples: 
//  diffObjs({a:10, b:"dog"}, {a:1, b:"dog"} ==> [ {a:10}, {a:1} ] 
//  diffObjs({a:10},   {a:10, b:"dog"} ==> [ {}, {b:"dog"} ] 
//  diffObjs({a:10, c:null}, {a:10, b:"dog"} ==> [ {c:null}, {b:"dog"} ] 
//  diffObjs({a:[1], b:"cat"},{a:1, b:"dog"} ==> [ {a:[1], b:"cat"}, {a:1, b:"dog"} ] 
//  diffObjs(
//   {a:{ m1:"x", m2:"y"}, b:3 }, 
//   {a:{ m1:"x", m2:"z", m3:1 }, b:3 }) ==> [ {a:{m2:"y"}}, {a:{m2:"z",m3:1}} ] 
// 
// *Return copies of compared arrays when differing by position or value* 
// 
// If the two arguments arrays, the results in elements 0 and 1 
// will contain results in array form that do not match with respect 
// to both value and order. If two positionally corresponding 
// elements in the array arguments have identical value (e.g., two 
// scalars with matching values or two references to the same object), 
// the corresponding values in the array will be null. The 
// cardinality of the arrays within the result array will therefore 
// always match that of the corresponding arguments. 
// 
// Examples: 
//  diffObjs([1,2],  [1,2]) ==> [ [null,null], [null,null] ] 
//  diffObjs([1,2],  [2,1]) ==> [ [1,2], [2,1] ] 
//  diffObjs([1,2],  [1,2,3]) ==> [ [1,2,null], [2,1,3] ] 
//  diffObjs([1,1,2,3], [1,2,3]) ==> [ [null,1,2,3], [null,2,3] ] 
// 

var diffObjs = function(v1, v2) { 

    // return NULL when passed references to 
    // the same objects or matching scalar values 
    if (v1 === v2) { 
     return null; 
    } 
    var cloneIt = function(v) { 
     if (v == null || typeof v != 'object') { 
      return v; 
     } 

     var isArray = Array.isArray(v); 

     var obj = isArray ? [] : {}; 
     if (!isArray) { 
      // handles function, etc 
      Object.assign({}, v); 
     } 

     for (var i in v) { 
      obj[i] = cloneIt(v[i]); 
     } 

     return obj; 
    } 

    // different types or array compared to non-array 
    if (typeof v1 != typeof v2 || Array.isArray(v1) != Array.isArray(v2)) { 
     return [cloneIt(v1), cloneIt(v2)]; 
    } 

    // different scalars (no cloning needed) 
    if (typeof v1 != 'object' && v1 !== v2) { 
     return [v1, v2]; 
    } 

    // one is null, the other isn't 
    // (if they were both null, the '===' comparison 
    // above would not have allowed us here) 
    if (v1 == null || v2 == null) { 
     return [cloneIt(v1), cloneIt(v2)]; 
    } 

    // We have two objects or two arrays to compare. 
    var isArray = Array.isArray(v1); 

    var left = isArray ? [] : {}; 
    var right = isArray ? [] : {}; 

    for (var i in v1) { 
     if (!v2.hasOwnProperty(i)) { 
      left[i] = cloneIt(v1[i]); 
     } else { 
      var sub_diff = diffObjs(v1[i], v2[i]); 
      // copy the differences between the 
      // two objects into the results. 
      // - If the object is array, use 'null' 
      // to indicate the two corresponding elements 
      // match. 
      // 
      // - If the object is not an array, copy only 
      // the members that point to an unmatched 
      // object. 
      if (isArray || sub_diff) { 
       left[i] = sub_diff ? cloneIt(sub_diff[0]) : null; 
       right[i] = sub_diff ? cloneIt(sub_diff[1]) : null; 
      } 
     } 
    } 

    for (var i in v2) { 
     if (!v1.hasOwnProperty(i)) { 
      right[i] = cloneIt(v2[i]); 
     } 
    } 

    return [ left, right]; 
};