diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java index e4752611fc..f732ac26f6 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java @@ -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; @@ -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; @@ -2775,6 +2781,37 @@ protected Discriminator resolveDiscriminator(JavaType type, ModelConverterContex } } } + } else { + SerializationConfig config = _mapper.getSerializationConfig(); + AnnotationIntrospector introspector = config.getAnnotationIntrospector(); + List subTypes = introspector.findSubtypes( + 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; diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/converting/CompositionTest.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/converting/CompositionTest.java index db26e54d64..58ae410bf8 100644 --- a/modules/swagger-core/src/test/java/io/swagger/v3/core/converting/CompositionTest.java +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/converting/CompositionTest.java @@ -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; @@ -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 schemas = ModelConverters.getInstance().readAll(cls); Json.prettyPrint(schemas); diff --git a/modules/swagger-core/src/test/java/io/swagger/v3/core/oas/models/composition/ModelWithDiscriminatorMapping.java b/modules/swagger-core/src/test/java/io/swagger/v3/core/oas/models/composition/ModelWithDiscriminatorMapping.java new file mode 100644 index 0000000000..33f3883849 --- /dev/null +++ b/modules/swagger-core/src/test/java/io/swagger/v3/core/oas/models/composition/ModelWithDiscriminatorMapping.java @@ -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; } + } +} diff --git a/modules/swagger-core/src/test/resources/Animal.json b/modules/swagger-core/src/test/resources/Animal.json index a5b8f86f18..36d8d62212 100644 --- a/modules/swagger-core/src/test/resources/Animal.json +++ b/modules/swagger-core/src/test/resources/Animal.json @@ -10,7 +10,11 @@ } }, "discriminator" : { - "propertyName" : "type" + "propertyName" : "type", + "mapping" : { + "human" : "#/components/schemas/Human", + "pet" : "#/components/schemas/Pet" + } } }, "Human": { diff --git a/modules/swagger-core/src/test/resources/AnimalClass.json b/modules/swagger-core/src/test/resources/AnimalClass.json index 02d53ec73c..21ccd3c7a4 100644 --- a/modules/swagger-core/src/test/resources/AnimalClass.json +++ b/modules/swagger-core/src/test/resources/AnimalClass.json @@ -10,7 +10,11 @@ } }, "discriminator" : { - "propertyName" : "type" + "propertyName" : "type", + "mapping" : { + "human" : "#/components/schemas/HumanClass", + "pet" : "#/components/schemas/PetClass" + } } }, "HumanClass": { diff --git a/modules/swagger-core/src/test/resources/ModelWithDiscriminatorMapping.json b/modules/swagger-core/src/test/resources/ModelWithDiscriminatorMapping.json new file mode 100644 index 0000000000..ae3d9a0226 --- /dev/null +++ b/modules/swagger-core/src/test/resources/ModelWithDiscriminatorMapping.json @@ -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" + } + } + } ] + } +} \ No newline at end of file