2012-09-25 3 views
21

Mam kilka automatycznie generowanych plików JSON, które chcę przechowywać w kontroli wersji. Problem polega na tym, że za każdym razem, gdy pliki są serializowane, atrybuty są wyświetlane w innej kolejności, co utrudnia określenie, czy pliki naprawdę się zmieniły i/lub jakie są rzeczywiste różnice.Kanonicalizowanie plików JSON

Czy ktoś wie o istniejącym narzędziu open source, które wykona to zadanie?

W przypadku awarii, czy ktoś wie o bibliotece JSON z parserem i generatorem, które można skonfigurować do wysyłania "ładnego" JSON-a z atrybutami w (powiedzmy) porządku leksykalnym? (Biblioteka Java lub Ruby byłaby idealna, ale inne mile widziane są również mile widziane.)

Odpowiedz

13

Python's JSON module jest bardzo użyteczny z innymi programami:

generate_json | python -mjson.tool > canonical.json 
+1

Dzięki. To będzie ładnie działać. –

+8

Uwaga: działa to, ponieważ 'sort_keys = True' w implementacji' json.tool', ale ta gwarancja nie jest nigdzie udokumentowana, więc warto napisać własny skrypt, który może zapewnić tę gwarancję: 'import json, sys; print json.dumps (json.load (sys.stdin), sort_keys = True) ' –

0

Nie próbowałem wielu kombinacji, ale wygląda na to, że google-gson zachowuje kolejność atrybutów w JSON.

usunięte przykład tutaj, ponieważ nie było już

wiem z doświadczenia w poprzednich projektach, że jest bardzo konfigurowalny, na przykład jeśli base object nie wystarcza, można użyć GsonBuilder tworzyć bardziej skomplikowane istotne adaptery.

nie mam jednak sprawdzili to w znacznym stopniu z korzystaniem z osobna, ale powinno być proste, aby sprawdzić czy posiada ono oczekiwany wynik

UPDATE

zamiast używać SVN/CVS, aby sprawdzić, czy pliki zostały zmodyfikowane, stwierdziliśmy, że GSON ma wbudowany versioning support które mogą lub nie mogą rozwiązać problemu, z ich dokumentów:

wielokrotne wersje tego samego obiektu może być utrzymywana za pomocą @Since adnotacja. Ta adnotacja może być używana w klasach, polach i, w przyszłej wersji, metodach. Aby wykorzystać tę funkcję, należy skonfigurować instancję Gson tak, aby ignorowała wszelkie pola/obiekty, które są większe niż niektóre numery wersji. Jeśli żadna wersja nie zostanie ustawiona w instancji Gson, będzie serializować i deserializować wszystkie pola i klasy, niezależnie od wersji.

UPDATE

Jedyne co mogę myśleć jest parsowanie plików zewnętrznych z rhino i korzystania JSON.stringify przekonwertować przeanalizowany JSON z powrotem do łańcucha, to możesz być pewien, że ma przebiegnij przez pojedynczy "analizator składni", a wynik nie będzie się różnić.

Następnie można wykryć wszelkie możliwe zmiany.

+0

@Since adnotacja jest mało prawdopodobne pomóc. Te pliki JSON pochodzą z zewnętrznego źródła (nie Java). –

+0

@StephenC, rozumiem teraz twój problem jaśniej, zobacz moją aktualizację – epoch

0

Biblioteka Java o otwartym kodzie źródłowym Jackson może wymagać pewnego wysiłku, aby skonfigurować, ale jest w stanie sprawnie drukować i ma całkiem niezłą adnotację @JsonPropertyOrder, która obsługuje alfabetyczną lub ręcznie określoną kolejność wyjściową.

+1

Czy ta adnotacja może być użyta w JSON, który ładujesz jako drzewo instancji 'TreeNode'? Nie mam klas Java Pojo ... ani schematu JSON do zabawy. Są to ogólne pliki JSON o nieprzewidywalnej strukturze i zawartości. –

+0

Niestety, nie poszedłem tą drogą. W mojej odpowiedzi część "może wymagać trochę wysiłku" była na miejscu z myślą o definicji POJO i mechanizmie Jackson SerializationConfig. –

0

Ruby 1.9+ zachowuje kolejność wstawiania skrótów i JSON dla 1.9+ honorów, które.

asdf = {'a' => 1, 'b' => 2} 
asdf.to_json # => "{\"a\":1,\"b\":2}" 

asdf = {'b' => 1, 'a' => 2} 
asdf.to_json # => "{\"b\":1,\"a\":2}" 

Oto jak wygenerować "Pretty" format:

asdf = {'a' => 1, 'b' => 2} 
puts JSON.pretty_generate(asdf) 
{ 
    "a": 1, 
    "b": 2 
} 

asdf = {'b' => 1, 'a' => 2} 
irb(main):022:0> puts JSON.pretty_generate(asdf) 
{ 
    "b": 1, 
    "a": 2 
} 

