Skip to content

Commit ccfd386

Browse files
feat: infer discriminator mapping
1 parent d126742 commit ccfd386

File tree

6 files changed

+155
-2
lines changed

6 files changed

+155
-2
lines changed

modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,23 @@
1515
import com.fasterxml.jackson.databind.AnnotationIntrospector;
1616
import com.fasterxml.jackson.databind.BeanDescription;
1717
import com.fasterxml.jackson.databind.JavaType;
18+
import com.fasterxml.jackson.databind.JsonMappingException;
1819
import com.fasterxml.jackson.databind.ObjectMapper;
1920
import com.fasterxml.jackson.databind.PropertyMetadata;
21+
import com.fasterxml.jackson.databind.SerializationConfig;
2022
import com.fasterxml.jackson.databind.SerializationFeature;
2123
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
2224
import com.fasterxml.jackson.databind.introspect.Annotated;
2325
import com.fasterxml.jackson.databind.introspect.AnnotatedClass;
26+
import com.fasterxml.jackson.databind.introspect.AnnotatedClassResolver;
2427
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
2528
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
2629
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
2730
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
2831
import com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder;
2932
import com.fasterxml.jackson.databind.jsontype.NamedType;
33+
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
34+
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
3035
import com.fasterxml.jackson.databind.util.Annotations;
3136
import io.swagger.v3.core.converter.AnnotatedType;
3237
import io.swagger.v3.core.converter.ModelConverter;
@@ -39,6 +44,7 @@
3944
import io.swagger.v3.core.util.ReferenceTypeUtils;
4045
import io.swagger.v3.core.util.PrimitiveType;
4146
import io.swagger.v3.core.util.ReflectionUtils;
47+
import io.swagger.v3.core.util.RefUtils;
4248
import io.swagger.v3.core.util.ValidatorProcessor;
4349
import io.swagger.v3.oas.annotations.Hidden;
4450
import io.swagger.v3.oas.annotations.Parameter;
@@ -2775,6 +2781,37 @@ protected Discriminator resolveDiscriminator(JavaType type, ModelConverterContex
27752781
}
27762782
}
27772783
}
2784+
} else {
2785+
SerializationConfig config = _mapper.getSerializationConfig();
2786+
AnnotationIntrospector introspector = config.getAnnotationIntrospector();
2787+
List<NamedType> subTypes = introspector.findSubtypes(
2788+
AnnotatedClassResolver.resolveWithoutSuperTypes(config, type.getRawClass())
2789+
);
2790+
2791+
if (subTypes != null && !subTypes.isEmpty()) {
2792+
try {
2793+
TypeSerializer serializer = _mapper.getSerializerFactory().createTypeSerializer(
2794+
config, type
2795+
);
2796+
2797+
if (serializer != null) {
2798+
TypeIdResolver resolver = serializer.getTypeIdResolver();
2799+
2800+
for (NamedType subType : subTypes) {
2801+
String schemaName = context.resolve(new AnnotatedType().type(subType.getType())).getName();
2802+
String subTypeName = resolver.idFromValueAndType(null, subType.getType());
2803+
2804+
// Per the specification, there is an implicit map to schemas with the same name
2805+
// We skip writing the mappings that are implied to keep the schema minimal
2806+
if (!subTypeName.equals(schemaName)) {
2807+
discriminator.mapping(subTypeName, RefUtils.constructRef(schemaName));
2808+
}
2809+
}
2810+
}
2811+
} catch (JsonMappingException e) {
2812+
LOGGER.error("Unable to create type serializer", e);
2813+
}
2814+
}
27782815
}
27792816

27802817
return discriminator;

modules/swagger-core/src/test/java/io/swagger/v3/core/converting/CompositionTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import io.swagger.v3.core.oas.models.composition.AnimalWithSchemaSubtypes;
99
import io.swagger.v3.core.oas.models.composition.Human;
1010
import io.swagger.v3.core.oas.models.composition.ModelWithFieldWithSubTypes;
11+
import io.swagger.v3.core.oas.models.composition.ModelWithDiscriminatorMapping;
1112
import io.swagger.v3.core.util.Json;
1213
import io.swagger.v3.core.util.ResourceUtils;
1314
import io.swagger.v3.oas.models.media.Schema;
@@ -48,6 +49,11 @@ public void createModelWithFieldWithSubTypes() throws IOException {
4849
compareAsJson(ModelWithFieldWithSubTypes.class, "ModelWithFieldWithSubTypes.json");
4950
}
5051

