jQuery/JavaScript JSON对象比较

9
有没有可能比较两个json对象的差异?目前有一个脚本正在通过jquery $post()轮询JSON对象。我想做的是,将刚刚轮询的对象与存储的对象进行比较。如果有任何更改,就将它们应用于存储的对象或替换它(无论哪种方式),但从UI的角度来看,将更改无缝地应用于JSON对象,找到两者之间的差异。我想这样做是因为现在我已经让UI每次轮询时完全重新加载,无论是否更改都会这样做,这基本上看起来像UX **。我认为,如果我能找到两个对象之间的差异,我就会触发一个函数,该函数会根据差异编辑UI。

5
你做错了,你不应该从服务器发送相似的对象,并在客户端手动查找差异。你应该让服务器仅发送差异。这样可以节省带宽和计算时间。 - Raynos
如果我能控制我正在处理的部分的服务器端频谱,那将是很棒的。不幸的是,我被困在使用网络上各种来源提供给我的预设函数来获取数据。我的手被绑住了,否则我会以你建议的方式不同地处理这个问题。 - chris
1
在这种情况下,您可能需要类似于 json diff 的东西。 - Raynos
1
@Raynos,那个JSON Diff基本上就是我想要实现的。现在我只需要弄清楚如何使用相同的逻辑或算法来满足我的需求。 - chris
5个回答

11
我想要做的是将刚刚轮询到的对象与存储的对象进行比较。如果它们之间有任何变化,就将它们应用于存储的对象或替换它(两种方式都可以)。如果您只需要一个非常简单的“是否发生了任何更改?”解决方案,并且如果已经发生更改,则只需使用新对象替换先前的对象(如我引用的部分所示)
则可以在解析JSON之前将其保存,即以Web服务器发送的字符串格式保存它。然后,当下一个响应到达时,将新字符串与旧字符串进行比较。如果它们不同(或者是第一个请求),则解析JSON并根据需要处理它以进行显示。当然,这假设您的服务器端代码以一致的格式创建JSON字符串(例如,不更改属性顺序等)。如果我们假设您已经拥有(解析过的)对象,那么isEqual(a,b)函数确实应该能够处理嵌套对象、属性为数组等情况。这可以通过递归来完成,并简单地返回true或false,但是getDifferences(a,b)函数在报告嵌套对象中的差异方面会变得令人困惑。考虑以下简单示例:
old: {"mum" : "Maria", "dad" : "Pierre", "kids" : ["Joe", "Mike", "Louisa"] }
new: {"mum" : "Julie", "dad" : "Pierre", "kids" : ["Joe", "Mary"] }
这个差异是不是 {"mum" : "Julie", "kids" : ["Mary"]}? "mum"属性已经改变,"kids"列表也发生了变化,但是"Mike"是否变成了"Mary",还是"Mike"和"Louisa"都消失了而"Mary"是新的呢?也许应该是 "kids": ["Joe","Mary"],因为这是新值。如何指示删除的内容呢?这只是我脑海中的第一个例子,我不知道您希望如何处理这些差异。情况可能会更糟:如果"kids"数组包含对象而不是字符串来表示整个家庭树怎么办?如果新的"mum"属性是["Maria", "Julie"](以允许继父等情况)。
如果针对特定数据您知道只有一维对象,那么可以简单地执行以下操作:
function getDifferences(oldObj, newObj) {
   var diff = {};

   for (var k in oldObj) {
      if (!(k in newObj))
         diff[k] = undefined;  // property gone so explicitly set it undefined
      else if (oldObj[k] !== newObj[k])
         diff[k] = newObj[k];  // property in both but has changed
   }

   for (k in newObj) {
      if (!(k in oldObj))
         diff[k] = newObj[k]; // property is new
   }

   return diff;
}
以上代码最简单的修改允许嵌套对象,只需要假设如果一个属性是对象/数组,那么您只关心它是否以任何方式不同,而不是深入挖掘报告哪些"子属性"已更改。若是如此,只需取上述函数并更改:
else if (oldObj[k] !== newObj[k])

else if (!isEqual(oldObj[k],newObj[k]))

在网络或者StackOverflow上有很多比较函数,其中isEqual()是其中之一。

(注意:我没有使用.hasOwnProperty(),因为我认为作为 JSON 返回给 Ajax 请求的对象不会从原型链继承属性。类似地,这样用于此目的的isEqual()函数无需考虑属性是函数,它只需要关心在 JSON 字符串中有效的内容。)


我喜欢这个解释。我正在设置一个新的测试环境。所以等那个完成后,我会尝试一下并看看我能否根据我的需求使用它,我真的很感激你为了帮助我理解而深入探讨的程度。如果它对我有用或者不起作用,我稍后会回来报告我的结果。 - chris
isEqual是lodash的本地函数,如果您只是将对象转换为字符串并进行比较,那会怎样呢? ´´´else if (String(oldObj[k]) !== String(newObj[k]))´´´ 这应该可以工作。 - Jonathan Arias

