ebean-orm / ebean-jackson

Jackson ObjectMapper module that uses Ebean's JSON support for serialisation and deserialisation
5 stars 2 forks source link

Having trouble serializing Ebean classes with Jackson module #8

Open jamie-nxtgencare opened 6 years ago

jamie-nxtgencare commented 6 years ago

Hi.

I'm receiving the following error message when trying to serialize an enhanced ebean model object:

com.fasterxml.jackson.databind.JsonMappingException: Type id handling not implemented for type <ebean type> (by serializer of type io.ebean.jackson.CommonBeanSerializer)

I'm using a library to store serialized objects in a cache, however, I've stripped all of my code down to the bare bones to try to troubleshoot this issue and can duplicate it simply with:

ObjectMapper mapper = new ObjectMapper().registerModule(new JacksonEbeanModule());
mapper.writeValueAsString(<new EbeanEntity()>);

That being said, I've been able to create a new entity the exact same way and convert it to JSON using eBeanServer.toJson(obj), so as far as I can tell there's nothing wrong with my object.

I'm also not sure if I have a version mismatch or something:

Ebean

compile 'io.ebean:ebean:11.3.1'
compile 'io.ebean:ebean-spring-txn:10.1.1'

ebean-jackson: compile group: 'io.ebean', name: 'ebean-jackson', version: '10.1.1'

I'm bringing in jackson as a dependency to another jackson library:

compile 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.8.8'

It's version matches the version of that jar: 2.8.8.

Let me know if this is an issue on my end or an actual problem. If you need more details about the contents of my model objects, I'd be happy to oblige, however, I've tried this with a number of different models and they all have the same problem; tracing through the code, it never even calls CommonBeanSerializer.serialize, so it's not even getting to the point where it's looking at the contents of the object yet.

rbygrave commented 6 years ago

Do you have the full stack trace? Can you include it?

jamie-nxtgencare commented 6 years ago

Right now the first call to serialize data is populating a cache if it has nothing in it. This is on a Spring boot application ready event: @EventListener(ApplicationReadyEvent.class)

So you'll see that's most of the first part of the stack trace. I imagine the "caused by" part of this stack trace is the part you are concerned with, but this is the full trace.


java.lang.reflect.UndeclaredThrowableException: Failed to invoke event listener method
HandlerMethod details:
Bean [xxx.MyService$$EnhancerBySpringCGLIB$$a39dfff5]
Method [public void xxx.MyService.initCache() throws com.fasterxml.jackson.core.JsonProcessingException]
Resolved arguments:
        at org.springframework.context.event.ApplicationListenerMethodAdapter.doInvoke(ApplicationListenerMethodAdapter.java:270)
        at org.springframework.context.event.ApplicationListenerMethodAdapter.processEvent(ApplicationListenerMethodAdapter.java:174)
        at org.springframework.context.event.ApplicationListenerMethodAdapter.onApplicationEvent(ApplicationListenerMethodAdapter.java:137)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:167)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
        at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:393)
        at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:347)
        at org.springframework.boot.context.event.EventPublishingRunListener.finished(EventPublishingRunListener.java:101)
        at org.springframework.boot.SpringApplicationRunListeners.callFinishedListener(SpringApplicationRunListeners.java:79)
        at org.springframework.boot.SpringApplicationRunListeners.finished(SpringApplicationRunListeners.java:72)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:305)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107)
        at xxx.Application.main(Application.java:15)
        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)
        at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Type id handling not implemented for type xxx.MyEbeanModel (by serializer of type io.ebean.jackson.CommonBeanSerializer)
        at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:284)
        at com.fasterxml.jackson.databind.SerializerProvider.mappingException(SerializerProvider.java:1110)
        at com.fasterxml.jackson.databind.SerializerProvider.reportMappingProblem(SerializerProvider.java:1135)
        at com.fasterxml.jackson.databind.JsonSerializer.serializeWithType(JsonSerializer.java:160)
        at com.fasterxml.jackson.databind.ser.impl.TypeWrappedSerializer.serialize(TypeWrappedSerializer.java:32)
        at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:292)
        at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:3681)
        at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:3057)
        at xxx.MyService.initCache(MyService.java:51)
        at xxx.MyService$$FastClassBySpringCGLIB$$272c8844.invoke(<generated>)
        at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
        at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:669)
        at xxx.MyService$$EnhancerBySpringCGLIB$$a39dfff5.initCache(<generated>)
        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)
        at org.springframework.context.event.ApplicationListenerMethodAdapter.doInvoke(ApplicationListenerMethodAdapter.java:253)
        ... 18 common frames omitted```
jamie-nxtgencare commented 6 years ago

Note also that I'm calling writeValueAsString to simplify what's happening. Previously, I was using Redisson, which is configured internally to use an ObjectMapper configured with JacksonEbeanModule. But the error is the same.

The following is a stacktrace in my intended usage, but it's pretty much the same, except Redisson is the one trying to do the serialization:


java.lang.IllegalArgumentException: com.fasterxml.jackson.databind.JsonMappingException: Type id handling not implemented for type xxx.MyModel (by serializer of type io.ebean.jackson.CommonBeanSerializer) (through reference chain: java.util.HashSet[0])
        at org.redisson.RedissonObject.encodeMapValue(RedissonObject.java:247)
        at org.redisson.RedissonMap.putAllOperationAsync(RedissonMap.java:310)
        at org.redisson.RedissonMap.putAllAsync(RedissonMap.java:275)
        at org.redisson.RedissonMap.putAll(RedissonMap.java:266)
        at xxx.MyService.initCache(MyService.java:48)
        at xxx.MyService$$FastClassBySpringCGLIB$$272c8844.invoke(<generated>)
        at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
        at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:669)
        at xxx.MyService$$EnhancerBySpringCGLIB$$258b4150.initCache(<generated>)
        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)
        at org.springframework.context.event.ApplicationListenerMethodAdapter.doInvoke(ApplicationListenerMethodAdapter.java:253)
        at org.springframework.context.event.ApplicationListenerMethodAdapter.processEvent(ApplicationListenerMethodAdapter.java:174)
        at org.springframework.context.event.ApplicationListenerMethodAdapter.onApplicationEvent(ApplicationListenerMethodAdapter.java:137)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:167)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
        at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:393)
        at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:347)
        at org.springframework.boot.context.event.EventPublishingRunListener.finished(EventPublishingRunListener.java:101)
        at org.springframework.boot.SpringApplicationRunListeners.callFinishedListener(SpringApplicationRunListeners.java:79)
        at org.springframework.boot.SpringApplicationRunListeners.finished(SpringApplicationRunListeners.java:72)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:305)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107)
        at xxx.Application.main(Application.java:15)
        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)
        at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Type id handling not implemented for type xxx.MyModel (by serializer of type io.ebean.jackson.CommonBeanSerializer) (through reference chain: java.util.HashSet[0])
        at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:284)
        at com.fasterxml.jackson.databind.SerializerProvider.mappingException(SerializerProvider.java:1110)
        at com.fasterxml.jackson.databind.SerializerProvider.reportMappingProblem(SerializerProvider.java:1135)
        at com.fasterxml.jackson.databind.JsonSerializer.serializeWithType(JsonSerializer.java:160)
        at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(CollectionSerializer.java:151)
        at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(CollectionSerializer.java:25)
        at com.fasterxml.jackson.databind.ser.std.AsArraySerializerBase.serializeWithType(AsArraySerializerBase.java:263)
        at com.fasterxml.jackson.databind.ser.impl.TypeWrappedSerializer.serialize(TypeWrappedSerializer.java:32)
        at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:292)
        at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:3681)
        at com.fasterxml.jackson.databind.ObjectMapper.writeValue(ObjectMapper.java:3014)
        at org.redisson.codec.JsonJacksonCodec$1.encode(JsonJacksonCodec.java:75)
        at org.redisson.RedissonObject.encodeMapValue(RedissonObject.java:245)
        ... 31 common frames omitted```
