Skip to content

DeserializationProblemHandler.handleUnexpectedToken() cast Object to String #4656

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
1 task done
yacine-pc opened this issue Aug 6, 2024 · 6 comments
Closed
1 task done
Labels
2.20 Issues planned at 2.20 or later has-failing-test Indicates that there exists a test case (under `failing/`) to reproduce the issue
Milestone

Comments

@yacine-pc
Copy link

Search before asking

  • I searched in the issues and found nothing similar.

Describe the bug

I am currently migrating from Jackson version 2.10.0 to 2.15.1 and I seem to be encountering a bug. When attempting to deserialize JSON into a POJO, a JsonMappingException (Cf. Stacktrace below) is thrown for the age field in the simplified code snippet provided below. The same code works perfectly with version 2.10.0.

Version Information

2.15.2

Reproduction

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;

import java.io.IOException;

class Person {
    public String id;
    public String name;
    public Long age;
}

class MongoTypeProblemHandler extends DeserializationProblemHandler {
    protected static final String NUMBER_LONG_KEY = "$numberLong";

    @Override
    public Object handleUnexpectedToken(DeserializationContext ctxt, JavaType targetType, JsonToken t, JsonParser p, String failureMsg) throws IOException {
        if (targetType.getRawClass().equals(Long.class) && JsonToken.START_OBJECT.equals(t)) {
            JsonNode tree = p.readValueAsTree();
            if (tree.get(NUMBER_LONG_KEY) != null) {
                try {
                    return Long.parseLong(tree.get(NUMBER_LONG_KEY).asText());
                } catch (NumberFormatException e) {
                }
            }
        }
        return NOT_HANDLED;
    }
}

public class Main {
    public static void main(String[] args) {
        String json = "{\"id\":  \"12ab\", \"name\": \"Bob\", \"age\": {\"$numberLong\": \"10\"}}";

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.addHandler(new MongoTypeProblemHandler());
        try {
            Person person = objectMapper.readValue(json, Person.class);
            System.out.println("id:" + person.id);
            System.out.println("name:" + person.name);
            System.out.println("age:" + person.age);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }

    }
}

Expected behavior

It should deserialize the JSON to POJO like version 2.10.0 for example

Additional context

Exception in thread "main" java.lang.RuntimeException: com.fasterxml.jackson.databind.JsonMappingException: class java.lang.Long cannot be cast to class java.lang.String (java.lang.Long and java.lang.String are in module java.base of loader 'bootstrap') (through reference chain: org.example.Person["age"])
at org.example.Main.main(Main.java:50)
Caused by: com.fasterxml.jackson.databind.JsonMappingException: class java.lang.Long cannot be cast to class java.lang.String (java.lang.Long and java.lang.String are in module java.base of loader 'bootstrap') (through reference chain: org.example.Person["age"])
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:402)
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:361)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.wrapAndThrow(BeanDeserializerBase.java:1853)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:316)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:177)
at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:323)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4825)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3772)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3740)
at org.example.Main.main(Main.java:45)
Caused by: java.lang.ClassCastException: class java.lang.Long cannot be cast to class java.lang.String (java.lang.Long and java.lang.String are in module java.base of loader 'bootstrap')
at com.fasterxml.jackson.databind.DeserializationContext.extractScalarFromObject(DeserializationContext.java:943)
at com.fasterxml.jackson.databind.deser.std.StdDeserializer._parseLong(StdDeserializer.java:949)
at com.fasterxml.jackson.databind.deser.std.NumberDeserializers$LongDeserializer.deserialize(NumberDeserializers.java:575)
at com.fasterxml.jackson.databind.deser.std.NumberDeserializers$LongDeserializer.deserialize(NumberDeserializers.java:550)
at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:138)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:314)
... 6 more

@yacine-pc yacine-pc added the to-evaluate Issue that has been received but not yet evaluated label Aug 6, 2024
@cowtowncoder cowtowncoder removed the to-evaluate Issue that has been received but not yet evaluated label Aug 12, 2024
@cowtowncoder
Copy link
Member

First things first: it would be good to verify that the issue still occurs in 2.17.2 as 2.15 is not the latest release.
I suspect it does but it'd be good to confirm.

