michel-kraemer/bson4jackson

Jackson 2.15.0 - Error deserializing an @JsonUnwrapped object with a field of type float

amombourquette opened this issue · 6 comments

This works as of Jackson 2.14.2, and fails with Jackson 2.15.0, both using bson4jackson 2.13.1

Example class being deserialized: (groovy syntax)

static class OuterClass {
    @JsonUnwrapped
    public InnerClass inner = new InnerClass()
    OuterClass() {}
}

static class InnerClass {
    public float floatValue = 40.0f
    public InnerClass() {}
}

Failing test (but passes with older jackson):

OuterClass outer = new OuterClass()
outer.inner.floatValue = 50.0f
ByteArrayOutputStream baos = new ByteArrayOutputStream()
ObjectMapper BSON_MAPPER = new ObjectMapper(new BsonFactory())
BSON_MAPPER.writeValue(baos, outer)
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray())
OuterClass clone = BSON_MAPPER.readValue(bais, OuterClass.class)
assertThat(clone.inner.floatValue).isEqualTo(outer.inner.floatValue)

I am uncertain if the actual bug exists in jackson or bson4jackson, but the following test using only jackson passes for both old and new versions: (This will use a ReaderBasedJsonParser instead of a BsonParser)

ObjectMapper MAPPER = new ObjectMapper()
OuterClass outer = MAPPER.readValue("{\"floatValue\":50.0}", OuterClass.class)
assertThat(outer.inner.floatValue).isEqualTo(50.0f)

One relevant change in jackson is in TokenBuffer._copyBufferValue(JsonParser p, JsonToken t)
2.14 version vs 2.15 version
2.14 version calls JsonParser.getNumberValueExact() which was overridden in BsonParser, which gets the value out of BsonParser.Context.value
2.15 version calls JsonParser.getNumberValueDeferred() which is not implemented by BsonParser, so it calls ParserBase.
getNumberValueDeferred(), which gets the value from _numberString, _numberFloat, _textBuffer as needed.

Hi @michel-kraemer , I wonder if you have any time to let me know your thoughts on this? Thanks!

com.fasterxml.jackson.databind.JsonMappingException: empty String (through reference chain: com.apptegic.excavate.util.JacksonUnwrappedTest$InnerClass["floatValue"])

	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:1830)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:394)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:185)
	at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:138)
	at com.fasterxml.jackson.databind.deser.impl.UnwrappedPropertyHandler.processUnwrapped(UnwrappedPropertyHandler.java:62)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeWithUnwrapped(BeanDeserializer.java:765)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:347)
	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.ObjectMapper._readMapAndClose(ObjectMapper.java:4825)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3809)
	
Caused by: java.lang.NumberFormatException: empty String
	at java.base/jdk.internal.math.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
	at java.base/jdk.internal.math.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
	at java.base/java.lang.Double.parseDouble(Double.java:543)
	at com.fasterxml.jackson.core.io.NumberInput.parseDouble(NumberInput.java:388)
	at com.fasterxml.jackson.databind.util.TokenBuffer$Parser.getNumberValue(TokenBuffer.java:1936)
	at com.fasterxml.jackson.databind.util.TokenBuffer$Parser.getNumberValue(TokenBuffer.java:1891)
	at com.fasterxml.jackson.databind.util.TokenBuffer$Parser.getFloatValue(TokenBuffer.java:1847)
	at com.fasterxml.jackson.databind.deser.std.NumberDeserializers$FloatDeserializer.deserialize(NumberDeserializers.java:596)
	at com.fasterxml.jackson.databind.deser.std.NumberDeserializers$FloatDeserializer.deserialize(NumberDeserializers.java:579)
	at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:138)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:392)
	... 35 more

Encountering the same issue with polymorphic deserialization using JsonTypeInfo.As.PROPERTY, if the first property that is encountered is not the type property. This scenario leads to use of the TokenBuffer which calls getNumberValueDeferred on ParserBase because BsonParser has no implementation for it yet.

The default implementation in JsonParser just calls getNumberValue but it is overridden in ParserBase (from which BsonParser extends). Is it an idea to override getNumberValueDeferred in BsonParser to restore the default getNumberValue implementation?

getNumberValueDeferred:689, ParserBase (com.fasterxml.jackson.core.base)
_copyBufferValue:1274, TokenBuffer (com.fasterxml.jackson.databind.util)
copyCurrentStructure:1194, TokenBuffer (com.fasterxml.jackson.databind.util)
deserializeTypedFromObject:143, AsPropertyTypeDeserializer (com.fasterxml.jackson.databind.jsontype.impl)
deserializeWithType:1296, BeanDeserializerBase (com.fasterxml.jackson.databind.deser)
deserialize:74, TypeWrappedDeserializer (com.fasterxml.jackson.databind.deser.impl)
readRootValue:323, DefaultDeserializationContext (com.fasterxml.jackson.databind.deser)
_readMapAndClose:4825, ObjectMapper (com.fasterxml.jackson.databind)
readValue:3833, ObjectMapper (com.fasterxml.jackson.databind)
bernd commented

The same issue appears in newer mongojack versions. There is a fix: mongojack/mongojack#241

bernd commented

I created a pull request to fix this issue: #122

Nice work! Thanks for the PR, @bernd! I've just published version 2.15.0 to fix this issue.