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

时间:2022-09-15 12:32:40

I get the error:

我收到错误:

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

when trying to deseralize a Map that uses non-trivial keys:

尝试解除使用非平凡键的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; }
    }
}

This serializes to the json:

这序列化为json:

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

One can see the key attempting to be serialized, but certainly not in a way that can be deserialized.

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

How does one serialize this object so that it can be deserialized?

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

3 个解决方案

#1


8  

I found the following while trying to solve this puzzle: Issue 210: Cannot serialize or deserialize Maps with complex keys.

我在尝试解决这个难题时发现了以下内容:问题210:无法使用复杂键序列化或反序列化地图。

For any internet travelers from the future (like myself)... you can enable this functionality in GSON 2.* with the enableComplexMapKeySerialization() method on GsonBuilder.

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

Here's the javadoc for that method.

这是该方法的javadoc。

When enabled, the map will be serialized (and correctly deserialized) as an array of [key, value] arrays:

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

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

#2


2  

The problem is that toString() is getting called on the keys to the map, rather than them being serialized themselves.

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

To fix this a custom serializer and deserializer needs to be set up, and the deserializer needs to be aware of the format that the object uses to display itself as a string (the toString() method must return a string that can be used to reconstruct the entire object).

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

For the above example:

对于上面的例子:

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

Running this code produces:

运行此代码会产生:

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

Note the invocations of toString() as part of the serialization. In this code, the logic for the deserializion from the String form is in the DataSerializer, though it may make sense to move it into the Data class as another constructor instead - it doesn't affect the final outcome.

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

Further note that Data was a rather simple object itself with no deeper structures. Trying to serialize that as the key would require additional work.

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

#3


0  

Its Up to you how you are maintaining the HahMap Keys, You can deserialized it with simple and easiest way.

它取决于您如何维护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()+" ]");
       }

Result:

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

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

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

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

#1


8  

I found the following while trying to solve this puzzle: Issue 210: Cannot serialize or deserialize Maps with complex keys.

我在尝试解决这个难题时发现了以下内容:问题210:无法使用复杂键序列化或反序列化地图。

For any internet travelers from the future (like myself)... you can enable this functionality in GSON 2.* with the enableComplexMapKeySerialization() method on GsonBuilder.

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

Here's the javadoc for that method.

这是该方法的javadoc。

When enabled, the map will be serialized (and correctly deserialized) as an array of [key, value] arrays:

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

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

#2


2  

The problem is that toString() is getting called on the keys to the map, rather than them being serialized themselves.

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

To fix this a custom serializer and deserializer needs to be set up, and the deserializer needs to be aware of the format that the object uses to display itself as a string (the toString() method must return a string that can be used to reconstruct the entire object).

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

For the above example:

对于上面的例子:

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

Running this code produces:

运行此代码会产生:

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

Note the invocations of toString() as part of the serialization. In this code, the logic for the deserializion from the String form is in the DataSerializer, though it may make sense to move it into the Data class as another constructor instead - it doesn't affect the final outcome.

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

Further note that Data was a rather simple object itself with no deeper structures. Trying to serialize that as the key would require additional work.

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

#3


0  

Its Up to you how you are maintaining the HahMap Keys, You can deserialized it with simple and easiest way.

它取决于您如何维护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()+" ]");
       }

Result:

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

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

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

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