I'm using a class with a Map attribute, whose value is a Collection. I'm using OrientDB, so when I obtain this object from database, it's returning a OTrackedMap object.
When I'm trying to serialize it, it works perfect until Jackson v2.9.2. If I upgrade it to v2.9.3 or v2.9.4, it crashes with the following exception:
INFO: Jackson version: 2.9.4
com.fasterxml.jackson.databind.JsonMappingException: Failed to specialize base type java.util.Map<java.lang.String,java.util.Collection<java.lang.String>> as com.orientechnologies.orient.core.db.record.OTrackedMap, problem: Type parameter #1/2 differs; can not specialize java.lang.String with java.lang.Object (through reference chain: com.example.jackson.AccessModel["repositoryPrivileges"])
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:391)
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:351)
at com.fasterxml.jackson.databind.ser.std.StdSerializer.wrapAndThrow(StdSerializer.java:316)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:727)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:319)
at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:3893)
at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:3207)
at com.example.jackson.JacksonTest.testSerialization(JacksonTest.java:37)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
[...]
You can take a look at this gist to see an example.
Looks like there is some kind of a problem with type compatibility of declarations.
What would be most helpful would be a simplified example, wherein OTrackedMap would be simplified into equivalent minimal declaration. The problem has to do with Map key type declaration (Object vs String), but I am not 100% sure whether flaw is in Jackson's checks of compatibility, or problem with declarations that try to downcast type.
So to solve that part it would be good to have even more compact example (since ideally we wouldn't add new external dependencies even for tests; and since avoiding that is doable here).
I've updated the gist with a working example.
Like you said, the problem had to do with Map key type declaration. If the key is changed from String to Object, it works. I guess that it's related to the Map implementation, which extends from LinkedHashMap<Object, T>.
I didn't want to create new issue, but I'm also having same exception when serializing or deserializing classes with nested generic types.
Here is a simple project with reduced example model and test to verify presence of problem.
Difference is that it affects versions 2.8.11 up to current latest 2.9.4, but works OK for versions <=2.8.10
@ptirador Then it may be problem with declarations: as I understand it, it is not legal to subtype with different type parameters, except for return type co-variance (of type itself, but not its parameterization?).
@antonio-tomac Same exception does not necessarily mean same root cause, but I guess it's fine to be tacked here for now.
Any update on this?
@cowtowncoder are there any news/updates regarding this issue? :)
If there is an update I will add an update. So no.
As far as I can see, the problem seems to be in TypeFactory#_verifyAndResolvePlaceholders in line 460. There is checked, if the two raw classes are equal. In this example this would be Object and String. In my opinion instanceof or isAssignableFrom should be used instead.
@julseik I can have a look to see if that makes sense. Type assignability can be bit dangerous, so I want to make sure this is legal.
@ptirador I am bit confused. With 2.9.5, gist given works. How does the failing case differ from this?
@cowtowncoder Sorry, I've just updated the gist. Could you try now with that one? I've tried with versions from 2.9.3 to 2.9.5 and it does not work.
I'm also seeing this problem with 2.9.3, 2.9.4, and 2.9.5. I'll be happy to test any RC with a fix.
Ok. I don't think original test is valid, and to me it looks like Jackson might be doing the right thing here.
Note that declaration:
static class CustomMap<T> extends LinkedHashMap<Object, T> { }
forces key type to be Object. That is not String, and you can not derive CustomType in any way to make its key type be String.
Test works around this by using "raw" type:
Map<String, Collection<String>> repoPrivilegesMap = new CustomMap();
since compiler would correctly catch the issue if trying to use
Map<String, Collection<String>> repoPrivilegesMap = new CustomMap<>();
(where type assingments do not work)
So. Type system used here is inconsistent.
Having said that, this is for serialization side and given that it is possible to force type mismatch, perhaps there is a case to be made that some kinds of coercion should be possible.
If possible to do safely I would be ok for example if key type of Maps would be allowed to go from any other type down to Object, given that it is unlikely to hurt in unrelated cases, and would solve the problem here.
I will see if I can make that happen.
Quick note: realized that use of "static typing" can be used to work around the problem, because that will force use of declared type over attempting to combine declared (static) type with runtime instance type. Although you may or may not want to force it globally (MapperFeature.USE_STATIC_TYPING), it is possible to force it on specific property:
// works on getter or field:
@JsonSerialize(typing = JsonSerialize.Typing.STATIC)
public Map<String, Collection<String>> getRepositoryPrivileges() { ... }
and this will prevent the problem here. So it may be worth considering this as short-term fix; if this is actual type for real use case it should be safe for serialization.
I will still pursue potential change I outlined above, but thought work-around would be useful.
Was this actually fixed? I'm using 2.10.4 and still having the same issue :confused:
@jhoanmanuelms The reported issue as per gist. If you are experiencing problems, please file a new issue so we can reproduce it and see what is going on. Even if exception looks the same does not necessarily mean the root cause is the same; usually there is some variation.
Most helpful comment
I didn't want to create new issue, but I'm also having same exception when serializing or deserializing classes with nested generic types.
Here is a simple project with reduced example model and test to verify presence of problem.
Difference is that it affects versions
2.8.11up to current latest2.9.4, but works OK for versions <=2.8.10