OpenAPITools / OpenAPITools/openapi-generator

[BUG] [Spring] Jackson does not correctly deserialize discriminator when discriminator included as object within yaml file

Open
#2,835 1 comment 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Issue: Bug
Dominant language
Java
Stars
26.8k
Forks
7.7k
PR merge metrics
PR metrics pending

Description

Description

Jackson does not correctly deserialize discriminator when discriminator included as object

openapi-generator version

3.3.4

OpenAPI declaration file content or url

api.yaml

info:
  title: Sample API
  description: API description in Markdown.
  version: 1.0.0
paths:
  /class:
    get:
      summary: Get a class.
      description: Optional extended description in Markdown.
      responses:
        200:
          description: OK
          content:
            application/json:
              schema:
                $ref: 'schema.yaml#/components/schemas/BaseClass'

schema.yaml

components:
  schemas:
    ChildClass1:
      allOf:
        - $ref: '#/components/schemas/BaseClass'
        - type: object
    ChildClass2:
      allOf:
        - $ref: '#/components/schemas/BaseClass'
        - type: object
    BaseClass:
      type: object
      discriminator:
        propertyName: type
      required:
        - className
      properties:
        type:
          type: string
Command line used for generation

openapi-generate generate -g spring -i api.yaml -i generated

Steps to reproduce

Invoke the generator above using the provided inputs.

Results (sample)
    static class CollectionClass {
        @JsonProperty("items")
        BaseClass[] items = new BaseClass[2];
    }
    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", visible = true)
    @JsonSubTypes({
            @JsonSubTypes.Type(value = DescendentClass1.class, name = "1"),
            @JsonSubTypes.Type(value = DescendentClass2.class, name = "2")
    })
    static class BaseClass {
        @JsonProperty("type")
        private String type;
    }

   static class DescendentClass1 extends BaseClass {

    }

    static class DescendentClass2 extends BaseClass {

    }
Test being run
    @Test
    public void shouldMap2() throws Exception {
        var mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        var expected = new CollectionClass();
        expected.items[0] = new DescendentClass1();
        expected.items[1] = new DescendentClass2();

        var expectedJson = mapper.writeValueAsString(expected);

        System.out.println(expectedJson);

        var actual = mapper.readValue(expectedJson, CollectionClass.class);

        var serverNode = mapper.readTree(expectedJson);
        var serverValue= mapper.treeToValue(serverNode, CollectionClass.class);
    }
Expected Result

expectedJson: {"items":[{"type":"1"},{"type":"2"}]}

Actual Result
com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id 'null' as a subtype of [simple type, class trickle.dto.JacksonMappingTest$BaseClass]: known type ids = [1, 2, JacksonMappingTest$BaseClass] (for POJO property 'items')
 at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: trickle.dto.JacksonMappingTest$CollectionClass["items"]->java.lang.Object[][0])

	at com.fasterxml.jackson.databind.exc.InvalidTypeIdException.from(InvalidTypeIdException.java:43)
	at com.fasterxml.jackson.databind.DeserializationContext.invalidTypeIdException(DeserializationContext.java:1635)
	at com.fasterxml.jackson.databind.DeserializationContext.handleUnknownTypeId(DeserializationContext.java:1187)
	at com.fasterxml.jackson.databind.jsontype.impl.TypeDeserializerBase._handleUnknownTypeId(TypeDeserializerBase.java:291)
	at com.fasterxml.jackson.databind.jsontype.impl.TypeDeserializerBase._findDeserializer(TypeDeserializerBase.java:162)
	at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedForId(AsPropertyTypeDeserializer.java:113)
	at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:97)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeWithType(BeanDeserializerBase.java:1178)
	at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:197)
	at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:21)
	at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:136)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:288)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
	at com.fasterxml.jackson.databind.ObjectMapper._readValue(ObjectMapper.java:3984)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2276)
	at com.fasterxml.jackson.databind.ObjectMapper.treeToValue(ObjectMapper.java:2758)
	at trickle.dto.JacksonMappingTest.shouldMap2(JacksonMappingTest.java:78)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:532)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:171)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:167)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:114)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:59)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:108)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:112)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:112)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:220)
	at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:188)
	at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:202)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:181)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Suggest a fix

Upon removing the type property from BaseClass and rerunning the test, the correct output is produced. It seems like there are two instances of the type property attempting to be deserialized. Maybe something can be done as including the discriminator property as an existing property?

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the api.yaml and schema.yaml inputs, then reproduce the failure through the Spring generator using the command shown. Use the shouldMap2 test and generated BaseClass/CollectionClass output as the entry point; done means discriminator-based deserialization succeeds and produces the expected items without the InvalidTypeIdException.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, spring
Domain
api, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.