FasterXML / FasterXML/jackson-databind
Support conditional type ID inclusion during deserialization
- Dominant language
- Java
- Stars
- 3.7k
- Forks
- 1.5k
- Avg merge
- 3d 6h
- Merged PRs (30d)
- 28
Description
### Is your feature request related to a problem? Please describe.
When a default implementation is used on a polymorphic `@JsonTypeInfo` the `visible = true` attribute needs to be set for that default implementation to know the missing type. However for the non-default cases usually the type is known ahead of time, and making the `type` property visible to the concrete deserializer adds significant allocation cost for the intermediate `TokenBuffer`.
Specifically, [this](https://github.com/FasterXML/jackson-databind/blob/7b3ed3244e7a2b9276daced7eaf9dd28357f420c/src/main/java/tools/jackson/databind/jsontype/impl/AsPropertyTypeDeserializer.java#L131-L143) code snippet allocates a token buffer (with a 16 element segment), the associated `TokenBuffer$Parser`, and `JsonParserSequence`.
### Describe the solution you'd like
Ideally, adding a third option to the `JsonTypeInfo.visible` field that indicates "visible only to the default implementation", but this is likely fairly complicated due to the field currently being a boolean.
Alternatively, perhaps adding an optimized `JsonParser` subtype like `PrependedTypeProperty(String typeField, String typeId, JsonParser delegate)` that avoids the majority of the allocations.
Currently, we're using our own subtype of `AsPropertyTypeDeserializer` that overrides `_deserializeTypedForId` that overrides `if (_typeIdVisible) {` to instead check `if (deserializer == _defaultImplDeserializer) {` in a few targeted places we've observed this performance issue, but this is not ideal because those are internal APIs. If modifying `@JsonTypeInfo` is too high lift, perhaps adding the option to the public API of `AsPropertyTypeDeserializer` or `StdTypeResolverBuilder` would be an alternative.
### Performance Test
```java
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "type",
visible = true,
defaultImpl = Default.class)
@JsonSubTypes(@JsonSubTypes.Type(value = Concrete.class, name = "concrete"))
private interface Base {}
private record Default(String type) implements Base {}
@JsonTypeName("concrete")
@JsonPropertyOrder({"type", "content"})
private static final class Concrete implements Base {
@JsonProperty("type")
public String type() {
return "concrete";
}
@JsonProperty("content")
public int content;
}
public static void main(String[] args) throws IOException {
ObjectMapper objectMapper =
new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Concrete obj = new Concrete();
obj.content = 12345;
byte[] bytes = objectMapper.writeValueAsBytes(
IntStream.range(0, 1000).mapToObj(_x -> obj).toList());
TypeReference> type = new TypeReference<>() {};
for (int i = 0; i < 10_000; i++) {
objectMapper.readValue(bytes, type);
}
}
```
### Additional context
_No response_
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with AsPropertyTypeDeserializer, especially _deserializeTypedForId and the referenced lines 131-143, then review JsonTypeInfo.visible and the public AsPropertyTypeDeserializer or StdTypeResolverBuilder APIs. Reproduce the supplied performance test and verify that default implementations can receive the type while non-default deserializers avoid the unnecessary intermediate allocations.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- backend, performance
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100