jackson-databind icon indicating copy to clipboard operation
jackson-databind copied to clipboard

Converter doesn't work without @JsonCreator for Records

Open zman0900 opened this issue 2 years ago • 14 comments

Describe the bug When annotation like @JsonDeserialize(converter = MyConverter.class) is applied to a component of a Record or to a field of an immutable class (such as lombok @Value or just plain class with final fields, all-args constructor, and only getters), it seems the Converter is not actually used unless @JsonCreator is also present on the constructor.

Version information Jackson Databind 2.14.1 (with parameter names module) Java 17

To Reproduce Given converter class like:

public class MyConverter extends StdConverter<String, Long> {
    @Override
    public Long convert(final String value) {
        return // some special conversion of string to long
    }
}

And some JSON like this:

{
  "maybeNumber":"blah",
  "other":"value"
}

I believe Jackson should be able to bind this Record:

public record TestRec(
    @JsonDeserialize(converter = MyConverter.class)
    long maybeNumber,
    String other
) { }

Or bind this class:

@lombok.Value
public class TestVal {
    long maybeNumber;
    String other;
}

But these lead to errors like:

com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `long` from String "blah": not a valid `long` value
 at [Source: (String)"{
 "maybeNumber":"blah",
 "other":"value"
}"; line: 2, column: 15] (through reference chain: something.TestRec["maybeNumber"])
	at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.java:67)
	at com.fasterxml.jackson.databind.DeserializationContext.weirdStringException(DeserializationContext.java:1996)
	at com.fasterxml.jackson.databind.DeserializationContext.handleWeirdStringValue(DeserializationContext.java:1224)
	at com.fasterxml.jackson.databind.deser.std.StdDeserializer._parseLongPrimitive(StdDeserializer.java:916)
	at com.fasterxml.jackson.databind.deser.std.StdDeserializer._parseLongPrimitive(StdDeserializer.java:904)
	at com.fasterxml.jackson.databind.deser.std.NumberDeserializers$LongDeserializer.deserialize(NumberDeserializers.java:573)
	at com.fasterxml.jackson.databind.deser.std.NumberDeserializers$LongDeserializer.deserialize(NumberDeserializers.java:550)
	at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:542)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:564)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:439)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1405)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:352)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:185)
	at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:323)
	at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:2105)
	at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1546)

Adding a compact constructor to the Record (or explicit all-args constructor to the class) that is annotated with @JsonCreator works around this.

public record TestRec(
    @JsonDeserialize(converter = MyConverter.class)
    long maybeNumber,
    String other
) { 
    @JsonCreator
    public TestRec {
    }
}

I don't believe this should be necessary, as using custom deserializer like @JsonDeserialize(using = MyDeserializer.class) seems to work fine without it.

zman0900 avatar Jan 20 '23 20:01 zman0900