【问题标题】:How to serialize a Map of a Map with GSON?如何使用 GSON 序列化 Map 的 Map?
【发布时间】:2011-05-31 16:04:37
【问题描述】:

我想使用 GSON 将下面的示例类序列化为 JSON。

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.LinkedHashMap;

public class Example
{
    private LinkedHashMap<String,Object> General;

    private static final String VERSION="Version";
    private static final String RANGE="Range";
    private static final String START_TIME="Start_Time";
    private static final String END_TIME="End_Time";

    public Example() {
        General = new LinkedHashMap<String,Object>();
        General.put(VERSION, "0.1");

        LinkedHashMap<String,String> Range = new LinkedHashMap<String, String>();
        Range.put(START_TIME, "now");
        Range.put(END_TIME, "never");

        General.put(RANGE, Range);
    }

    public String toJSON() {
        Gson gson = new GsonBuilder().serializeNulls().create();
        return gson.toJson(this);
    }
}

我希望得到以下输出:

{"General":{"Version":"0.1","Range":{"Start_Time":"now","End_Time":"never"}}}

但是调用函数toJSON()会返回

{"General":{"Version":"0.1","Range":{}}}

似乎 GSON 无法序列化 Map Range 内的 Map General。这是 GSON 的限制还是我在这里做错了什么?

【问题讨论】:

  • 如果其他答案正确且默认 Map 序列化程序无法处理嵌套地图,我会向 GSON 团队提交错误报告。对我来说,这似乎是非常糟糕的默认值;至少它应该抛出异常而不是悄悄吞下内容。
  • +1 @StaxMan 好像是这样。我之前看到过这个,想出了一些解决方法。如果有某种方法可以做到这一点,那么值得记录。

标签: java json map gson


【解决方案1】:

Nishant's answer 起作用的原因是因为 Gson 的默认构造函数默认启用所有类型的东西,否则您必须手动启用使用 GsonBuilder。

来自JavaDocs

使用默认配置构造一个 Gson 对象。默认配置有以下设置:

  • toJson 方法生成的 JSON 采用紧凑的表示形式。这意味着所有不需要的空白都被删除。您可以使用 GsonBuilder.setPrettyPrinting() 更改此行为。
  • 生成的 JSON 会省略所有为空的字段。请注意,数组中的空值保持原样,因为数组是有序列表。此外,如果一个字段不为空,但其生成的 JSON 为空,则保留该字段。您可以通过设置 GsonBuilder.serializeNulls() 来配置 Gson 以序列化空值。
  • Gson 为 Enums、Map、java.net.URL、java.net.URI、java.util.Locale、java.util.Date、java.math.BigDecimal 和 java.math.BigInteger 提供默认的序列化和反序列化类。如果您希望更改默认表示,可以通过 GsonBuilder.registerTypeAdapter(Type, Object) 注册类型适配器来实现。
  • 默认日期格式与 java.text.DateFormat.DEFAULT 相同。此格式在序列化期间忽略日期的毫秒部分。您可以通过调用 GsonBuilder.setDateFormat(int) 或 GsonBuilder.setDateFormat(String) 来更改此设置。
  • 默认情况下,Gson 忽略 com.google.gson.annotations.Expose 注释。您可以通过 GsonBuilder.excludeFieldsWithoutExposeAnnotation() 使 Gson 仅对标有此注解的字段进行序列化/反序列化。
  • 默认情况下,Gson 忽略 com.google.gson.annotations.Since 注释。您可以通过 GsonBuilder.setVersion(double) 让 Gson 使用此注解。
  • 输出 Json 的默认字段命名策略与 Java 中的相同。因此,Java 类字段 versionNumber 将在 Json 中输出为“versionNumber@”。将传入的 Json 映射到 Java 类应用相同的规则。您可以通过 GsonBuilder.setFieldNamingPolicy(FieldNamingPolicy) 更改此策略。
  • 默认情况下,Gson 不考虑序列化和反序列化的瞬态或静态字段。您可以通过 GsonBuilder.excludeFieldsWithModifiers(int) 更改此行为。

