Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,23 @@
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyMetadata;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotatedClass;
import com.fasterxml.jackson.databind.introspect.AnnotatedClassResolver;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.util.Annotations;
import io.swagger.v3.core.converter.AnnotatedType;
import io.swagger.v3.core.converter.ModelConverter;
Expand All @@ -39,6 +44,7 @@
import io.swagger.v3.core.util.ReferenceTypeUtils;
import io.swagger.v3.core.util.PrimitiveType;
import io.swagger.v3.core.util.ReflectionUtils;
import io.swagger.v3.core.util.RefUtils;
import io.swagger.v3.core.util.ValidatorProcessor;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Parameter;
Expand Down Expand Up @@ -2775,6 +2781,37 @@ protected Discriminator resolveDiscriminator(JavaType type, ModelConverterContex
}
}
}
} else {
SerializationConfig config = _mapper.getSerializationConfig();
AnnotationIntrospector introspector = config.getAnnotationIntrospector();
List<NamedType> subTypes = introspector.findSubtypes(
Copy link
Author

@PatrickFeiring PatrickFeiring Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As outlined in the issue comment, there are likely ways to find subtypes in a more exhaustive manner, but using the introspector seemed a good conservative starting point that covers the most common use-cases, as I don't know Jackson internals all that well
#3411 (comment)

AnnotatedClassResolver.resolveWithoutSuperTypes(config, type.getRawClass())
);

if (subTypes != null && !subTypes.isEmpty()) {
try {
TypeSerializer serializer = _mapper.getSerializerFactory().createTypeSerializer(
config, type
);

if (serializer != null) {
TypeIdResolver resolver = serializer.getTypeIdResolver();

for (NamedType subType : subTypes) {
String schemaName = context.resolve(new AnnotatedType().type(subType.getType())).getName();
String subTypeName = resolver.idFromValueAndType(null, subType.getType());

// Per the specification, there is an implicit map to schemas with the same name
// We skip writing the mappings that are implied to keep the schema minimal
if (!subTypeName.equals(schemaName)) {
discriminator.mapping(subTypeName, RefUtils.constructRef(schemaName));
}
}
}
} catch (JsonMappingException e) {
LOGGER.error("Unable to create type serializer", e);
}
}
}

return discriminator;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import io.swagger.v3.core.oas.models.composition.AnimalWithSchemaSubtypes;
import io.swagger.v3.core.oas.models.composition.Human;
import io.swagger.v3.core.oas.models.composition.ModelWithFieldWithSubTypes;
import io.swagger.v3.core.oas.models.composition.ModelWithDiscriminatorMapping;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.core.util.ResourceUtils;
import io.swagger.v3.oas.models.media.Schema;
Expand Down Expand Up @@ -48,6 +49,11 @@ public void createModelWithFieldWithSubTypes() throws IOException {
compareAsJson(ModelWithFieldWithSubTypes.class, "ModelWithFieldWithSubTypes.json");
}

@Test(description = "create a ModelWithDiscriminatorMapping")
public void createModelWithDiscriminatorMapping() throws IOException {
compareAsJson(ModelWithDiscriminatorMapping.class, "ModelWithDiscriminatorMapping.json");
}

private void compareAsJson(Class<?> cls, String fileName) throws IOException {
final Map<String, Schema> schemas = ModelConverters.getInstance().readAll(cls);
Json.prettyPrint(schemas);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.swagger.v3.core.oas.models.composition;

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;

@JsonTypeInfo(use= JsonTypeInfo.Id.SIMPLE_NAME, include = JsonTypeInfo.As.PROPERTY, property="kind")
@JsonSubTypes({
@JsonSubTypes.Type(value = ModelWithDiscriminatorMapping.First.class),
@JsonSubTypes.Type(value = ModelWithDiscriminatorMapping.Second.class, name = "SecondType"),
@JsonSubTypes.Type(value = ModelWithDiscriminatorMapping.Third.class)
})
public class ModelWithDiscriminatorMapping {

public static class First extends ModelWithDiscriminatorMapping {
private String name;

public String getName() { return name; }

public void setName(String name) { this.name = name; }
}

public static class Second extends ModelWithDiscriminatorMapping {
private String value;

public String getValue() { return value; }

public void setValue(String value) { this.value = value; }
}

@JsonTypeName("ThirdType")
public static class Third extends ModelWithDiscriminatorMapping {
private String value;

public String getValue() { return value; }

public void setValue(String value) { this.value = value; }
}
}
6 changes: 5 additions & 1 deletion modules/swagger-core/src/test/resources/Animal.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
}
},
"discriminator" : {
"propertyName" : "type"
"propertyName" : "type",
"mapping" : {
"human" : "#/components/schemas/Human",
"pet" : "#/components/schemas/Pet"
}
}
},
"Human": {
Expand Down
6 changes: 5 additions & 1 deletion modules/swagger-core/src/test/resources/AnimalClass.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
}
},
"discriminator" : {
"propertyName" : "type"
"propertyName" : "type",
"mapping" : {
"human" : "#/components/schemas/HumanClass",
"pet" : "#/components/schemas/PetClass"
}
}
},
"HumanClass": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"First" : {
"type" : "object",
"allOf" : [ {
"$ref" : "#/components/schemas/ModelWithDiscriminatorMapping"
}, {
"type" : "object",
"properties" : {
"name" : {
"type" : "string"
}
}
} ]
},
"ModelWithDiscriminatorMapping" : {
"required" : [ "kind" ],
"type" : "object",
"properties" : {
"kind" : {
"type" : "string"
}
},
"discriminator" : {
"propertyName" : "kind",
"mapping" : {
"SecondType" : "#/components/schemas/Second",
"ThirdType" : "#/components/schemas/Third"
}
}
},
"Second" : {
"type": "object",
"allOf": [
{
"$ref": "#/components/schemas/ModelWithDiscriminatorMapping"
},
{
"type": "object",
"properties": {
"value": {
"type": "string"
}
}
}
]
},
"Third" : {
"type" : "object",
"allOf" : [ {
"$ref" : "#/components/schemas/ModelWithDiscriminatorMapping"
}, {
"type" : "object",
"properties" : {
"value" : {
"type" : "string"
}
}
} ]
}
}