Skip to content
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

Creator properties are ignored on abstract types when collecting bean properties, breaking AsExternalTypeDeserializer #4920

Open
1 task done
zhenlin-pay2 opened this issue Jan 23, 2025 · 4 comments
Labels
to-evaluate Issue that has been received but not yet evaluated

Comments

@zhenlin-pay2
Copy link

Search before asking

  • I searched in the issues and found nothing similar.

Describe the bug

When using Jackson with frameworks such as Immutables where users are expected to write bean definitions as abstract types and let the framework generate concrete implementations, Jackson annotations on the abstract type sometimes have no effect, or worse, trigger bugs. This appears to be due to the assumption in BeanDeserializerFactory.addBeanProps that creator properties do not need to be considered for abstract types. In particular, deserialisation fails with a bizarre exception in in the following situation:

  • The type to be deserialised is abstract but with a defined ValueInstantiator (e.g. from a@JsonCreator annotated factory method).
  • The property to be deserialised has a getter but no setter.
  • The type of the property to be deserialised is also abstract.
  • The getter has a @JsonTypeInfo annotation with include = JsonTypeInfo.As.EXTERNAL_PROPERTY.

Version Information

2.17.1

Reproduction

See attached: example.zip

Expected behavior

No response

Additional context

Deserialisation fails with the following exception:

Unexpected token (VALUE_STRING), expected START_ARRAY: need Array value to contain `As.WRAPPER_ARRAY` type information for class example.TypedData$Value
 at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 2, column: 20] (through reference chain: example.TypedData["value"])
com.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token (VALUE_STRING), expected START_ARRAY: need Array value to contain `As.WRAPPER_ARRAY` type information for class example.TypedData$Value
 at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 2, column: 20] (through reference chain: papaya.struct.jackson.TypedData["value"])
	at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59)
	at com.fasterxml.jackson.databind.DeserializationContext.wrongTokenException(DeserializationContext.java:1913)
	at com.fasterxml.jackson.databind.DeserializationContext.reportWrongTokenException(DeserializationContext.java:1699)
	at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer._locateTypeId(AsArrayTypeDeserializer.java:141)
	at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer._deserialize(AsArrayTypeDeserializer.java:96)
	at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer.deserializeTypedFromObject(AsArrayTypeDeserializer.java:61)
	at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserializeWithType(AbstractDeserializer.java:263)
	at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:542)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:570)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:440)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1493)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:348)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:185)
	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)

Notice that the call chain goes through BeanDeserializer.deserializeFromObjectUsingNonDefault rather than the expected BeanDeserializer.deserializeWithExternalTypeId. This is because _externalTypeIdHandler is null, which is because when BeanDeserializerBase.resolve is called, _beanProperties is empty (!), which is because BeanDeserializerFactory.addBeanProps ignores creator properties when the type is abstract. Changing the value of isConcrete in addBeanProps to true while running in a debugger suffices to fix this problem, though I cannot say if this has any other effects.

@zhenlin-pay2 zhenlin-pay2 added the to-evaluate Issue that has been received but not yet evaluated label Jan 23, 2025
@pjfanning
Copy link
Member

Have you tried other versions of jackson-databind, eg v2.18.2?

@zhenlin-pay2
Copy link
Author

The line in question has not been changed for 8 years, but yes, I confirm that it is broken in 2.18.2 too.

final SettableBeanProperty[] creatorProps = isConcrete

@cowtowncoder
Copy link
Member

In case of abstract types, perhaps introspection could try to find factory Creator methods -- constructors should not be used as they cannot really be used directly.

@zhenlin-pay2
Copy link
Author

Isn't that what the @JsonCreator annotation is for? It is there in the example and does work in basic scenarios (e.g. without polymorphic deserialisation). There are other ways of making a ValueInstantiator available too – the code where I am encountering this problem uses bytecode generation and installs a custom ValueInstantiator.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
to-evaluate Issue that has been received but not yet evaluated
Projects
None yet
Development

No branches or pull requests

3 participants