Search before asking

  • [x] I searched in the issues and found nothing similar.

Describe the bug

In Jackson 3.0.0, parsing a simple JSON Object, such as the following:

{
    "key" : "value"
}

as a Map<String, String> via parser.readValueAs(new TypeReference<Map<String, String>>() { })

fails with the following exception:

tools.jackson.databind.exc.MismatchedInputException: Unexpected end-of-input when trying read value of type `java.util.LinkedHashMap<java.lang.String,java.lang.String>`
 at [Source: (String)"{
    "key" : "value"
}
"; line: 1, column: 0]

    at tools.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
    at tools.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1822)
    at tools.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1618)
    at tools.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1567)
    at tools.jackson.databind.deser.jdk.MapDeserializer.deserialize(MapDeserializer.java:439)
    at tools.jackson.databind.deser.jdk.MapDeserializer.deserialize(MapDeserializer.java:30)
    at tools.jackson.databind.DeserializationContext._readValue(DeserializationContext.java:403)
    at tools.jackson.databind.DeserializationContext.readValue(DeserializationContext.java:391)
    at tools.jackson.databind.DeserializationContext.readValue(DeserializationContext.java:372)
    at tools.jackson.core.base.ParserMinimalBase.readValueAs(ParserMinimalBase.java:815)
    at Jackson3ParserTest.testJackson3Parser(Jackson3ParserTest.java:26)

It works with Jackson 2.20.0

Version Information

Does not work with Jackson 3.0.0 Works with Jackson 2.20.0

Reproduction

I have attached a simple project (jackson-parser-test.zip) with unit tests for Jackson 2 (works) and Jackson 3 (does not work).

The unit test that fails for Jackson 3 looks like this:

import org.junit.jupiter.api.Test;
import tools.jackson.core.JsonParser;
import tools.jackson.core.StreamReadFeature;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.json.JsonMapper;

import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class Jackson3ParserTest {

    @Test
    void testJackson3Parser() {
        String jsonText = """
                {
                    "key" : "value"
                }
                """;

        JsonMapper jsonMapper = JsonMapper.builder()
                .enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION)
                .build();

        try (JsonParser parser = jsonMapper.createParser(jsonText)) {
            Map<String, String> result = parser.readValueAs(new TypeReference<Map<String, String>>() { });
            assertEquals("value", result.get("key"));
        }
    }
}

Expected behavior

Expect the call to parser.readValueAs(new TypeReference<Map<String, String>>() { }) to not throw an exception, and return a Map<String, String> object populated with the correct entries.

Additional context

No response

Comment From: philsttr

As a workaround for Jackson 3, if I change the code to explicitly advance the parser to the first token, it works ...

        try (JsonParser parser = jsonMapper.createParser(jsonText)) {

            // BEGIN workaround for https://github.com/FasterXML/jackson-databind/issues/5344
            if (parser.currentToken() == null) {
                parser.nextToken();
            }
            // END workaround

            Map<String, String> result = parser.readValueAs(new TypeReference<Map<String, String>>() { });
            assertEquals("value", result.get("key"));
        }

In Jackson 2 this was not necessary, because Jackson 2 calls an _initForReading method that advanced the parser to the first token automatically.

Comment From: philsttr

Another workaround is to call jsonMapper.readValue instead...

        try (JsonParser parser = jsonMapper.createParser(jsonText)) {
            Map<String, String> result = jsonMapper.readValue(parser, new TypeReference<Map<String, String>>() { });
            assertEquals("value", result.get("key"));
        }

So it appears the bug only affects calls to parser.readValueAs(...)

Comment From: cowtowncoder

Hmmmh. This is a tricky one to reason about, I'll have to think about it. My first instinct is that it is a bug, but there may be some considerations on how changes affect other handling.

The reason for difference b/w JsonParser.readValueAs(type) and ObjectMapper.readValue(JsonParser, type) is that latter by-passes set up of DeserializationContext whereas latter constructs a new one. For what that is worth -- not something users are expected to know (or have to care about).

Comment From: cowtowncoder

This might be easiest to fix at JsonParser level, actually, since ObjectReadContext.readValue(...) expects properly positioned JsonParser.

Will need to create matching issue on jackson-core, set up test scaffolding (w/ test ObjectReadContext). Probably create tests on databind side too to verify functionality.

Comment From: cowtowncoder

Actually easy enough to address completely on databind side, see #5345

Comment From: philsttr

Thanks for the quick turn around, and congrats on the 3.0 release! The improvements are really nice.

Comment From: cowtowncoder

Thank you for reporting the issue!