使用Gson反序列化映射键需要一个对象

时间:2014-01-18 20:07:26

标签: java json serialization deserialization gson

我收到错误:

Exception in thread "main" com.google.gson.JsonParseException: 
Expecting object found: "com.shagie.app.SimpleMap$Data@24a37368"

尝试解除使用非平凡键的Map时:

package com.shagie.app;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import java.util.HashMap;

public class SimpleMap {
    public static void main(String[] args) {
        Wrapper w = new Wrapper();
        w.m.put(new Data("f", 1), new Data("foo", 3));
        w.m.put(new Data("b", 2), new Data("bar", 4));

        GsonBuilder gb = new GsonBuilder();
        gb.setPrettyPrinting();
        Gson g = gb.create();

        String json = g.toJson(w);

        System.out.println(json);

        w = g.fromJson(json, Wrapper.class);
        System.out.println(w.m.isEmpty());
    }

    static public class Wrapper {
        HashMap<Data, Data> m = new HashMap<Data, Data>();
    }

    static public class Data {
        String s;
        Integer i;
        public Data(String arg, Integer val) { s = arg; i = val; }
    }
}

这序列化为json:

{
  "m": {
    "com.shagie.app.SimpleMap$Data@24a37368": {
      "s": "foo",
      "i": 3
    },
    "com.shagie.app.SimpleMap$Data@66edc3a2": {
      "s": "bar",
      "i": 4
    }
  }
}

可以看到尝试序列化的密钥,但肯定不是可以反序列化的。

如何序列化此对象以便可以反序列化?

3 个答案:

答案 0 :(得分:10)

我在尝试解决这个难题时发现了以下内容:Issue 210: Cannot serialize or deserialize Maps with complex keys

  

对于未来的任何互联网旅行者(如我自己)...您可以使用GsonBuilder上的enableComplexMapKeySerialization()方法在GSON 2. *中启用此功能。

这是javadoc for that method

启用后,地图将作为[key,value]数组的数组序列化(并正确反序列化):

{"m":[[{"s":"f", "i",1}, {"s":"foo", "i":3}], [{"s":"b", "i",2}, {"s":"bar", "i":4}]]}

答案 1 :(得分:3)

问题是toString()正在调用地图的键,而不是自己序列化。

要解决此问题,需要设置自定义序列化程序和反序列化程序,反序列化程序需要知道对象用于将自身显示为字符串的格式({{1} }方法必须返回一个可用于重建整个对象的字符串。)

对于上面的例子:

toString()

运行此代码会产生:

serialize called: {"s":"foo","i":3}
toString called: 1:f
serialize called: {"s":"bar","i":4}
toString called: 2:b
{
  "m": {
    "1:f": {
      "s": "foo",
      "i": 3
    },
    "2:b": {
      "s": "bar",
      "i": 4
    }
  }
}
deserialize called with: "1:f"
deserialize returns: f 1
deserialize called with: {"s":"foo","i":3}
deserialize returns: foo 3
deserialize called with: "2:b"
deserialize returns: b 2
deserialize called with: {"s":"bar","i":4}
deserialize returns: bar 4

请注意package com.shagie.app; import com.google.gson.*; import java.lang.reflect.Type; import java.util.HashMap; public class SimpleMapFixed { public static void main(String[] args) { Wrapper w = new Wrapper(); w.m.put(new Data("f", 1), new Data("foo", 3)); w.m.put(new Data("b", 2), new Data("bar", 4)); GsonBuilder gb = new GsonBuilder(); gb.setPrettyPrinting(); gb.registerTypeAdapter(Data.class, new DataSerializer()); Gson g = gb.create(); String json = g.toJson(w); System.out.println(json); w = g.fromJson(json, Wrapper.class); System.out.println(w.m.isEmpty()); } static public class Wrapper { HashMap<Data, Data> m = new HashMap<Data, Data>(); } static public class DataSerializer implements JsonSerializer<Data>, JsonDeserializer<Data> { @Override public Data deserialize(JsonElement je, Type t, JsonDeserializationContext ctx) throws JsonParseException { Data rv; JsonObject jo; System.out.println("deserialize called with: " + je.toString()); if (je.isJsonObject()) { jo = je.getAsJsonObject(); rv = new Data(jo.get("s").getAsString(), jo.get("i").getAsInt()); } else { String js = je.getAsString(); String[] s = js.split(":", 2); // split into two (and only two) rv = new Data(s[1], Integer.valueOf(s[0])); } System.out.println("deserialize returns: " + rv.s + " " + rv.i); return rv; } @Override public JsonElement serialize(Data data, Type type, JsonSerializationContext jsonSerializationContext) { JsonObject jo = new JsonObject(); jo.addProperty("s", data.s); jo.addProperty("i", data.i); System.out.println("serialize called: " + jo.toString()); return jo; } } static public class Data { String s; Integer i; public Data(String arg, Integer val) { s = arg; i = val; } @Override public String toString() { String rv = i.toString() + ':' + s; System.out.println("toString called: " + rv); return rv; } } } 的调用作为序列化的一部分。在这段代码中,来自String表单的反序列化逻辑在toString()中,尽管将它作为另一个构造函数移入DataSerializer类可能是有意义的 - 它不会影响最终结果

进一步注意Data本身是一个相当简单的对象,没有更深层次的结构。试图将其序列化为密钥需要额外的工作。

答案 2 :(得分:0)

由您决定如何维护HahMap Keys,您可以通过简单,最简单的方式对其进行反序列化。

final Type typeOf = new TypeToken <Map<String, Map<String, Data>>>(){}.getType();
final  Map<String, Map<String, Data>> newMap = gson.fromJson(json, typeOf);
final Map<String, Data> map = newMap.get("m");
final Iterator<Entry<String, Data>> it = map.entrySet().iterator();

while (it.hasNext()) {
   Map.Entry<String,Data> pair = (Map.Entry<String,Data>) it.next();
   String key = pair.getKey();
   System.out.println("key  "+ key + " Values[  i= " + data.getI() + ", s= " +data.getS()+" ]");
       }

<强>结果:

key = snippet.Snippet$Data@61506150值[i = 3,s = foo]

key = snippet.Snippet$Data@63ff63ff值[i = 4,s = bar]