【问题标题】:Deserializing map key with Gson expects an object使用 Gson 反序列化映射键需要一个对象
【发布时间】:2014-02-08 03:52:12
【问题描述】:

我得到错误:

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:

{ “米”:{ “com.shagie.app.SimpleMap$Data@24a37368”:{ “s”:“富”, “我”:3 }, “com.shagie.app.SimpleMap$Data@66edc3a2”:{ “s”:“酒吧”, “我”:4 } } }

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

如何序列化这个对象以便反序列化?

【问题讨论】:

  • 虽然我确实找到了一个可行的答案,但我不相信它是最佳解决方案,或者如果我遗漏了 API 中的某些内容会导致 GSON 只做我想做的事情真的很想要。

标签: java json serialization deserialization gson


【解决方案1】:

问题是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;
        }
    }
}

运行此代码会产生:

序列化调用:{"s":"foo","i":3} toString 调用:1:f 序列化调用:{"s":"bar","i":4} toString 调用:2:b { “米”:{ “1:f”:{ “s”:“富”, “我”:3 }, “2:b”:{ “s”:“酒吧”, “我”:4 } } } 反序列化调用:“1:f” 反序列化返回:f 1 反序列化调用:{"s":"foo","i":3} 反序列化返回:foo 3 反序列化调用:“2:b” 反序列化返回:b 2 反序列化调用:{"s":"bar","i":4} 反序列化返回:栏 4

注意toString() 的调用是序列化的一部分。在此代码中,从 String 形式反序列化的逻辑在 DataSerializer 中,尽管将其作为另一个构造函数移入 Data 类可能是有意义的 - 它不会影响最终结果。

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

【讨论】:

  • 这是一个很好的答案,但深埋在Gson Documentation 中它说:“新应用程序应该更喜欢TypeAdapter,它的流式API 比这个接口的树形API 更高效。”
【解决方案2】:

这取决于你如何维护 HahMap 键,你可以用简单和最简单的方法对其进行反序列化。

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 = sn-p.Snippet$Data@61506150 值 [ i= 3, s= foo ]

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

【讨论】:

    【解决方案3】:

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

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

    这是javadoc for that method

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

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

    【讨论】:

    • 这是“THE”解决方案。谢谢。
    猜你喜欢
    • 1970-01-01
    • 2019-06-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-08
    • 1970-01-01
    相关资源
    最近更新 更多