72

This link from the Gson project seems to indicate that I would have to do something like the following for serializing a typed Map to 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);
  }

Cool and that works, but it seems like so much overhead (a whole Type Adapter class?). I have used other JSON libraries like JSONLib and they let you build a map in the following way:

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

Or if I have a custom class something like the following:

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

The process is more manual, but requires way less code and doesn't have the overhead of haivng to create a custom Type Adapter for Number or in most cases for my own custom class.

Is this the only way to serialize a map with Gson, or has anyone found a way that beats out what Gson recommends in the link above.

stevebot
  • 23,275
  • 29
  • 119
  • 181
  • Which version of Gson are you using? What should the output of the example be (never mind if the type adapter is provided by the user)? – Programmer Bruce Dec 05 '11 at 05:32

5 Answers5

130

Only the TypeToken part is neccesary (when there are Generics involved).

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"));

Output:

{"two":"world","one":"hello"}
hello
world
Neuron
  • 5,141
  • 5
  • 38
  • 59
  • Lol, I don't pay attention to my own code when answering questions sometimes. Yes, Maps/Lists/etc are serializable by default but you need to use the `Type = new TypeToken.getType()` statement above. You do still have to create TypeAdapters for types that aren't primitive (or the primitive wrapper classes). Thanks for providing the correct answer, arturo. – Ankit Aggarwal Aug 28 '12 at 02:14
  • 1
    @zygimantus `TypeToken` the class is `public` while its default constructor is `protected`, in order to instantiate the object you can extend it as an anonymous inner class: `new TypeToken<...>() {}` – Cord Rehn May 01 '17 at 19:42
  • 2
    val typeOfHashMap = object:TypeToken>() {}.type //in Kotlin – Juan Mendez Feb 03 '18 at 05:32
  • what is the underlying Map implementation this uses? (HashMap etc.?) – nilanjanaLodh Nov 08 '18 at 16:09
  • The implementation is [com.google.gson.internal.LinkedTreeMap](https://github.com/google/gson/blob/master/gson/src/main/java/com/google/gson/internal/LinkedTreeMap.java). – Guillaume Perrot Apr 06 '19 at 01:01
  • Is there any way to get Gson to create a default HashMap instead of their builtin LinkedTreeMap? – Shmuel Newmark Apr 29 '21 at 16:39
101

Default

The default Gson implementation of Map serialization uses toString() on the key:

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

Will give:

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


Using enableComplexMapKeySerialization

If you want the Map Key to be serialized according to default Gson rules you can use enableComplexMapKeySerialization. This will return an array of arrays of key-value pairs:

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

Will return:

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

More details can be found here.

matt burns
  • 24,742
  • 13
  • 105
  • 107
  • 4
    Why isn't complex map key serialization default behavior? – AlexSee May 16 '17 at 09:06
  • @AlexSee I upvoted your comment, but i guess that as the name implies, since it only affects the keys, this might have been added as an after-thought because it is only needed when you go from POJO to json and not vice-versa. – Lorenzo Dec 06 '20 at 10:30
6

In Gson 2.7.2 it's as easy as

Gson gson = new Gson();
String serialized = gson.toJson(map);
isapir
  • 21,295
  • 13
  • 115
  • 116
  • 3
    This only works for primitve data types. For a complex type like in the example you have to use the answer of matt burns. – jcomouth Aug 26 '18 at 21:30
4

I'm pretty sure GSON serializes/deserializes Maps and multiple-nested Maps (i.e. Map<String, Map<String, Object>>) just fine by default. The example provided I believe is nothing more than just a starting point if you need to do something more complex.

Check out the MapTypeAdapterFactory class in the GSON source: http://code.google.com/p/google-gson/source/browse/trunk/gson/src/main/java/com/google/gson/internal/bind/MapTypeAdapterFactory.java

So long as the types of the keys and values can be serialized into JSON strings (and you can create your own serializers/deserializers for these custom objects) you shouldn't have any issues.

Evan Teran
  • 87,561
  • 32
  • 179
  • 238
Ankit Aggarwal
  • 1,546
  • 10
  • 11
-4
Map<String, Object> config = gson.fromJson(reader, Map.class);
jlbfalcao
  • 111
  • 1
  • 2