yasson icon indicating copy to clipboard operation
yasson copied to clipboard

Map deserialization doesn't use Adapter and generates runtime exception on access

Open nickarls opened this issue 6 years ago • 3 comments

Given a simple POJO "Person" with a String attribute "name" and a test like

public static void main(String[] args) { String json = "{\"foo\" : \"bar\"}"; Map<Person, String> map = JsonbBuilder.create().fromJson(json, new HashMap<Person, String>() {}.getClass().getGenericSuperclass()); map.keySet().forEach(Person::getName); }

I end up with Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to test.Person at java.util.HashMap$KeySet.forEach(HashMap.java:933) at test.Test.main(Test.java:14)

If I register an adapter for Person, it is never called. How can a String even fit into the Person map key and only be detected at runtime?

nickarls avatar Mar 15 '19 08:03 nickarls

Looks like a reopen of #110. In runtime the <Person> signature is not present, so any value fits. However Yasson should avoid class cast failing earlier and also tell something more specific in this case.

A bit of a background on current implementation and why your adapter is not used: JSON doesn't allow anything else than string to be used as a key in a json object structure. Serializing Maps with any different key type than String has to end up as a String key mapping. Currently we use String.valueOf (toString) for serialization of any key type other than string.

This can be customized registering adapter / (de)serializer for whole map type for example:

public class PersonKeyMapSerializer implements JsonbSerializer<Map<Person,String>> {}

Adapters / serializers registered directly for the map key type are not used during Map processing. I am not all against it, but if we do want to introduce it, we would still have to check that to/from type cannot be anything else than string for a key.

bravehorsie avatar Mar 15 '19 11:03 bravehorsie

Does Gson or Jackson etc have any workaround for this case?

nickarls avatar Mar 17 '19 21:03 nickarls

Maybe I should open a new issue, but my one is similar to this...

I have a Map<NOT_A_STRING, ?> and I would like to have a json like this:

  "items": [
     "item" {
         "key": { ... },
        "value": { ... }
     },
     "item" {
         "key": { ... },
        "value": { ... }
     },
  ]

I tried to build a MapAdapter, but was only able to get the content of "key" and "value" as a string, using jsonb.toJson(e.getValue()...)

Then I tried to use serializers. In this case the @JsonbTypeSerializer can't be used because in the MapSerializer the default constructor is missing and a constructor with a builder is required.

So I've added it to the configuration, it was a little bit complex because of the Builder requirement.

Unfortunately the MapSerializer is based on Map<String, ?> maps. So I've inherited from it and then I've overridden the serializeInternal method. I was not able to serialize the key and the value object, because a class cast exception was thrown.

If it could be useful, I'll build a small project just to better clarify the point.

siaspa avatar Jun 21 '19 08:06 siaspa