【问题标题】:How to keep fields sequence in Gson serialization如何在 Gson 序列化中保持字段顺序
【发布时间】:2011-06-16 00:15:16
【问题描述】:

似乎Gson.toJson(Object object) 生成带有随机分布的对象字段的 JSON 代码。有没有办法以某种方式修复字段顺序?

public class Foo {
    public String bar;
    public String baz;
    public Foo( String bar, String baz ) {
        this.bar = bar;
        this.baz = baz;
    }
}
Gson gson = new Gson();
String jsonRequest = gson.toJson(new Foo("bar","baz"));

字符串jsonRequest可以是:

  • { "bar":"bar", "baz":"baz" }(正确)
  • { "baz":"baz", "bar":"bar" }(顺序错误)

【问题讨论】:

  • 为什么序列相关?这表明解析 JSON 的工具有误。
  • 我也想知道为什么。但服务器是一个 .NET 应用程序。该应用程序的作者说他们需要名为“__type”(它指向模式)的字段排在首位。
  • 我会请求 .NET 应用程序作者放弃此要求并实施更强大的 JSON 处理。
  • 如果你有机会,你也可以用 JSON 规范来打击 .NET 应用程序的作者,关于 JSON 对象是什么,它清楚地指出,“一个对象是零或零的无序集合更多名称/值对..." [ietf.org/rfc/rfc4627.txt?number=4627] 因此,他们的应用程序/消息传递 API 不符合 JSON 规范。 (这样的论点往往特别适合那些花哨的“企业架构师”。)
  • 也许如果你想要元数据在一开始你会想要这个。

标签: java json serialization gson


【解决方案1】:

您需要创建一个自定义 JSON 序列化程序。

例如

public class FooJsonSerializer implements JsonSerializer<Foo> {

    @Override
    public JsonElement serialize(Foo foo, Type type, JsonSerializationContext context) {
        JsonObject object = new JsonObject();
        object.add("bar", context.serialize(foo.getBar());
        object.add("baz", context.serialize(foo.getBaz());
        // ...
        return object;
    }

}

并按如下方式使用:

Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class, new FooJsonSerializer()).create();
String json = gson.toJson(foo);
// ...

这将保持您在序列化程序中指定的顺序。

另见:

【讨论】:

  • 不幸的是,这种方法的工作原理不在 JsonObject.add(String, JsonElement) 方法的文档中(或在 addProperty 方法的文档中),而是在 entrySet( ) 方法! (感兴趣的人:提供此功能的底层实现使用 LinkedHashMap 存储添加的元素。)
  • 谢谢。但是在我的情况下,解决方案变得太重了。我更喜欢@Programmer Bruce 推荐的 JsonWriter。是的,对 .NET json 解析器感到羞耻。
  • @romanovsky 太重了?也许你正在做一些有更简单方法的事情。在几乎所有情况下,我更喜欢使用自定义反序列化器而不是直接使用 JsonWriter。对于您描述的特定问题,我将 BalusC 的解决方案更进一步,使其更通用并使元素顺序在外部可配置。然后可以将其重用于不同的对象。
  • 这是一个很好的解决方案。我想要这样的东西,这个解决方案真的帮助了我。谢谢队友:)
  • 很好的答案。 +1 我们可以根据对象组合在 GsonBuilder 中注册多个适配器。 GsonBuilder gson = new GsonBuilder(); gson.registerTypeAdapter(MyType2.class, new MyTypeAdapter()); gson.registerTypeAdapter(MyType.class, new MySerializer()); gson.registerTypeAdapter(MyType.class, new MyDeserializer()); gson.registerTypeAdapter(MyType.class, new MyInstanceCreator());
【解决方案2】:

如果 GSON 不支持定义字段顺序,则有其他库支持。例如,Jackson 允许使用 @JsonPropertyOrder 定义它。对我来说,必须指定自己的自定义序列化程序似乎需要做很多工作。

是的,我同意根据 JSON 规范,应用程序不应期望特定的字段顺序。