jamie-nxtgencare commented 6 years ago

I believe I've traced down the source of the problem; even though I wasn't using Redisson for anything, I was still configuring the JsonJacksonCodec that it required, and I was supplying it with the same ObjectMapper I was using to serialize with by hand.

If I don't configure this object, the problem goes away. It appears as though configuring Redisson with this codec modifies the original object mapper passed into it and enables type inclusion. I'm trying to figure out what setting it is that it enables that causes mapping to no longer work; if I don't configure this codec and use a plain ObjectMapper, I don't even need ebean-jackson to serialize and deserialize my ebean objects.

jamie-nxtgencare commented 6 years ago

So it looks like the objectMapper is configured to ignore everything but the fields:

    .setVisibilityChecker(objectMapper.getSerializationConfig().getDefaultVisibilityChecker()
        .withFieldVisibility(JsonAutoDetect.Visibility.ANY)
        .withGetterVisibility(JsonAutoDetect.Visibility.NONE)
        .withSetterVisibility(JsonAutoDetect.Visibility.NONE)
        .withCreatorVisibility(JsonAutoDetect.Visibility.NONE));```

That causes an infinite recursion error when using an otherwise vanilla ObjectMapper (through the EbeanInterceptor field).

When I use the ebean-jackson module, this problem goes away, but unfortunately the codec also enables type inclusion:

TypeResolverBuilder<?> mapTyper = new DefaultTypeResolverBuilder(DefaultTyping.NON_FINAL) { public boolean useForType(JavaType t) { switch (_appliesFor) { case NON_CONCRETE_AND_ARRAYS: while (t.isArrayType()) { t = t.getContentType(); } // fall through case OBJECT_AND_NON_CONCRETE: return (t.getRawClass() == Object.class) || !t.isConcrete(); case NON_FINAL: while (t.isArrayType()) { t = t.getContentType(); } // to fix problem with wrong long to int conversion if (t.getRawClass() == Long.class) { return true; } if (t.getRawClass() == XMLGregorianCalendar.class) { return false; } return !t.isFinal(); // includes Object.class default: // case JAVA_LANG_OBJECT: return (t.getRawClass() == Object.class); } } }; mapTyper.init(JsonTypeInfo.Id.CLASS, null); mapTyper.inclusion(JsonTypeInfo.As.PROPERTY); mapper.setDefaultTyping(mapTyper);



Which I suspect is what this module can't handle.

In any case, I'm confident the problem isn't with ebean-jackson. Why this codec accepts an ObjectMapper and mangles it is beyond me (presumably just serializing things into a supported format for redis), but it seems to take quite a few liberties in usage expectations.

Thanks for looking into this.
SympathyForTheDev commented 6 years ago

I have a workaround for this issue by using the @JsonAutoDetect annotation on an extended class of io.ebean.model like this

@MappedSuperclass
@JsonAutoDetect(setterVisibility = PUBLIC_ONLY, getterVisibility = PUBLIC_ONLY, creatorVisibility = PUBLIC_ONLY, fieldVisibility = PUBLIC_ONLY)
public abstract class BaseModel extends Model