2

I am trying to deserialize an object that has a ConcurrentMap in it but I get an exception.

Caused by: java.lang.IllegalArgumentException: Can not set java.util.concurrent.ConcurrentMap field com.example.Row.doubleValues to java.util.LinkedHashMap

My function looks something like this:

T deserialise(final InputStream input, final Class<T> type) {
  GsonBuilder gsonBuilder = new GsonBuilder();
  Gson gson = gsonBuilder.create();
  InputStreamReader isr = new InputStreamReader(input);
  return gson.fromJson(isr, type);
}

How do I get it to deserialize the map properly? Do I need to provide a custom deserializer?

Note that I have both regular Maps and ConcurrentMaps in the same class. I am also the one who serializes the class, so, if I can provide a custom serializer that specifies the type of concurrent maps and then a deserializer that specifies how those should be created as objects that should work.

Update: The class that I am deserializing looks like this:

public class Row implements Serializable {
    private static final long serialVersionUID = 1L; //there's a diff value here

    private final String key;
    private final ConcurrentMap<String, Double> doubleValues = Maps.newConcurrentMap();
    private Map<String, String> writeOnceValues = Maps.newHashMap();
    ...
}
giampaolo
  • 6,906
  • 5
  • 45
  • 73
naumcho
  • 18,671
  • 14
  • 48
  • 59
  • could you show the relevant part of the class you want to deserialize into? – Katona Sep 17 '13 at 18:44
  • @Katona added an example – naumcho Sep 17 '13 at 19:53
  • one obvious solution is to change the type of `doubleValues` from `ConcurrentMap` to `Map`, do you really need that? Otherwise you will need custom deserialization, see [this](http://stackoverflow.com/questions/16127904/gson-fromjson-return-linkedhashmap-instead-of-enummap) question, the problem is similar but not indentical and I think that solution (`InstanceCreator`) might work here. – Katona Sep 17 '13 at 20:15

1 Answers1

-1

This is my proposed solution with a TypeAdapter. Before showing you the code, I have to say that final should not be used for a field if field is present into parsed string, since Gson will try to assign value to that field.

My changed Row class, just for purpose of showing solution.

public class Row implements Serializable {
    private static final long serialVersionUID = 1L; //there's a diff value here

    private final String key = "myKey";
    private ConcurrentMap<String, Double> doubleValues = Maps.newConcurrentMap();
    private Map<String, String> writeOnceValues = Maps.newHashMap();

    @Override
    public String toString() {
        return "Row [key=" + key.getClass() + ", doubleValues=" + doubleValues.getClass()
                + ", writeOnceValues=" + writeOnceValues.getClass() + "]";
    }

} 

and the type adapter where I essentially use standard parsing but return a ConcurrentHashMap.

public final class ConcurrentHashMapTypeAdapter<K,V> extends TypeAdapter<ConcurrentMap<K,V>> {

  @Override
  public synchronized ConcurrentMap<K,V> read(JsonReader in) throws IOException {
    if (in.peek() == JsonToken.NULL) {
      in.nextNull();
      return null;
    }
    Type aType = new TypeToken<LinkedTreeMap<K,V>>() {}.getType();
    Gson g = new Gson();
    LinkedTreeMap<K,V> ltm = g.fromJson(in, aType);
    return new ConcurrentHashMap<K,V>(ltm); 
  }

  @Override
  public synchronized void write(JsonWriter out, ConcurrentMap<K, V> value) throws IOException {
     Gson g = new Gson();
     out.value(g.toJson(value));
  }
}

and a test method where I show you that it works.

public static void main(String[] args) {
    Gson simpleGson = new Gson();

    // deserialize row once to get a suitable JSON string
    String s = simpleGson.toJson(new Row());
    System.out.println("JSON string: " + s);

    Row r;
    try{
        r = simpleGson.fromJson(s, Row.class);
    } catch(Exception e){
        System.out.println("Oh no, assignment is not possible");
        e.printStackTrace();
    }


    GsonBuilder gb = new GsonBuilder();
    Type aType = new TypeToken<ConcurrentMap<String,Double>>() {}.getType();
    gb.registerTypeAdapter(aType, new ConcurrentHashMapTypeAdapter<String, Double>());

    Gson gsonWithTypeAdapter = gb.create();

    r = gsonWithTypeAdapter.fromJson(s, Row.class);
    System.out.println("Now it works");
    System.out.println("Row: " +r);
}

This is my execution:

JSON string: {"key":"myKey","doubleValues":{},"writeOnceValues":{}}
Oh no, assignment is not possible
java.lang.IllegalArgumentException: Can not set java.util.concurrent.ConcurrentMap field stackoverflow.questions.q18856793.Row.doubleValues to com.google.gson.internal.LinkedTreeMap
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(Unknown Source)
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(Unknown Source)
    at sun.reflect.UnsafeObjectFieldAccessorImpl.set(Unknown Source)
    at java.lang.reflect.Field.set(Unknown Source)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:95)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:172)
    at com.google.gson.Gson.fromJson(Gson.java:803)
    at com.google.gson.Gson.fromJson(Gson.java:768)
    at com.google.gson.Gson.fromJson(Gson.java:717)
    at com.google.gson.Gson.fromJson(Gson.java:689)
    at stackoverflow.questions.q18856793.Q18856793.main(Q18856793.java:23)
Now it works
Row: Row [key=class java.lang.String, doubleValues=class java.util.concurrent.ConcurrentHashMap, writeOnceValues=class com.google.gson.internal.LinkedTreeMap]
giampaolo
  • 6,906
  • 5
  • 45
  • 73
  • What is the purpose of `TypeAdapterFactory FACTORY` in `ConcurrentHashMapTypeAdapter` class, if it isn't used anywhere in the example? – andrybak Jun 20 '16 at 09:52
  • `TypeAdapter<...>` mechanism in Gson is specifically designed to provide information about types where it is not available due to type-erasure in Java. Generified `TypeAdapter` is just wrong. – andrybak Jun 21 '16 at 20:06
  • @andrybak it's been 3 years. Give me at least 1 day to check the answer.. – giampaolo Jun 21 '16 at 22:16
  • @andrybak: the FACTORY field is useless, I do not remember why I put it there, going to remove. About my solution with TypeAdapter, if you have a more elegant way to resolve OP question without TypeAdapter I would be glad to see your answer and compare to mine :). I'm always eager to learn :). No passive aggressive intent, here. – giampaolo Jun 21 '16 at 23:01
  • @ giampaolo : I am using my own class as a Key in ConcurrentMap and when I am going to deserialize using gson, I am getting java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap is not Comparable at com.google.gson.internal.LinkedTreeMap.find(LinkedTreeMap.java:164) at com.google.gson.internal.LinkedTreeMap.put(LinkedTreeMap.java:94) – Rahul Sahu Dec 25 '18 at 13:12