FasterXML / jackson-databind

General data-binding package for Jackson (2.x): works on streaming API (core) implementation(s)
Apache License 2.0
3.52k stars 1.38k forks source link

`ObjectMapper.convertValue()` does not work with `@JsonTypeInfo` #3151

Closed geohuk closed 3 years ago

geohuk commented 3 years ago

Describe the bug When using ObjectMapper to serialize and deserialize an object that contains JsonTypeInfo a MismatchedInputException is produced:

java.lang.IllegalArgumentException: Unexpected token (VALUE_STRING), expected START_OBJECT: need JSON Object to contain As.WRAPPER_OBJECT type information for class com.cumulocity.connectivity.provider.kite.model.error.ClientException
 at [Source: UNKNOWN; line: -1, column: -1]

    at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:4234)
    at com.fasterxml.jackson.databind.ObjectMapper.convertValue(ObjectMapper.java:4165)
    at my.jackson.test.ClientExceptionTest.serializeDeserializeTest(ClientExceptionTest.java:23)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:221)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token (VALUE_STRING), expected START_OBJECT: need JSON Object to contain As.WRAPPER_OBJECT type information for class com.cumulocity.connectivity.provider.kite.model.error.ClientException
 at [Source: UNKNOWN; line: -1, column: -1]
    at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59)
    at com.fasterxml.jackson.databind.DeserializationContext.wrongTokenException(DeserializationContext.java:1650)
    at com.fasterxml.jackson.databind.DeserializationContext.reportWrongTokenException(DeserializationContext.java:1400)
    at com.fasterxml.jackson.databind.jsontype.impl.AsWrapperTypeDeserializer._deserialize(AsWrapperTypeDeserializer.java:100)
    at com.fasterxml.jackson.databind.jsontype.impl.AsWrapperTypeDeserializer.deserializeTypedFromObject(AsWrapperTypeDeserializer.java:52)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeWithType(BeanDeserializerBase.java:1209)
    at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:68)
    at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:4229)
    ... 24 more

Version information 2.11.2

To Reproduce Here is my model:

package my.jackson.test;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import lombok.Data;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(include = As.WRAPPER_OBJECT, use = Id.NAME)
public class ClientException {
    private String exceptionCategory;
    private String exceptionId;
    private String text;
}

JUnit test case that causes the exception:

package my.jackson.test;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import static org.junit.Assert.*;

@Slf4j
public class ClientExceptionTest {

    @Test
    public void serializeDeserializeTest() throws JsonProcessingException {
        ClientException clientException = new ClientException();
        clientException.setExceptionId("someId");
        clientException.setExceptionCategory("someCat");
        clientException.setText("someText");

        ObjectMapper mapper = new ObjectMapper();
        String clientExceptionString = mapper.writeValueAsString(clientException);
        ClientException actual = mapper.convertValue(clientExceptionString, ClientException.class);
        assertEquals(clientException, actual);
    }

}

Expected behavior Object serialized by ObjectMapper can be deserialized by ObjectMapper.

cowtowncoder commented 3 years ago

Quick note: convertValue() is quite likely to have issues with polymorphic typing: this combination is not meant to be used (although as of now, there is no checking to disallow such usage).

Does mapper.readValue() works in this case?

geohuk commented 3 years ago

Yes it does work fine with mapper.readValue(...). Interesting thanks!

cowtowncoder commented 3 years ago

Right: the problem wrt convertValue() is that polymorphic typing requires strict type information as additional metadata -- but this typically means that structures of polymorphic-enabled and non-polymorphic values differ (are incompatible).

So if your input is in form of serialized JSON (or other supported formats) -- a byte[], InputStream, String etc -- you should be using readValue(). "convertValue()" should only be used across Java types for which polymorphic handling is not enabled.