... te same atrybuty są wkładane w innej kolejności ...

Nie ma to dla mnie większego sensu, ale zamierzam zrobić zdjęcie.

Ponieważ Ruby utrzymuje zamówienie reklamowe, nie jest zbyt ważne, jaka jest kolejność danych, jeśli utworzysz hash w podanej kolejności; Wymusza kolejność sortując klucze i zregenerować hash, i przekazać, że do JSON:

require 'json' 

puts Hash[{'a' => 1, 'b' => 2}.sort_by{ |a| a }].to_json 
=> {"a":1,"b":2} 

puts Hash[{'b' => 2, 'a' => 1}.sort_by{ |a| a }].to_json 
=> {"a":1,"b":2} 

puts Hash[{'b' => 2, 'c' => 3, 'a' => 1}.sort_by{ |a| a }].to_json 
=> {"a":1,"b":2,"c":3} 

puts Hash[{'b' => 2, 'c' => 3, 'a' => 1}.sort_by{ |a| a }].to_json 
=> {"a":1,"b":2,"c":3} 

puts Hash[{'a' => 1, 'c' => 3, 'b' => 2}.sort_by{ |a| a }].to_json 
=> {"a":1,"b":2,"c":3} 
+0

Utrzymanie zamówienia reklamowego nie jest wystarczające, jeśli te same atrybuty są wstawiane w innej kolejności. (W rzeczywistości te skróty pochodzą ze skryptu w języku Ruby, który używa 'to_json'.) –

+0

" te same atrybuty są wstawiane w innej kolejności ". Zaktualizuj pytanie wyjaśniające, co masz na myśli. –

0

Oto prosty koder JSON w Qt - powinny być stosunkowo łatwe przekształcenie w Javie. Wszystko, co naprawdę musisz zrobić, to upewnić się, że klucze są sortowane podczas wypisywania - można czytać z innym pakietem JSON.

QString QvJson::encodeJson(const QVariant& jsonObject) { 
    QVariant::Type type = jsonObject.type(); 
    switch (type) { 
     case QVariant::Map: 
      return encodeObject(jsonObject); 
     case QVariant::List: 
      return encodeArray(jsonObject); 
     case QVariant::String: 
      return encodeString(jsonObject); 
     case QVariant::Int: 
     case QVariant::Double: 
      return encodeNumeric(jsonObject); 
     case QVariant::Bool: 
      return encodeBool(jsonObject); 
     case QVariant::Invalid: 
      return encodeNull(jsonObject); 
     default: 
      return encodingError("encodeJson", jsonObject, ErrorUnrecognizedObject); 
    } 
} 

QString QvJson::encodeObject(const QVariant& jsonObject) { 
    QString result("{ "); 
    QMap<QString, QVariant> map = jsonObject.toMap(); 
    QMapIterator<QString, QVariant> i(map); 
    while (i.hasNext()) { 
     i.next(); 
     result.append(encodeString(i.key())); 

     result.append(" : "); 

     result.append(encodeJson(i.value())); 

     if (i.hasNext()) { 
      result.append(", "); 
     } 
    } 
    result.append(" }"); 
    return result; 
} 

QString QvJson::encodeArray(const QVariant& jsonObject) { 
    QString result("[ "); 
    QList<QVariant> list = jsonObject.toList(); 
    for (int i = 0; i < list.count(); i++) { 
     result.append(encodeJson(list.at(i))); 
     if (i+1 < list.count()) { 
      result.append(", "); 
     } 
    } 
    result.append(" ]"); 
    return result; 
} 

QString QvJson::encodeString(const QVariant &jsonObject) { 
    return encodeString(jsonObject.toString()); 
} 

QString QvJson::encodeString(const QString& value) { 
    QString result = "\""; 
    for (int i = 0; i < value.count(); i++) { 
     ushort chr = value.at(i).unicode(); 
     if (chr < 32) { 
      switch (chr) { 
       case '\b': 
        result.append("\\b"); 
        break; 
       case '\f': 
        result.append("\\f"); 
        break; 
       case '\n': 
        result.append("\\n"); 
        break; 
       case '\r': 
        result.append("\\r"); 
        break; 
       case '\t': 
        result.append("\\t"); 
        break; 
       default: 
        result.append("\\u"); 
        result.append(QString::number(chr, 16).rightJustified(4, '0')); 
      } // End switch 
     } 
     else if (chr > 255) { 
      result.append("\\u"); 
      result.append(QString::number(chr, 16).rightJustified(4, '0')); 
     } 
     else { 
      result.append(value.at(i)); 
     } 
    } 
    result.append('"'); 
    QString displayResult = result; // For debug, since "result" often doesn't show 
    Q_UNUSED(displayResult); 
    return result; 
} 

