代码之家  ›  专栏  ›  技术社区  ›  Stephen C

规范化JSON文件

  •  33
  • Stephen C  · 技术社区  · 13 年前

    我有一堆自动生成的JSON文件,我想将它们存储在版本控制中。问题是,每次序列化文件时,属性都会以不同的顺序出现,因此很难知道文件是否真的发生了变化和/或真正的差异是什么。

    有人知道现有的开源工具可以执行这项任务吗?

    如果做不到这一点,有人知道有一个JSON库,它有一个解析器和一个生成器,可以配置为输出具有(比如)词法顺序属性的“漂亮”JSON吗?(Java或Ruby库是理想的选择,但也欢迎其他线索。)

    8 回复  |  直到 13 年前
        1
  •  16
  •   Martin Vidner    13 年前

    Python's JSON module 在其他程序中非常有用:

    generate_json | python -mjson.tool > canonical.json
    
        2
  •  4
  •   Andrew Sacamano    10 年前

    如果你愿意通过打电话来节省一些开销

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

    然后你可以这样做:

    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
  •  1
  •   user1546081    9 年前
        4
  •  0
  •   Gustav Barkefors    13 年前

    开源Java库 Jackson 可能需要一些努力来设置,但能够进行漂亮的打印,并且有一个非常整洁的 @JsonPropertyOrder 支持字母或手动指定输出顺序的注释。

        5
  •  0
  •   epoch    13 年前

    我没有尝试过很多组合,但看起来 google-gson 保持属性的顺序 JSON

    删除了此处的一个示例,因为它不再相关

    我从以前项目的经验中知道,它是非常可定制的,例如,如果 base object 不够,可以使用 GsonBuilder 以创建更复杂的适配器。

    然而,我还没有在您的用例中对此进行广泛的测试,但检查它是否具有预期的输出应该很简单

    更新

    我发现GSON内置了 versioning support 可能会也可能不会从他们的文档中解决您的问题:

    使用可以维护同一对象的多个版本 @Since 注释。此注释可以用于类、字段,在将来的版本中还可以用于方法。为了利用此功能,您必须将Gson实例配置为忽略任何大于某个版本号的字段/对象。如果没有在Gson实例上设置版本,那么无论版本如何,它都将序列化和反序列化所有字段和类。

    更新

    我唯一能想到的就是用 rhino 和使用 JSON.stringify 转换已解析的 JSON语言 回到一个字符串,那么您可以确保它已经通过一个“解析器”运行,并且输出不会有什么不同。

    然后,您可以检测到任何可能的更改。

        6
  •  0
  •   the Tin Man    13 年前

    Ruby1.9+维护散列的插入顺序,而JSON for 1.9+则遵循这一点。

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

    以下是如何生成“漂亮”的格式:

    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
    }
    

    …相同的属性以不同的顺序插入。。。

    这对我来说没有多大意义,但我要试一试。

    因为Ruby维护插入顺序,所以如果按照给定的顺序创建散列,那么数据的顺序就不太重要了;通过对键进行排序并重新生成哈希来强制排序,然后将其传递给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}
    
        7
  •  0
  •   Hot Licks    13 年前

    这里有一个Qt中的简单JSON编码器——应该相对容易重新编译成Java。您真正需要做的就是确保在编写时对键进行排序——可以使用另一个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);
    }
    
        8
  •  0
  •   Ryan    13 年前

    对正在序列化的对象的键进行排序,然后再输出它们。在Ruby 1.9中,散列是默认排序的;在Ruby 1.8中,它们不是。在任何一种情况下,您都可以使用active_support中的OrderedHash进行确认。

    无论何时编写JSON数据,都要对键进行排序。请注意,在Ruby1.8中,符号无法排序,因此必须调用 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
    # }
    

    如果您的数据由对象数组或嵌套对象组成,则需要递归地创建排序哈希:

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