Gson:有没有更简单的方法序列化Map?

72

这个链接来自Gson项目,似乎表明我需要像下面这样做才能将一个有类型的Map序列化为JSON:

    public static class NumberTypeAdapter 
      implements JsonSerializer<Number>, JsonDeserializer<Number>,
InstanceCreator<Number> {

    public JsonElement serialize(Number src, Type typeOfSrc, JsonSerializationContext
context) {
      return new JsonPrimitive(src);
    }

    public Number deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context)
        throws JsonParseException {
      JsonPrimitive jsonPrimitive = json.getAsJsonPrimitive();
      if (jsonPrimitive.isNumber()) {
        return jsonPrimitive.getAsNumber();
      } else {
        throw new IllegalStateException("Expected a number field, but was " + json);
      }
    }

    public Number createInstance(Type type) {
      return 1L;
    }
  }

  public static void main(String[] args) {
    Map<String, Number> map = new HashMap<String, Number>();    
    map.put("int", 123);
    map.put("long", 1234567890123456789L);
    map.put("double", 1234.5678D);
    map.put("float", 1.2345F);
    Type mapType = new TypeToken<Map<String, Number>>() {}.getType();

    Gson gson = new GsonBuilder().registerTypeAdapter(Number.class, new
NumberTypeAdapter()).create();
    String json = gson.toJson(map, mapType);
    System.out.println(json);

    Map<String, Number> deserializedMap = gson.fromJson(json, mapType);
    System.out.println(deserializedMap);
  }

很酷并且它能够工作,但是它似乎有太多的开销(整个类型适配器类?)。我已经使用过其他 JSON 库,比如 JSONLib,在以下方式中它们允许你构建一个映射:

JSONObject json = new JSONObject();
for(Entry<String,Integer> entry : map.entrySet()){
     json.put(entry.getKey(), entry.getValue());
}

或者如果我有一个自定义类,例如以下内容:

JSONObject json = new JSONObject();
for(Entry<String,MyClass> entry : map.entrySet()){
 JSONObject myClassJson =  JSONObject.fromObject(entry.getValue());
     json.put(entry.getKey(), myClassJson);
}

这个过程更加手动,但是需要的代码更少,并且没有创建自定义数字类型适配器或在大多数情况下为我自己的自定义类适配器的开销。

这是使用Gson序列化Map的唯一方式吗?还是有人找到了一种超越Gson推荐链接中方法的方式呢?


你使用的是哪个版本的Gson?示例的输出应该是什么(不用担心类型适配器是否由用户提供)? - Programmer Bruce
5个回答

131

只有涉及泛型时,TypeToken 部分是必要的。

Map<String, String> myMap = new HashMap<String, String>();
myMap.put("one", "hello");
myMap.put("two", "world");

Gson gson = new GsonBuilder().create();
String json = gson.toJson(myMap);

System.out.println(json);

Type typeOfHashMap = new TypeToken<Map<String, String>>() { }.getType();
Map<String, String> newMap = gson.fromJson(json, typeOfHashMap); // This type must match TypeToken
System.out.println(newMap.get("one"));
System.out.println(newMap.get("two"));

输出:

{"two":"world","one":"hello"}
hello
world

有时候回答问题时,我会忽略自己的代码。是的,Maps/Lists/etc默认是可序列化的,但你需要在上面使用Type = new TypeToken<T>.getType()语句。对于非原始类型(或原始包装类),你仍然需要创建TypeAdapters。感谢提供正确答案的arturo。 - Ankit Aggarwal
1
@zygimantus TypeToken 类是 public 的,而它的默认构造函数是 protected 的。为了实例化对象,您可以将其扩展为匿名内部类:new TypeToken<...>() {} - Cord Rehn
2
val typeOfHashMap = object: TypeToken<HashMap<String,String>>() {}.type // 在 Kotlin 中 - Juan Mendez
这个有什么底层的 Map 实现?(HashMap 等?) - nilanjanaLodh
实现是com.google.gson.internal.LinkedTreeMap - Guillaume Perrot
有没有办法让Gson创建默认的HashMap而不是他们内置的LinkedTreeMap? - Shmuel Newmark

101

默认值

Gson对Map的默认序列化实现使用键的toString()方法:

Gson gson = new GsonBuilder()
        .setPrettyPrinting().create();
Map<Point, String> original = new HashMap<>();
original.put(new Point(1, 2), "a");
original.put(new Point(3, 4), "b");
System.out.println(gson.toJson(original));

将会给:

{
  "java.awt.Point[x\u003d1,y\u003d2]": "a",
  "java.awt.Point[x\u003d3,y\u003d4]": "b"
}

使用enableComplexMapKeySerialization

如果您想要按照默认的Gson规则序列化Map Key,可以使用enableComplexMapKeySerialization。这将返回一个键值对数组的数组:


Gson gson = new GsonBuilder().enableComplexMapKeySerialization()
        .setPrettyPrinting().create();
Map<Point, String> original = new HashMap<>();
original.put(new Point(1, 2), "a");
original.put(new Point(3, 4), "b");
System.out.println(gson.toJson(original));

将返回:

[
  [
    {
      "x": 1,
      "y": 2
    },
    "a"
  ],
  [
    {
      "x": 3,
      "y": 4
    },
    "b"
  ]
]

更多细节可以在这里找到。


4
为什么复杂的映射键序列化不是默认行为? - AlexSee
@AlexSee 我给你的评论点了赞,但我猜这个功能只影响键名,因为它的名称暗示着这可能是事后添加的,因为只有在从POJO转换为JSON时才需要它,反之则不然。 - Lorenzo

6
在Gson 2.7.2中,这很容易,只需要:
Gson gson = new Gson();
String serialized = gson.toJson(map);

3
仅适用于原始数据类型。 对于像示例中那样的复杂类型,您需要使用马特·伯恩斯的答案。 - jcomouth

4
我非常确定 GSON 默认可以对 Map 和多层嵌套的 Maps (例如 Map<String, Map<String, Object>>)进行序列化/反序列化。所提供的示例只是一个起点,如果您需要执行更复杂的操作,请查看 GSON 源代码中的 MapTypeAdapterFactory 类:http://code.google.com/p/google-gson/source/browse/trunk/gson/src/main/java/com/google/gson/internal/bind/MapTypeAdapterFactory.java。只要键和值的类型可以序列化为 JSON 字符串(并且您可以为这些自定义对象创建自己的序列化程序/反序列化程序),就不应该有任何问题。

-4
Map<String, Object> config = gson.fromJson(reader, Map.class);

5
这是关于反序列化而不是我的问题所述的序列化方法。 - stevebot

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接