İki JSON nesnesini aynı öğelerle farklı bir sırada eşit olarak nasıl karşılaştırabilirim?


104

Listelerin sırasını göz ardı ederek python'da iki JSON nesnesinin eşit olup olmadığını nasıl test edebilirim?

Örneğin ...

JSON belgesi a :

{
    "errors": [
        {"error": "invalid", "field": "email"},
        {"error": "required", "field": "name"}
    ],
    "success": false
}

JSON belgesi b :

{
    "success": false,
    "errors": [
        {"error": "required", "field": "name"},
        {"error": "invalid", "field": "email"}
    ]
}

ave listelerin bsıralaması "errors"farklı olsa bile eşit şekilde karşılaştırmalıdır .



1
Neden onları çözüp karşılaştırmıyoruz? Yoksa "Dizi" nin sırasının listmı yoksa öğelerin de önemli olmadığını mı söylüyorsunuz?
mgilson

@ user2085282 Bu sorunun devam eden farklı bir sorunu var.
user193661

2
Lütfen saflığımı affet, ama neden? Liste öğelerinin bir nedenle belirli bir sırası vardır.
ATOzTOA

1
Bu yanıtta belirtildiği gibi, bir JSON dizisi sıralanır, böylece farklı sıralama düzenlerine sahip dizileri içeren bu nesneler tam anlamıyla eşit olmaz. stackoverflow.com/a/7214312/18891
Eric Ness

Yanıtlar:


146

Aynı öğelere sahip ancak farklı sırayla karşılaştırılan iki nesneyi eşit olarak karşılaştırmak istiyorsanız, yapmanız gereken bariz şey, bunların sıralı kopyalarını karşılaştırmaktır - örneğin, JSON dizeleriniz tarafından temsil edilen sözlükler ave b:

import json

a = json.loads("""
{
    "errors": [
        {"error": "invalid", "field": "email"},
        {"error": "required", "field": "name"}
    ],
    "success": false
}
""")

b = json.loads("""
{
    "success": false,
    "errors": [
        {"error": "required", "field": "name"},
        {"error": "invalid", "field": "email"}
    ]
}
""")
>>> sorted(a.items()) == sorted(b.items())
False

... ancak bu işe yaramaz, çünkü her durumda, en "errors"üst düzey diktenin öğesi, aynı öğelerin farklı bir sırada olduğu bir listedir sorted()ve "en üst" düzey dışında hiçbir şeyi sıralamaya çalışmaz. yinelenebilir.

Bunu düzeltmek için, orderedbulduğu listeleri yinelemeli olarak sıralayacak (ve (key, value)sıralanabilmeleri için sözlükleri çiftler listesine dönüştürecek) bir işlev tanımlayabiliriz :

def ordered(obj):
    if isinstance(obj, dict):
        return sorted((k, ordered(v)) for k, v in obj.items())
    if isinstance(obj, list):
        return sorted(ordered(x) for x in obj)
    else:
        return obj

Bu işlevi ave işlevine uygularsak b, sonuçlar eşittir:

>>> ordered(a) == ordered(b)
True

1
çok teşekkür ederim Zero Piraeus. tam da ihtiyacım olan genel çözüm. ancak tek sorun, kodun yalnızca python 2.x için çalıştığı, python3 için değil. Aşağıdaki hatayı alıyorum: TypeError: sıralanamayan türler: dict () <dict () Her neyse, çözüm artık açık. Python3 için çalışmasını sağlamaya çalışacağım. Çok teşekkürler

1
@HoussamHsm Sıralanamaz dicts probleminden ilk bahsettiğinizde Python 3.x ile çalışmak için bunu düzeltmek istedim, ama bir şekilde benden uzaklaştı. Artık hem 2.x hem de 3.x sürümlerinde çalışıyor :-)
Zero Piraeus

gibi bir liste olduğunda ['astr', {'adict': 'something'}], TypeErroronları sıralamaya çalışırken aldım .
Zhenxiao Hao

1
@ Blairg23 JSON nesnelerini eşit olarak karşılaştırmakla ilgili soruyu, öğeleri aynı olan, ancak sözde herhangi bir sözlük sırası hakkında değil , farklı bir sırada olan listeler içerdiklerinde yanlış anladınız .
Zero Piraeus

1
@ Blairg23 Sorunun daha net bir şekilde yazılabileceğini kabul ediyorum (yine de düzenleme geçmişine bakarsanız, başladığından daha iyidir). Re: sözlükler ve düzen - evet, biliyorum ;-)
Zero Piraeus

46

Başka bir yol json.dumps(X, sort_keys=True)seçeneği kullanmak olabilir :

import json
a, b = json.dumps(a, sort_keys=True), json.dumps(b, sort_keys=True)
a == b # a normal string comparison

Bu, iç içe geçmiş sözlükler ve listeler için işe yarar.


