Skip to content

Support conditional type ID inclusion during deserialization #6147

Description

@westinrm

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 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

    @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<List<Base>> type = new TypeReference<>() {};
        for (int i = 0; i < 10_000; i++) {
            objectMapper.readValue(bytes, type);
        }
    }
Image

Additional context

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    to-evaluateIssue that has been received but not yet evaluated

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions