OpenAPITools / OpenAPITools/openapi-generator
[BUG] [Spring] Jackson does not correctly deserialize discriminator when discriminator included as object within yaml file
Nobody has claimed this yet.
- 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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