PNS
PNS

Reputation: 19905

Custom Java map deserialzer in Gson

Is it possible to convert an array of object pairs into a Map, by selecting values from each pair, without creating an intermediate POJO?

For example, say that there are 2 Java classes (Key.class, Value.class) and a JSON array constructed from serialized pairs of their objects, as follows

[
  {"key":{}, "value":{}}, // Objects k1, v1
  {"key":{}, "value":{}}, // Objects k2, v2
  {"key":{}, "value":{}}, // Objects k3, v3
  {"key":{}, "value":{}}, // Objects k4, v4
]

Can Gson "directly" (i.e., without a POJO definition) deserialize the above array into a Map, in which the actual keys and values are populated from a manipulation of the corresponding Key and Value objects, instead of the "full" objects?

In other words, given two Java types K and V that may be different to Key and Value, the deserialization per pair should be

(k1:Key, v1:Value) --> (k1':K, v1':V)

where

k1' = function(k1)
v1' = function(v1)

A custom adapter like this does not work:

GsonBuilder builder = new GsonBuilder();
Type type = (new TypeToken<Map<K, V>>() {}).getType();    
builder.registerTypeAdapter(type, new JsonDeserializer<Map<Key, Value>>() {
  @Override
  public Map<K, V> deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
    // Custom deserialization block,
    // invoking the `function()` method
  }
Gson gson = builder.create();

Using the above adapter results in Gson passing the entire array to the deserialize() method, where it has to be manipulated manually, which is both tedious and error-prone.

With a custom POJO like

public class KeyValue {
  Key k;
  Value v;
}

the above JSON array can be deserialized into an array or set of KeyValue objects, which can then be converted to a Map<K, V>. But could the same be done without the KeyValue POJO;

Upvotes: 1

Views: 1452

Answers (1)

D.B.
D.B.

Reputation: 4713

If you want to avoid having the POJO class you refer to as KeyValue I think the only way to do that with gson is to create an adapter. The reason being that your JSON does not conform to any generic, built-in class and therefore gson is not going to be able to understand it without having the custom logic to do so.

Since you have the Key and Value classes you can take advantage of that inside of your custom adapter. I think you will find that this reduces the tediousness of the code.

public static void main(String[] args) throws IOException {
        GsonBuilder builder = new GsonBuilder();
        Type type = (new TypeToken<Map<Key, Value>>() {}).getType();    
        builder.registerTypeAdapter(type, new JsonDeserializer<Map<Key, Value>>() {
          @Override
          public Map<Key, Value> deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
              Map<Key, Value> resultMap = new HashMap<>();
              JsonArray jArr = json.getAsJsonArray();
              for(int i=0; i < jArr.size(); i++){
                  JsonObject item = jArr.get(i).getAsJsonObject();
                  processKeyValue(item, resultMap);
              }
              return resultMap;
          }

          private void processKeyValue(JsonObject mapEntry, Map<Key, Value> resultMap){
              Gson gson = new Gson();
              Key key = gson.fromJson(mapEntry.get("key"), Key.class);
              Value value = gson.fromJson(mapEntry.get("value"), Value.class);
              resultMap.put(key, value);
          }
        });
        Gson gson = builder.create();

        //Note: assume that the variable "json" is a JSON string. I removed my code for getting the string since I'm just reading from a file and I don't know how you're getting your data.

        // Deserialization

        Map<Key,Value> outputMap = gson.fromJson(json, type);


        System.out.println("RESULTS: "+outputMap.toString());
    }

I used your example data, but slightly modified to have a little more content. I created two very simple POJO classes for Key and Value - they only have a String field named id, a getter and setter for that field, and a toString method.

This was the input I used when running the above code:

[
  {"key":{"id":"k1"}, "value":{"id":"v1"}}, 
  {"key":{"id":"k2"}, "value":{"id":"v2"}},
  {"key":{"id":"k3"}, "value":{"id":"v3"}}, 
  {"key":{"id":"k4"}, "value":{"id":"v4"}}
]

The output is:

RESULTS: {Key [id=k2]=Value [id=v2], Key [id=k1]=Value [id=v1], Key [id=k3]=Value [id=v3], Key [id=k4]=Value [id=v4]}

Upvotes: 1

Related Questions