Failure itself does seem like a regression.

@yacine-pc
Copy link
Author

yacine-pc commented Aug 12, 2024

Yes sorry for that, just tested with version 2.17.2, and the same error occurs. Here is the stack trace for 2.17.2:

Exception in thread "main" java.lang.RuntimeException: com.fasterxml.jackson.databind.JsonMappingException: class java.lang.Long cannot be cast to class java.lang.String (java.lang.Long and java.lang.String are in module java.base of loader 'bootstrap') (through reference chain: org.example.Person["age"])
	at org.example.Main.main(Main.java:50)
Caused by: com.fasterxml.jackson.databind.JsonMappingException: class java.lang.Long cannot be cast to class java.lang.String (java.lang.Long and java.lang.String are in module java.base of loader 'bootstrap') (through reference chain: org.example.Person["age"])
	at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:402)
	at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:361)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.wrapAndThrow(BeanDeserializerBase.java:1937)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:312)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:177)
	at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:342)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4905)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3848)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3816)
	at org.example.Main.main(Main.java:45)
Caused by: java.lang.ClassCastException: class java.lang.Long cannot be cast to class java.lang.String (java.lang.Long and java.lang.String are in module java.base of loader 'bootstrap')
	at com.fasterxml.jackson.databind.DeserializationContext.extractScalarFromObject(DeserializationContext.java:958)
	at com.fasterxml.jackson.databind.deser.std.StdDeserializer._parseLong(StdDeserializer.java:954)
	at com.fasterxml.jackson.databind.deser.std.NumberDeserializers$LongDeserializer.deserialize(NumberDeserializers.java:575)
	at com.fasterxml.jackson.databind.deser.std.NumberDeserializers$LongDeserializer.deserialize(NumberDeserializers.java:550)
	at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:138)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:310)
	... 6 more

@cowtowncoder cowtowncoder added the 2.18 Issues planned at 2.18 or later label Aug 12, 2024
@cowtowncoder
Copy link
Member

Thank you @yacine-pc .

I am not sure if this can be fixed or not (and won't necessarily have time to dig in deep in near future).

In the meantime I would recommend writing a custom deserializer, attach it like so:

@JsonDeserialize(using = MyLongDeserializer.class)
public Long age;

to handle things as expected. This would be more reliable mechanism than relying on DeserializationProblemHandler for recovery.

@cowtowncoder cowtowncoder added 2.19 Issues planned at 2.19 or later and removed 2.18 Issues planned at 2.18 or later labels Apr 23, 2025
@cowtowncoder
Copy link
Member

Had some time to look into this and it's... complicated. I can see what is happening: it has to do 2.12-added mechanism to help work with XML content. Cast exception is side-effect, but I am not quite sure yet how to resolve this issue.

cowtowncoder added a commit that referenced this issue May 8, 2025
@cowtowncoder cowtowncoder added the has-failing-test Indicates that there exists a test case (under `failing/`) to reproduce the issue label May 8, 2025
cowtowncoder added a commit that referenced this issue May 8, 2025
@cowtowncoder cowtowncoder added 2.20 Issues planned at 2.20 or later and removed 2.19 Issues planned at 2.19 or later labels May 18, 2025
@cowtowncoder
Copy link
Member

Ok. I know how to fix this... but fix cannot, alas, go in 2.19 since it requires some changes to semantics of extension point which, while meant as internal is still opened up and used across modules.

@cowtowncoder cowtowncoder changed the title DeserializationProblemHandler.handleUnexpectedToken cast Object to String DeserializationProblemHandler.handleUnexpectedToken() cast Object to String May 18, 2025
cowtowncoder added a commit that referenced this issue May 18, 2025
@cowtowncoder cowtowncoder added this to the 2.20.0 milestone May 18, 2025
@cowtowncoder
Copy link
Member

Phew. Lots of mechanical changes as this affects Joda and Java 8 Time modules as well.

Now fixed for 2.20(.0) (and 3.0.0(-rc5)).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2.20 Issues planned at 2.20 or later has-failing-test Indicates that there exists a test case (under `failing/`) to reproduce the issue
Projects
None yet
Development

No branches or pull requests

2 participants