{"error":"a"}, {"error":"b"}vs {"error":"b"}, {"error":"a"} ikinci vakayı ilk vakaya sıralayamayacak
ChromeHearts

@ Blairg23 ama dikte iç içe listeleriniz varsa ne yapardınız? En üst düzey dikteyi karşılaştırıp bir gün diyemezsiniz, bu sorunun konusu bu değil.
stpk

4
İçeride listeleriniz varsa bu işe yaramaz. örneğin json.dumps({'foo': [3, 1, 2]}, sort_keys=True) == json.dumps({'foo': [2, 1, 3]}, sort_keys=True)
Danil

8
@Danil ve muhtemelen olmamalı. Listeler sıralı bir yapıdır ve sadece sırayla farklılık gösteriyorlarsa, onları farklı kabul etmeliyiz. Belki sizin kullanımınız için sıranın önemi yok, ama bunu varsaymamalıyız.
stpk

listeler dizine göre sıralandığından, bunlara başvurulmayacaktır. [0, 1] çoğu durumda [1, 0] 'a eşit olmamalıdır. Yani bu normal durum için iyi bir çözümdür, ancak yukarıdaki soru için değil. hala +1
Harrison

18

Bunları çözün ve mgilson yorumu olarak karşılaştırın.

Anahtarlar ve değerler eşleştiği sürece sözlük için sıra önemli değildir. (Sözlüğün Python'da sırası yoktur)

>>> {'a': 1, 'b': 2} == {'b': 2, 'a': 1}
True

Ancak sıra, listede önemlidir; sıralama, listeler için sorunu çözecektir.

>>> [1, 2] == [2, 1]
False
>>> [1, 2] == sorted([2, 1])
True

>>> a = '{"errors": [{"error": "invalid", "field": "email"}, {"error": "required", "field": "name"}], "success": false}'
>>> b = '{"errors": [{"error": "required", "field": "name"}, {"error": "invalid", "field": "email"}], "success": false}'
>>> a, b = json.loads(a), json.loads(b)
>>> a['errors'].sort()
>>> b['errors'].sort()
>>> a == b
True

Yukarıdaki örnek, söz konusu JSON için çalışacaktır. Genel çözüm için Sıfır Pire'nin cevabına bakın.


2

Aşağıdaki iki dikt için 'dictWithListsInValue' ve 'reorderedDictWithReorderedListsInValue' birbirlerinin basitçe yeniden sıralı sürümleri olan

dictObj = {"foo": "bar", "john": "doe"}
reorderedDictObj = {"john": "doe", "foo": "bar"}
dictObj2 = {"abc": "def"}
dictWithListsInValue = {'A': [{'X': [dictObj2, dictObj]}, {'Y': 2}], 'B': dictObj2}
reorderedDictWithReorderedListsInValue = {'B': dictObj2, 'A': [{'Y': 2}, {'X': [reorderedDictObj, dictObj2]}]}
a = {"L": "M", "N": dictWithListsInValue}
b = {"L": "M", "N": reorderedDictWithReorderedListsInValue}

print(sorted(a.items()) == sorted(b.items()))  # gives false

bana yanlış sonuç verdi, yani yanlış.

Bu yüzden kendi cutstom ObjectComparator'ımı şöyle yarattım:

def my_list_cmp(list1, list2):
    if (list1.__len__() != list2.__len__()):
        return False

    for l in list1:
        found = False
        for m in list2:
            res = my_obj_cmp(l, m)
            if (res):
                found = True
                break

        if (not found):
            return False

    return True


def my_obj_cmp(obj1, obj2):
    if isinstance(obj1, list):
        if (not isinstance(obj2, list)):
            return False
        return my_list_cmp(obj1, obj2)
    elif (isinstance(obj1, dict)):
        if (not isinstance(obj2, dict)):
            return False
        exp = set(obj2.keys()) == set(obj1.keys())
        if (not exp):
            # print(obj1.keys(), obj2.keys())
            return False
        for k in obj1.keys():
            val1 = obj1.get(k)
            val2 = obj2.get(k)
            if isinstance(val1, list):
                if (not my_list_cmp(val1, val2)):
                    return False
            elif isinstance(val1, dict):
                if (not my_obj_cmp(val1, val2)):
                    return False
            else:
                if val2 != val1:
                    return False
    else:
        return obj1 == obj2

    return True


dictObj = {"foo": "bar", "john": "doe"}
reorderedDictObj = {"john": "doe", "foo": "bar"}
dictObj2 = {"abc": "def"}
dictWithListsInValue = {'A': [{'X': [dictObj2, dictObj]}, {'Y': 2}], 'B': dictObj2}
reorderedDictWithReorderedListsInValue = {'B': dictObj2, 'A': [{'Y': 2}, {'X': [reorderedDictObj, dictObj2]}]}
a = {"L": "M", "N": dictWithListsInValue}
b = {"L": "M", "N": reorderedDictWithReorderedListsInValue}

print(my_obj_cmp(a, b))  # gives true

bu bana beklenen doğru çıktıyı verdi!

Mantık oldukça basit:

Nesneler 'liste' türündeyse, bulunana kadar birinci listenin her bir öğesini ikinci listenin öğeleriyle karşılaştırın ve öğe ikinci listeden geçtikten sonra bulunamazsa, o zaman "bulundu" = false olacaktır. 'bulunan' değeri döndürülür

Aksi takdirde, karşılaştırılacak nesneler 'dict' türündeyse, her iki nesnede de tüm ilgili anahtarlar için mevcut değerleri karşılaştırın. (Özyinelemeli karşılaştırma yapılır)

Aksi takdirde obj1 == obj2'yi çağırın. Varsayılan olarak dizelerin ve sayıların nesnesi için iyi çalışır ve bunlar için eq () uygun şekilde tanımlanır.

(Algoritmanın, object2'de bulunan öğeler kaldırılarak daha da geliştirilebileceğini, böylece object1'in sonraki öğesinin kendisini zaten object2'de bulunan öğelerle karşılaştırmayacağını unutmayın)


Lütfen kodunuzun girintisini düzeltebilir misiniz ?
colidyre

@colidyre girinti şimdi iyi mi?
NiksVij

Hayır, hala sorun var. Fonksiyon başlığından sonra bloğun da girintilenmesi gerekir.
colidyre

Evet. Bir kez daha yeniden düzenledim. IDE'ye yapıştırdım ve şimdi çalışıyor.
NiksVij

1

Kendi eşittir fonksiyonunuzu yazabilirsiniz:

  • Şu durumlarda diktler eşittir: 1) tüm anahtarlar eşitse, 2) tüm değerler eşitse
  • listeler eşittir: tüm öğeler eşitse ve aynı sıradaysa
  • ilkeller eşittir a == b

