【问题标题】:Canonicalizing JSON files规范化 JSON 文件
【发布时间】:2012-09-17 01:50:02
【问题描述】:

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

有谁知道可以执行此任务的现有开源工具吗?

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

【问题讨论】:

    标签: java ruby json


    【解决方案1】:

    【讨论】:

      【解决方案2】:

      如果你愿意通过调用来承担一些开销

      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】:

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

        generate_json | python -mjson.tool > canonical.json
        

        【讨论】:

        • 注意:这是因为 sort_keys=Truejson.tool 的实现中有效,但这种保证似乎没有在任何地方记录,因此可能值得编写自己的脚本来保证:import json, sys; print json.dumps(json.load(sys.stdin), sort_keys=True)
        【解决方案4】:

        在输出之前对要序列化的对象的键进行排序。在 Ruby 1.9 中,哈希是默认排序的;在 Ruby 1.8 中它们不是。无论哪种情况,您都可以使用来自 active_support 的 OrderedHash。

        每当您要编写 JSON 数据时,对键进行排序。请注意,在 Ruby 1.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
        #   }
        # }
        

        【讨论】:

          【解决方案5】:

          这是 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);
          }
          

          【讨论】:

            【解决方案6】:

            Ruby 1.9+ 维护哈希的插入顺序,1.9+ 的 JSON 遵循这一点。

            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}
            

            【讨论】:

            • 如果以不同的顺序插入相同的属性,则保持插入顺序是不够的。 (事实上​​,这些哈希来自使用 to_json 的 Ruby 脚本。)
            • “相同的属性以不同的顺序插入”。请更新您的问题,解释您的意思。
            【解决方案7】:

            我没有尝试过很多组合,但似乎google-gson 保持JSON 中的属性顺序。

            这里删除了一个示例,因为它不再相关

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

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

            更新

            我发现 GSON 内置了 versioning support,而不是使用 SVN/CVS 检查您的文件是否被修改,这可能会或可能不会解决您的问题,来自他们的文档:

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

            更新

            我能想到的唯一另一件事是使用rhino 解析您的外部文件并使用JSON.stringify 将解析后的JSON 转换回字符串,然后您可以确定它已经通过单个“解析器” ' 并且输出不会不同。

            然后您可以检测任何可能的变化。

            【讨论】:

            • @Since 注释不太可能有帮助。这些 JSON 文件来自外部源(不是 Java)。
            • @StephenC,我现在更清楚地了解您的问题,请参阅我的更新
            【解决方案8】:

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

            【讨论】:

            • 该注解能否用于您作为TreeNode 实例树加载的JSON?我没有 Java Pojo 类……或 JSON 模式可玩。这些是具有不可预测结构和内容的通用 JSON 文件。
            • 不幸的是,我没有走那条路。我的答案中的“可能需要一些努力”部分是考虑到 POJO 定义和杰克逊的 SerializationConfig 机制。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2017-03-11
            • 1970-01-01
            • 2021-07-21
            • 1970-01-01
            • 2011-11-14
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多