Canonisation des fichiers JSON

j'ai un tas de fichiers JSON générés automatiquement que je veux stocker dans le contrôle de version. Le problème est que chaque fois que les fichiers sont sérialisés, les attributs sortent dans un ordre différent, ce qui rend difficile de savoir si les fichiers ont vraiment changé et / ou quelles sont les différences réelles.

est-ce que quelqu'un connaît un outil open source existant qui effectuera cette tâche?

A défaut, est-ce que quelqu'un connaît une bibliothèque JSON avec un analyseur et un générateur qui peut-être configuré pour afficher "pretty" JSON avec les attributs dans l'ordre lexical? (Une bibliothèque Java ou Ruby serait idéale, mais d'autres pistes sont également les bienvenues.)

24
demandé sur Stephen C 2012-09-25 18:09:07
la source

8 ответов

module JSON de Python est très utilisable à partir d'autres programmes:

generate_json | python -mjson.tool > canonical.json
15
répondu Martin Vidner 2012-10-27 00:39:57
la source

Si vous êtes prêt à passer par un peu de surcharge en appelant

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

Ensuite, vous pouvez faire quelque chose comme ceci:

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;
  }
}
3
répondu Andrew Sacamano 2016-02-06 17:23:23
la source
1
répondu user1546081 2016-10-09 08:09:03
la source

L'open source en Java library Jackson peut prendre un certain effort pour mettre en place, mais il est capable de jolie impression et a une très soigné @JsonPropertyOrder annotation supportant l'ordre alphabétique ou l'ordre de sortie spécifié manuellement.

0
répondu Gustav Barkefors 2012-09-25 18:20:32
la source

je n'ai pas essayé beaucoup de combinaisons, mais il semble que l' google-gson conserve l'ordre des attributs dans JSON.

supprimé un exemple ici, il n'était pas pertinente

je sais d'expérience dans des projets antérieurs, qu'il est extrêmement personnalisable, par exemple si le objet de base n'est pas suffisant, on peut utiliser GsonBuilder pour créer des adaptateurs plus compliqués.

je n'ai pas cependant testé en détail avec votre cas d'utilisation, mais il devrait être simple de vérifier si elle a la sortie attendue

UPDATE

plutôt que D'utiliser SVN/CVS pour vérifier si vos fichiers ont été modifiés, J'ai trouvé que GSON a intégré gestion des versions qui peuvent ou non répondre à votre question, à partir de leur docs:

plusieurs versions D'un même objet peuvent être maintenues en utilisant @Depuis annotation. Ce l'annotation peut être utilisée sur les Classes, les champs et, dans une version future, Les méthodes. Pour tirer parti de cette fonctionnalité, vous devez configurer votre instance Gson pour ignorer tout champ/objet qui est supérieur à un numéro de version. Si aucune version n'est définie sur l'instance Gson, elle sérialisera et désérialisera tous les champs et classes, quelle que soit la version.

UPDATE

la seule autre chose qui me vient à l'esprit est l'analyse de votre les fichiers avec rhino et en utilisant JSON.stringify pour convertir les analysée JSON retour à une chaîne, alors vous pouvez être sûr qu'il a fonctionné à travers un seul 'parser' et la sortie ne sera pas différente.

vous pouvez alors détecter tout changement possible.

0
répondu epoch 2012-09-25 22:24:22
la source

Ruby 1.9 + maintient l'ordre d'insertion des hachures, et JSON pour 1.9+ honore cela.

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

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

Voici comment générer un "joli" 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
}

... les mêmes attributs sont insérées dans un ordre différent...

cela n'a pas beaucoup de sens pour moi, mais je vais tenter ma chance.

parce que Ruby maintient l'ordre d'insertion, ce n'est pas très important quel est l'ordre des données si vous créez le hachage dans un ordre donné; forcer l'ordre en triant les touches et régénérer le hachage, et passer cela à 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
répondu the Tin Man 2012-09-26 05:08:12
la source

voici un encodeur JSON simple dans Qt -- devrait être relativement facile à reformater en Java. Tout ce que vous devez vraiment faire est de vous assurer que les clés sont triées lors de l'écriture -- peut lire avec un autre paquet 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
répondu Hot Licks 2012-09-26 05:33:42
la source

triez les clés des objets que vous sérialisez avant de les afficher. Dans Ruby 1.9, les hashs sont ordonnés par défaut; dans Ruby 1.8, ils ne le sont pas. Vous pouvez utiliser OrderedHash de active_support pour être sûr dans les deux cas.

chaque fois que vous allez écrire vos données JSON, triez les clés. Notez que dans Ruby 1.8, les symboles ne peuvent pas être triés, donc vous devez appeler to_s dans ton genre.

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
# }

Si vos données se compose d'un tableau d'objets ou des objets imbriqués, vous aurez besoin de créer triés hachages de manière récursive:

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
#   }
# }
0
répondu Ryan 2012-10-13 11:20:50
la source

Autres questions sur