QString QvJson::encodeNumeric(const QVariant& jsonObject) { 
    return jsonObject.toString(); 
} 

QString QvJson::encodeBool(const QVariant& jsonObject) { 
    return jsonObject.toString(); 
} 

QString QvJson::encodeNull(const QVariant& jsonObject) { 
    return "null"; 
} 

QString QvJson::encodingError(const QString& method, const QVariant& jsonObject, Error error) { 
    QString text; 
    switch (error) { 
     case ErrorUnrecognizedObject: 
      text = QObject::tr("Unrecognized object type"); 
      break; 
    default: 
      Q_ASSERT(false); 
    } 
    return QObject::tr("*** Error %1 in QvJson::%2 -- %3").arg(error).arg(method).arg(text); 
} 
0

Posortuj klucze obiektów serializowanych przed ich wyprowadzeniem. W Ruby 1.9 hasze są domyślnie sortowane; w Rubim 1.8 nie są. Możesz użyć OrderedHash z active_support, aby mieć pewność w obu przypadkach.

Ilekroć masz zamiar pisać dane JSON, posortuj klucze. Zauważ, że w Ruby 1.8 symbole nie mogą być posortowane, więc musisz zadzwonić pod numer to_s.

require 'rubygems' 
require 'json' 
require 'active_support/ordered_hash' 

obj = { 
    :fig => false, 
    :bananas => false, 
    :apples => true, 
    :eggplant => true, 
    :cantaloupe => true, 
    :dragonfruit => false 
} 

def sorted_hash(hsh) 
    sorted_keys = hsh.keys.sort_by { |k| k.to_s } 
    sorted_keys.inject(ActiveSupport::OrderedHash.new) do |o_hsh, k| 
    o_hsh[k] = hsh[k] 
    o_hsh 
    end 
end 

puts JSON.pretty_generate(obj) 
# Could output in any order, depending on version of Ruby 
# { 
# "eggplant": true, 
# "cantaloupe": true, 
# "dragonfruit": false, 
# "fig": false, 
# "bananas": false, 
# "apples": true 
# } 

puts JSON.pretty_generate(sorted_hash(obj)) 
# Always output in the same order 
# { 
# "apples": true, 
# "bananas": false, 
# "cantaloupe": true, 
# "dragonfruit": false, 
# "eggplant": true, 
# "fig": false 
# } 

Jeśli dane składa się z szeregu obiektów lub obiektów zagnieżdżonych, musisz utworzyć posortowane mieszań rekurencyjnie:

nested_obj = {:a => {:d => true, :b => false}, :e => {:k => false, :f => true}, :c => {:z => false, :o => true}} 

def recursive_sorted_hash(hsh) 
    sorted_keys = hsh.keys.sort_by { |k| k.to_s } 
    sorted_keys.inject(ActiveSupport::OrderedHash.new) do |o_hsh, k| 
    o_hsh[k] = hsh[k].is_a?(Hash) ? recursive_sorted_hash(hsh[k]) : hsh[k] 
    o_hsh 
    end 
end 

puts JSON.pretty_generate(nested_obj) 
# Again, could be in any order 
# { 
# "a": { 
#  "b": false, 
#  "d": true 
# }, 
# "e": { 
#  "f": true, 
#  "k": false 
# }, 
# "c": { 
#  "z": false, 
#  "o": true 
# } 
# } 

puts JSON.pretty_generate(recursive_sorted_hash(nested_obj)) 
# Even nested hashes are in alphabetical order 
# { 
# "a": { 
#  "b": false, 
#  "d": true 
# }, 
# "c": { 
#  "o": true, 
#  "z": false 
# }, 
# "e": { 
#  "f": true, 
#  "k": false 
# } 
# } 
3

Jeśli chcesz przejść przez pewien koszt, dzwoniąc pod numer

gson.toJson(canonicalize(gson.toJsonTree(obj))); 

Następnie można zrobić coś takiego:

protected static JsonElement canonicalize(JsonElement src) { 
    if (src instanceof JsonArray) { 
    // Canonicalize each element of the array 
    JsonArray srcArray = (JsonArray)src; 
    JsonArray result = new JsonArray(); 
    for (int i = 0; i < srcArray.size(); i++) { 
     result.add(canonicalize(srcArray.get(i))); 
    } 
    return result; 
    } else if (src instanceof JsonObject) { 
    // Sort the attributes by name, and the canonicalize each element of the object 
    JsonObject srcObject = (JsonObject)src; 
    JsonObject result = new JsonObject(); 
    TreeSet<String> attributes = new TreeSet<>(); 
    for (Map.Entry<String, JsonElement> entry : srcObject.entrySet()) { 
     attributes.add(entry.getKey()); 
    } 
    for (String attribute : attributes) { 
     result.add(attribute, canonicalize(srcObject.get(attribute))); 
    } 
    return result; 
    } else { 
    return src; 
    } 
} 
Powiązane problemy