【讨论】:

  • 这就是我需要的东西。在这种情况下它应该工作得最好(即简单的对象序列化,强制一个属性成为另一侧的 .NET 解析器的第一个属性)。谢谢@StaxMan。
  • 我正在使用 Jackson 将一些 Bean 序列化为 JSON 以进行持久性和测试。对格式的更改使得很难针对序列化字符串编写断言。这只是使我的测试有效的药物=)
  • @Spina 你永远不应该将 JSON 输出与硬编码的字符串进行比较,那只是自找麻烦。除了可能改变空白或转义方面的排序之外,还可以合法地改变并且不改变逻辑内容。因此,要么比较应该强制执行某种规范(据我所知,没有为 JSON 指定标准),要么它应该使用 JSON 对象模型(更好的选择)。例如,杰克逊有JsonNode,它的equals() 非常适合测试;忽略属性的顺序,值是统一的。
  • @StaxMan 虽然我同意您对生产代码的说法,但对于单元测试(和某些集成测试),我认为它是编写断言的一种方便且简单的方法。
  • @Spina 好的,对于可能就足够的简单测试。意识到诸如排序之类的问题仍然很好,但至少在测试中可以完全控制输入,从而避免大多数问题。
【解决方案3】:

实际上 Gson.toJson(Object object) 不会以随机顺序生成字段。结果 json 的顺序取决于字段名称的文字顺序。

我遇到了同样的问题,它是通过类中属性名称的字面顺序来解决的。

问题中的示例将始终返回以下 jsonRequest:

{ "bar":"bar", "baz":"baz" }

为了有一个特定的顺序,你应该修改字段的名称,例如:如果你想让baz 排在第一位,那么bar

public class Foo {
    public String f1_baz;
    public String f2_bar;

    public Foo ( String f1_baz, String f2_bar ) {
        this.f1_baz = f1_baz;
        this.f2_bar = f2_bar;
    }
}

jsonRequest 将是{ "f1_baz ":"baz", "f2_bar":"bar" }

【讨论】:

  • 需要详细说明吗?
  • @FranMarzoa 答案已修改
  • 感谢,感谢您努力改进它。顺便说一句,我想知道 SerializedName 注释名称是否会改变该顺序。
【解决方案4】:

这是我在给定目录中循环遍历 json 文本文件并使用排序版本覆盖它们顶部的解决方案:

private void standardizeFormat(File dir) throws IOException {
    File[] directoryListing = dir.listFiles();
    if (directoryListing != null) {
        for (File child : directoryListing) {
            String path = child.getPath();
            JsonReader jsonReader = new JsonReader(new FileReader(path));

            Gson gson = new GsonBuilder().setPrettyPrinting().registerTypeAdapter(LinkedTreeMap.class, new SortedJsonSerializer()).create();
            Object data = gson.fromJson(jsonReader, Object.class);
            JsonWriter jsonWriter = new JsonWriter(new FileWriter(path));
            jsonWriter.setIndent("  ");
            gson.toJson(data, Object.class, jsonWriter);
            jsonWriter.close();
        }
    }
}

private class SortedJsonSerializer implements JsonSerializer<LinkedTreeMap> {
    @Override
    public JsonElement serialize(LinkedTreeMap foo, Type type, JsonSerializationContext context) {
        JsonObject object = new JsonObject();
        TreeSet sorted = Sets.newTreeSet(foo.keySet());
        for (Object key : sorted) {
            object.add((String) key, context.serialize(foo.get(key)));
        }
        return object;
    }
}

这很 hacky,因为它取决于 Gson 在 Type 只是 Object 时使用 LinkedTreeMap 的事实。这是一个可能无法保证的实现细节。无论如何,这对于我短暂的目的来说已经足够了......

【讨论】:

  • 效果很好!只要 Gson 在内部使用 LinkedTreeMap 就可以了。
猜你喜欢
  • 1970-01-01
  • 2014-10-29
  • 1970-01-01
  • 2012-10-13
  • 2014-07-30
  • 2022-12-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多