Eğer json ile uğraşıyoruz olduğundan, standart piton türlerini olur: dict, listvb sabit tip denetimi yapmak, böylece if type(obj) == 'dict':, vb

Kaba örnek (test edilmedi):

def json_equals(jsonA, jsonB):
    if type(jsonA) != type(jsonB):
        # not equal
        return False
    if type(jsonA) == dict:
        if len(jsonA) != len(jsonB):
            return False
        for keyA in jsonA:
            if keyA not in jsonB or not json_equal(jsonA[keyA], jsonB[keyA]):
                return False
    elif type(jsonA) == list:
        if len(jsonA) != len(jsonB):
            return False
        for itemA, itemB in zip(jsonA, jsonB):
            if not json_equal(itemA, itemB):
                return False
    else:
        return jsonA == jsonB

0

İki JSON nesnesinde hata ayıklamak isteyenler için (genellikle bir referans ve bir hedef vardır ), işte kullanabileceğiniz bir çözüm. Hedeften referansa kadar farklı / uyumsuz olanların " yolunu " listeleyecektir .

level seçeneği ne kadar derine bakmak istediğinizi seçmek için kullanılır.

show_variables ilgili değişkeni göstermek için seçeneği açılabilir.

def compareJson(example_json, target_json, level=-1, show_variables=False):
  _different_variables = _parseJSON(example_json, target_json, level=level, show_variables=show_variables)
  return len(_different_variables) == 0, _different_variables

def _parseJSON(reference, target, path=[], level=-1, show_variables=False):  
  if level > 0 and len(path) == level:
    return []
  
  _different_variables = list()
  # the case that the inputs is a dict (i.e. json dict)  
  if isinstance(reference, dict):
    for _key in reference:      
      _path = path+[_key]
      try:
        _different_variables += _parseJSON(reference[_key], target[_key], _path, level, show_variables)
      except KeyError:
        _record = ''.join(['[%s]'%str(p) for p in _path])
        if show_variables:
          _record += ': %s <--> MISSING!!'%str(reference[_key])
        _different_variables.append(_record)
  # the case that the inputs is a list/tuple
  elif isinstance(reference, list) or isinstance(reference, tuple):
    for index, v in enumerate(reference):
      _path = path+[index]
      try:
        _target_v = target[index]
        _different_variables += _parseJSON(v, _target_v, _path, level, show_variables)
      except IndexError:
        _record = ''.join(['[%s]'%str(p) for p in _path])
        if show_variables:
          _record += ': %s <--> MISSING!!'%str(v)
        _different_variables.append(_record)
  # the actual comparison about the value, if they are not the same, record it
  elif reference != target:
    _record = ''.join(['[%s]'%str(p) for p in path])
    if show_variables:
      _record += ': %s <--> %s'%(str(reference), str(target))
    _different_variables.append(_record)

  return _different_variables
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.