简体   繁体   中英

How to serialize HashMap<Object,Object> with GSON?

I am trying to serialize HashMap which has custom objects (both of the same type) both as a key and a value. However gson is outputing something like this:

{ key.toString():{value}}

Basicaly instead of serializing the object used as key in the map it just uses its toString value. The value object gets serialized fine. Resulting Json obviously cannot be deserialized. How do I convince GSON to fully serialize the key object?

EDIT: The objects that are stored in HashMap contains information about players (I am building custom matchmaker for our board game group). They look like this:

public class Player implements Serializable{
private static final long serialVersionUID = 42L;
private String playerName,faction,teamName;
private HashMap<String,Integer> resources;

The hashmap is supposed to contain informations about upcoming matches, basicaly key playing against value like this:

HashMap<Player,Player> matchMap=new HashMap<>();
matchMap.put(player1,opponentForPlayer1);

JSON keys can only be Strings . Try below solution which uses custom JsonSerializer and JsonDeserializer .

Custom JsonSerializer which will convert key ( Player object) to JSON String while serialization:

import java.lang.reflect.Type;
import java.util.Map;
import java.util.Map.Entry;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

public class CustomMapSerializer implements JsonSerializer<Map<Player, Player>> {

    @Override
    public JsonElement serialize(Map<Player, Player> src, Type typeOfSrc, JsonSerializationContext context) {
        JsonObject json = new JsonObject();

        Gson gson = new Gson();

        for (Entry<Player, Player> entry : src.entrySet()) {
            json.add(gson.toJson(entry.getKey()), gson.toJsonTree(entry.getValue()));
        }

        return json;
    }

}

Custom JsonDeserializer to convert key (JSON String ) back to Player object during deserialization:

import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import com.google.gson.Gson;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;

public class CustomMapDeserializer implements JsonDeserializer<Map<Player, Player>> {

    @Override
    public Map<Player, Player> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
            throws JsonParseException {
        Map<Player, Player> players = new HashMap<Player, Player>();

        Gson gson = new Gson();

        JsonObject object = json.getAsJsonObject();

        for (Entry<String, JsonElement> entry : object.entrySet()) {
            players.put(gson.fromJson(entry.getKey(), Player.class), gson.fromJson(entry.getValue(), Player.class));
        }

        return players;
    }
}

Refer below example for serialization and deserialization :

import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class Solutiony {

    public static void main(String[] args) {
        Map<String, Integer> data = new HashMap<>();
        data.put("value", 100);

        Player p = new Player();
        p.setFaction("0.9");
        p.setPlayerName("x");
        p.setTeamName("A");
        p.setResources(data);

        Player p2 = new Player();
        p2.setFaction("1.0");
        p2.setPlayerName("y");
        p2.setTeamName("B");
        p2.setResources(data);

        Map<Player, Player> map = new HashMap<Player, Player>();
        map.put(p, p2);

        Gson gson = new GsonBuilder().registerTypeAdapter(map.getClass(), new CustomMapSerializer())
                .registerTypeAdapter(map.getClass(), new CustomMapDeserializer())
                .setPrettyPrinting().create();

        //Serialization
        String val = gson.toJson(map);

        //De-serialization
        Map<Player, Player> map2 = gson.fromJson(val, map.getClass());
    }
}

Serialized JSON will look like:

{
  "{\"playerName\":\"x\",\"faction\":\"0.9\",\"teamName\":\"A\",\"resources\":{\"value\":100}}": {
    "playerName": "y",
    "faction": "1.0",
    "teamName": "B",
    "resources": {
      "value": 100
    }
  }
}

Based on fluffys comment the simplest solution is using enableComplexMapKeySerialization() while creating gson object like this:

Gson gson=new GsonBuilder().enableComplexMapKeySerialization().create();

If that is not available or working for whatever reason pcsutars answer also work.

It's hard to come up with an exact answer without a specific example.

You can change how the key is named using the SerializedName annotation:

private class SomeObject {
  @SerializedName("custom_naming") private final String someField;
  private final String someOtherField;

  public SomeObject(String a, String b) {
    this.someField = a;
    this.someOtherField = b;
  }
}

SomeObject someObject = new SomeObject("first", "second");
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();
String jsonRepresentation = gson.toJson(someObject);
System.out.println(jsonRepresentation);

The output is:

{"custom_naming": "first", "SomeOtherField": "second"}

(example shamelessly stolen from the doc:)

To customize the serialization, check out the documentation: https://github.com/google/gson/blob/master/UserGuide.md#custom-serialization-and-deserialization

// Step 1: writing a custom serializer


/** Here is an example of how to write a custom serializer for JodaTime DateTime class. */
private class DateTimeSerializer implements JsonSerializer<DateTime> {
  public JsonElement serialize(DateTime src, Type typeOfSrc, JsonSerializationContext context) {
    return new JsonPrimitive(src.toString());
  }
}
// Step 2: registering it
GsonBuilder gson = new GsonBuilder();
gson.registerTypeAdapter(MyType.class, new DateTimeSerializer());

registerTypeAdapter call checks if the type adapter implements more than one of these interfaces and register it for all of them.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM