使用Gson反序列化Map键时需要一个对象

5

我收到了以下错误信息:

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

当尝试反序列化使用非平凡(non-trivial)键的 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
    }
  }
}

可以看到尝试进行序列化的键,但显然无法反序列化。

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


1
虽然我找到了一个可行的解决方案,但我并不确定它是否是最优解,或者API中是否有我忽略的东西会导致GSON无法按照我真正想要的方式运行。 - user289086
3个回答

13

在尝试解决这个难题时,我发现以下内容:问题 210: 无法序列化或反序列化具有复杂键的映射

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

这是该方法的javadoc

启用后,该映射将被序列化(并正确反序列化)为[key,value]数组的数组:

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

这是“唯一”的解决方案。谢谢。 - mauretto

4
问题在于调用了映射表中的键的toString()方法,而没有对它们进行序列化。

为了解决这个问题,需要设置自定义的序列化器和反序列化器,并且反序列化器需要知道对象显示为字符串的格式(toString()方法必须返回可用于重构整个对象的字符串)。

针对上面的示例:

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;
        }
    }
}

运行这段代码会产生以下结果:
调用serialize函数: {"s":"foo","i":3}
调用toString函数: 1:f
调用serialize函数: {"s":"bar","i":4}
调用toString函数: 2:b
{
  "m": {
    "1:f": {
      "s": "foo",
      "i": 3
    },
    "2:b": {
      "s": "bar",
      "i": 4
    }
  }
}
调用deserialize函数,参数为:"1:f"
返回值为:f 1
调用deserialize函数,参数为:{"s":"foo","i":3}
返回值为:foo 3
调用deserialize函数,参数为:"2:b"
返回值为:b 2
调用deserialize函数,参数为:{"s":"bar","i":4}
返回值为:bar 4
请注意,序列化过程中调用了toString()。在此代码中,从字符串形式反序列化的逻辑在DataSerializer中实现,但将其作为Data类的另一个构造函数移动到其中可能更有意义 - 这不影响最终结果。
进一步注意,Data本身是一个相当简单的对象,没有更深层次的结构。尝试将其作为键进行序列化需要额外的工作。

这是一个很好的答案,但在Gson文档中深藏不露地说:“新应用程序应该优先选择TypeAdapter,其流API比此接口的树API更有效。” - durron597

0

如何维护哈希表键值取决于您自己,您可以使用简单且最容易的方式进行反序列化。

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 Values [ i= 3, s= foo ]

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


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