52+
@Test(description = "create a ModelWithDiscriminatorMapping")
53+
public void createModelWithDiscriminatorMapping() throws IOException {
54+
compareAsJson(ModelWithDiscriminatorMapping.class, "ModelWithDiscriminatorMapping.json");
55+
}
56+
5157
private void compareAsJson(Class<?> cls, String fileName) throws IOException {
5258
final Map<String, Schema> schemas = ModelConverters.getInstance().readAll(cls);
5359
Json.prettyPrint(schemas);
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package io.swagger.v3.core.oas.models.composition;
2+
3+
import com.fasterxml.jackson.annotation.JsonSubTypes;
4+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
5+
import com.fasterxml.jackson.annotation.JsonTypeName;
6+
7+
@JsonTypeInfo(use= JsonTypeInfo.Id.SIMPLE_NAME, include = JsonTypeInfo.As.PROPERTY, property="kind")
8+
@JsonSubTypes({
9+
@JsonSubTypes.Type(value = ModelWithDiscriminatorMapping.First.class),
10+
@JsonSubTypes.Type(value = ModelWithDiscriminatorMapping.Second.class, name = "SecondType"),
11+
@JsonSubTypes.Type(value = ModelWithDiscriminatorMapping.Third.class)
12+
})
13+
public class ModelWithDiscriminatorMapping {
14+
15+
public static class First extends ModelWithDiscriminatorMapping {
16+
private String name;
17+
18+
String getName() { return name; }
19+
20+
void setName(String name) { this.name = name; }
21+
}
22+
23+
public static class Second extends ModelWithDiscriminatorMapping {
24+
private String value;
25+
26+
String getValue() { return value; }
27+
28+
void setValue(String value) { this.value = value; }
29+
}
30+
31+
@JsonTypeName("ThirdType")
32+
public static class Third extends ModelWithDiscriminatorMapping {
33+
private String value;
34+
35+
String getValue() { return value; }
36+
37+
void setValue(String value) { this.value = value; }
38+
}
39+
}

modules/swagger-core/src/test/resources/Animal.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@
1010
}
1111
},
1212
"discriminator" : {
13-
"propertyName" : "type"
13+
"propertyName" : "type",
14+
"mapping" : {
15+
"human" : "#/components/schemas/Human",
16+
"pet" : "#/components/schemas/Pet"
17+
}
1418
}
1519
},
1620
"Human": {

modules/swagger-core/src/test/resources/AnimalClass.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@
1010
}
1111
},
1212
"discriminator" : {
13-
"propertyName" : "type"
13+
"propertyName" : "type",
14+
"mapping" : {
15+
"human" : "#/components/schemas/HumanClass",
16+
"pet" : "#/components/schemas/PetClass"
17+
}
1418
}
1519
},
1620
"HumanClass": {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
{
2+
"First" : {
3+
"type" : "object",
4+
"allOf" : [ {
5+
"$ref" : "#/components/schemas/ModelWithDiscriminatorMapping"
6+
}, {
7+
"type" : "object",
8+
"properties" : {
9+
"name" : {
10+
"type" : "string",
11+
"writeOnly" : true
12+
}
13+
}
14+
} ]
15+
},
16+
"ModelWithDiscriminatorMapping" : {
17+
"required" : [ "kind" ],
18+
"type" : "object",
19+
"properties" : {
20+
"kind" : {
21+
"type" : "string"
22+
}
23+
},
24+
"discriminator" : {
25+
"propertyName" : "kind",
26+
"mapping" : {
27+
"SecondType" : "#/components/schemas/Second",
28+
"ThirdType" : "#/components/schemas/Third"
29+
}
30+
}
31+
},
32+
"Second" : {
33+
"type": "object",
34+
"allOf": [
35+
{
36+
"$ref": "#/components/schemas/ModelWithDiscriminatorMapping"
37+
},
38+
{
39+
"type": "object",
40+
"properties": {
41+
"value": {
42+
"type": "string",
43+
"writeOnly": true
44+
}
45+
}
46+
}
47+
]
48+
},
49+
"Third" : {
50+
"type" : "object",
51+
"allOf" : [ {
52+
"$ref" : "#/components/schemas/ModelWithDiscriminatorMapping"
53+
}, {
54+
"type" : "object",
55+
"properties" : {
56+
"value" : {
57+
"type" : "string",
58+
"writeOnly" : true
59+
}
60+
}
61+
} ]
62+
}
63+
}

0 commit comments

Comments
 (0)