6

很抱歉回答旧帖,但我的回答可能会帮助其他可能遇到同样问题的人。以下是比较两个JSON对象最简单和最短的代码。谢谢

<script type="text/javascript">
    $(document).ready(function () {
        var a = { "id": "210", "memberlist": "john" };
        var b = { "id": "210", "memberlist": "john1" };

        alert(JSON.stringify(a) != JSON.stringify(b) ? 'not same' : ' same');
    });
</script>

1
是的,但我记得问题的整个重点是检测变化,然后确定这些差异。老实说,我不记得是什么时候,大约是4年前了,无论如何,对于这个评论,这只会告诉您它们是否完全相同。尽管对于这个级别的想法,也许可以起作用,即:这些是否相同(true或false),但即使在大型对象上,这也可能会给客户端带来一些压力,更多的情况是循环遍历并比较。因为对于较大的对象,这可能会占用大量客户端内存。 - chris

0
一个潜在的解决方案是使用jQuery的extend。只要对象具有相同的属性,那么这将非常有效。
var newJSON = $.extend({},oldJSON,serverData);

那么你将会保留旧对象,并创建一个新对象,如果在新对象中没有旧对象的任何属性,则使用旧对象的属性,并用来自服务器的新数据覆盖任何现有属性。


从 OP 中,“如果有任何更改从一个应用到另一个,则将其应用于存储的对象或替换它(无论哪种方式)”。 - bstakes
你误解了他。他想知道发生了什么变化,而不仅仅是获取一个新对象。如果他想要后者,那么他可以执行 var newJSON = serverData - Raynos
@Raynos - 不,正如bstakes从问题中引用的那样,OP说要确定是否有差异,然后“将它们应用于存储的对象或替换它(无论哪种方式)”。我理解这意味着理想的解决方案将报告差异,但如果无法完成(或无法轻松完成),则直接替换是可以接受的。 - nnnnnn
@nnnnnn - 那你为什么要合并它们呢?你可以直接使用新的代替旧的。这个答案根本没有回答问题,反而显示了对问题的理解存在深层次的问题。 - vsync

0
var objectsAreEqual = function(obj1, x){
  var MAX_DEPTH = 10;
  var testEq = function(obj1, x, depth){
    if(depth < MAX_DEPTH){
      for (var p in obj1) {
          if(typeof(obj1[p]) !== typeof(x[p])) return false;
          if((obj1[p]===null) !== (x[p]===null)) return false;
          switch (typeof(obj1[p])) {
              case 'undefined':
                  if (typeof(x[p]) != 'undefined') return false;
                  break;
              case 'object':
                  if(obj1[p]!==null && x[p]!==null && (obj1[p].constructor.toString() !== x[p].constructor.toString() || !testEq(obj1[p], x[p], depth + 1))) return false;
                  break;
              case 'function':
                  if (p != 'equals' && obj1[p].toString() != x[p].toString()) return false;
                  break;
              default:
                  if (obj1[p] !== x[p]) return false;
          }
      }
    }
    return true;
  };
  // this is a little ugly, but the algorithm above fails the following: testEq([[1,2],[]], [[1,2],[1,3]], 0)
  return testEq(obj1, x, 0) && testEq(x, obj1, 0); 

};

0

这是我的代码:

    function getDifferences(oldObj, newObj) {
    var diff = {};

    for (var k in oldObj) {
        if (!(k in newObj)){
            diff[k] = undefined;  // old key does not exist in new
        } else {
            switch (typeof oldObj[k]){
                case "array": {
                    String(oldObj[k]) !== String(newObj[k]) ? diff[k] = newObj[k] : null;
                    break;
                } case "object": {                        
                    Object.keys(oldObj[k]).forEach(key =>{                           
                        if(oldObj[k][key] !== newObj[k][key]){
                            if(diff[k]){
                                diff[k][key] = newObj[k][key];
                            } else {
                                diff[k] = {[key]: newObj[k][key]}
                            }
                        }
                    });
                    //JSON.stringify(oldObj[k]) !== JSON.stringify(newObj[k]) ? diff[k] = newObj[k] : null; Optional basic comparision
                    break;
                } default: { //Values are strings or numbers
                    oldObj[k] !== newObj[k] ? diff[k] = newObj[k] : null;
                    break;
                }
            }
        }
    }

    for (k in newObj) {
        if (!(k in oldObj))
        diff[k] = newObj[k]; // property is new
    }     
    return diff;
}

我们获取元素的类型并进行内部比较以确定它们是否相等。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接