jackson-databind
jackson-databind copied to clipboard
Converter doesn't work without @JsonCreator for Records
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.