Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

REST Jackson JsonDeserialize, StackOverflowError after upgrade

In the previous version of jackson (1.9.2) the following code worked fine:

import org.codehaus.jackson.map.JsonDeserializer;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.map.DeserializationContext;
...
@JsonDeserialize(using = RoleDeserializer.class)
public interface RoleDto {}

public class RoleDeserializer extends SomeSharedDeserializer<RoleDto> {}

public class SomeSharedDeserializer<T> extends JsonDeserializer<T> {
    @Override
    public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException
    {
        return jp.readValueAs(getImplementation());
    }

    public Class<? extends T> getImplementation(){ ... returns some generated implementation of RoleDto }
}

After we migrated to the last jackson version (1.9.13 provided by Wildfly 8.2) we got an exception:

com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of RoleDto, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information

Ok, as in jackson new packages are used, we upgraded them to:

import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer;

The deserializer is visible now (the previous exception is gone), However, we get StackOverflowError exception. The com.fasterxml.jackson.databind.ObjectMapper reads value (line 3023):

    DeserializationContext ctxt = createDeserializationContext(jp, cfg);
    JsonDeserializer<Object> deser = _findRootDeserializer(ctxt, valueType);
    // ok, let's get the value
    if (cfg.useRootWrapping()) {
        result = _unwrapAndDeserialize(jp, ctxt, cfg, valueType, deser);
    } else {
        result = deser.deserialize(jp, ctxt);
    }

We go to the line: result = deser.deserialize(jp, ctxt);

It causes to infinite loop and StackOverflowError as a result.

One of the way which is recommended is to implement our own SomeSharedDeserializer as:

ObjectCodec oc = jp.getCodec();
JsonNode node = oc.readTree(jp);
//here manually create new object and return it

But our classes are generated. As another solution I tried to use

ObjectMapper mapper = new ObjectMapper();
mapper.readValue(jp, getImplementation());

But got the same result - StackOverflow exception.

How can we fix it? Can we use some deserializer, to pass it JsonParser instance, generated class that implements base interface and without StackOverflowError?

like image 938
Alexandr Avatar asked Nov 10 '22 20:11

Alexandr


1 Answers

Here is you can find a full description and trials to find a solution. The following solution has been found:

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.cfg.DeserializerFactoryConfig;
import com.fasterxml.jackson.databind.deser.BeanDeserializerFactory;
import com.fasterxml.jackson.databind.deser.ResolvableDeserializer;
import com.fasterxml.jackson.databind.type.SimpleType;
...
    public abstract class RestDtoDeserializer<T> extends JsonDeserializer<T>
    {
        @Override
        public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException
        {
            DeserializationConfig config = ctxt.getConfig();
            SimpleType simpleType = SimpleType.construct(getImplementationClass());
            BeanDescription beanDesc = config.introspect(simpleType);
            BeanDeserializerFactory instance = new BeanDeserializerFactory(new DeserializerFactoryConfig());
            JsonDeserializer deserializer = instance.buildBeanDeserializer(ctxt, simpleType, beanDesc);
            ((ResolvableDeserializer)deserializer).resolve(ctxt);
            return (T) deserializer.deserialize(jp, ctxt);
        }

        public abstract Class<? extends T> getImplementationClass();
like image 198
Alexandr Avatar answered Nov 14 '22 23:11

Alexandr