好的,现在我明白问题所在了。正如您所料,默认的 Map 序列化程序不支持嵌套映射。正如您在this source snippet from DefaultTypeAdapters 中看到的(特别是如果您使用调试器单步执行),变量childGenericType 出于某种神秘原因被设置为java.lang.Object 类型,因此永远不会分析该值的运行时类型。

我猜有两种解决方案:

  1. Implement your own Map serializer / deserializer
  2. 使用更复杂的方法版本,如下所示:

    public String toJSON(){
        final Gson gson = new Gson();
        final JsonElement jsonTree = gson.toJsonTree(General, Map.class);
        final JsonObject jsonObject = new JsonObject();
        jsonObject.add("General", jsonTree);
        return jsonObject.toString();
    }
    

【讨论】:

  • 对不起,我对 Nishant 的回答的第一条评论是错误的。他的建议对我来说并不完全奏效。
  • @asmaier 好的,在我的回答中添加了更多内容
  • 奇怪的是,如果你用 ImmutableMap 替换 LinkedHashMap 就可以正常工作。
【解决方案2】:

试试这个:

Gson gson = new Gson();
System.out.println(gson.toJson(General));

不确定您是否仍在寻找解决方案,这对我有用:

import java.util.LinkedHashMap;

import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class Example {
//  private static LinkedHashMap<String,Object> General;
    private ImmutableMap General;

    private static final String VERSION="Version";
    private static final String RANGE="Range";
    private static final String START_TIME="Start_Time";
    private static final String END_TIME="End_Time";

    public Example() {

        LinkedHashMap<String,String> Range = new LinkedHashMap<String, String>();
        Range.put(START_TIME, "now");
        Range.put(END_TIME, "never");

//        General.put(RANGE, Range);
        General = ImmutableMap.of(VERSION, "0.1", RANGE, Range);
    }

    public String toJSON() {
//        Gson gson = new GsonBuilder().serializeNulls().create();
          Gson gson = new Gson();
          return gson.toJson(this);
    }

}

返回:{"General":{"Version":"0.1","Range":{"Start_Time":"now","End_Time":"never"}}}


显然你可以改用ImmutableMap.copyOf(your_hashmap)here

【讨论】:

  • 它有效,但为什么呢?也有点不方便。如果我想在我的类示例中添加更多地图,我必须在我的函数 toJSON() 中编写类似 String out=gson.toJson(this.General); out+=gson.toJson(this.Map2); out+=gson.toJson(this.map3); ... return out; 的内容
  • 不确定哥们。我有这个问题,我走了这条路。我认为原因是这个sites.google.com/site/gson/…
  • 对不起,我的第一条评论是错误的,我不能再编辑它了。使用gson.toJson(General) 返回{"Version":"0.1","Range":{"Start_Time":"bla","End_Time":"blu"},"Checksum":"+1"}。但我想拥有 {"General": {"Version":"0.1",.... 。所以你的解决方案对我来说并不完全有效。
  • 奇怪的是,我已经写下了代码并执行了。我没有看到校验和。它是来自某个地方的场吗?输出为{"Version":"0.1","Range":{"Start_Time":"now","End_Time":"never"}},但这显然不能解决您的问题。
  • 很好地编辑了一些额外的东西。但是,无法真正了解不可变如何完成工作
【解决方案3】:

更简单的替代方法是使用 Jackson 而不是 GSON,嵌套地图的序列化开箱即用:

    LinkedHashMap<String, Object> general;
    general = new LinkedHashMap<String, Object>();
    general.put("Version", "0.1");

    LinkedHashMap<String, String> Range = new LinkedHashMap<String, String>();
    Range.put("Start_Time", "now");
    Range.put("End_Time", "never");

    general.put("Range", Range);

    // Serialize the map to json using Jackson
    ByteArrayOutputStream os = new ByteArrayOutputStream();
    new org.codehaus.jackson.map.ObjectMapper().writer().writeValue(os,
            general);       
    String json = os.toString();
    os.close();

    System.out.println(json);

输出:

{"Version":"0.1","Range":{"Start_Time":"now","End_Time":"never"}}

【讨论】:

    猜你喜欢
    • 2013-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-25
    • 2017-04-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多