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 @@ -16,8 +16,10 @@
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
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;
Expand All @@ -28,6 +30,8 @@
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 Down Expand Up @@ -967,7 +971,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
model.setDiscriminator(null);
}

Discriminator discriminator = resolveDiscriminator(type, context);
Discriminator discriminator = resolveDiscriminator(model, beanDesc, context, annotatedType.getJsonViewAnnotation());
if (discriminator != null) {
model.setDiscriminator(discriminator);
}
Expand Down Expand Up @@ -2668,22 +2672,63 @@ protected Map<String, Object> resolveExtensions(Annotated a, Annotation[] annota
return null;
}

protected TypeSerializer getTypeSerializer(JavaType type) {
SerializationConfig config = _mapper.getSerializationConfig();

try {
return _mapper.getSerializerFactory().createTypeSerializer(
config, type
);
} catch (JsonMappingException e) {
LOGGER.error("Unable to create type serializer", e);
}

return null;
}

protected TypeSerializer getTypeSerializerForDiscriminatorInference(JavaType type) {
// longer method would involve AnnotationIntrospector.findTypeResolver(...) but:
JsonTypeInfo typeInfo = type.getRawClass().getDeclaredAnnotation(JsonTypeInfo.class);
if (typeInfo == null || typeInfo.use() == JsonTypeInfo.Id.NONE) {
return null;
}

TypeSerializer serializer = getTypeSerializer(type);
if (serializer == null) {
return null;
}

JsonTypeInfo.As include = serializer.getTypeInclusion();
if (
!(
include == JsonTypeInfo.As.PROPERTY
|| include == JsonTypeInfo.As.EXISTING_PROPERTY
|| include == JsonTypeInfo.As.WRAPPER_OBJECT
)
) {
return null;
}

return serializer;
}

protected void resolveDiscriminatorProperty(JavaType type, ModelConverterContext context, Schema model) {
// add JsonTypeInfo.property if not member of bean
JsonTypeInfo typeInfo = type.getRawClass().getDeclaredAnnotation(JsonTypeInfo.class);
if (typeInfo != null) {
String typeInfoProp = typeInfo.property();
if (StringUtils.isNotBlank(typeInfoProp)) {
Schema modelToUpdate = model;
if (StringUtils.isNotBlank(model.get$ref())) {
modelToUpdate = context.getDefinedModels().get(model.get$ref().substring(SCHEMA_COMPONENT_PREFIX));
}
if (modelToUpdate.getProperties() == null || !modelToUpdate.getProperties().keySet().contains(typeInfoProp)) {
Schema discriminatorSchema = openapi31 ? new JsonSchema().typesItem("string").name(typeInfoProp) : new StringSchema().name(typeInfoProp);
modelToUpdate.addProperties(typeInfoProp, discriminatorSchema);
if (modelToUpdate.getRequired() == null || !modelToUpdate.getRequired().contains(typeInfoProp)) {
modelToUpdate.addRequiredItem(typeInfoProp);
}
TypeSerializer serializer = getTypeSerializerForDiscriminatorInference(type);
if (serializer == null) {
return;
}
String propertyName = serializer.getPropertyName();
if (StringUtils.isNotBlank(propertyName)) {
Schema modelToUpdate = model;
if (StringUtils.isNotBlank(model.get$ref())) {
modelToUpdate = context.getDefinedModels().get(model.get$ref().substring(SCHEMA_COMPONENT_PREFIX));
}
if (modelToUpdate.getProperties() == null || !modelToUpdate.getProperties().keySet().contains(propertyName)) {
Schema discriminatorSchema = openapi31 ? new JsonSchema().typesItem("string").name(propertyName) : new StringSchema().name(propertyName);
modelToUpdate.addProperties(propertyName, discriminatorSchema);
if (modelToUpdate.getRequired() == null || !modelToUpdate.getRequired().contains(propertyName)) {
modelToUpdate.addRequiredItem(propertyName);
}
}
}
Expand Down Expand Up @@ -2722,23 +2767,23 @@ protected Schema resolveWrapping(JavaType type, ModelConverterContext context, S
return model;
}

protected Discriminator resolveDiscriminator(JavaType type, ModelConverterContext context) {
protected Discriminator resolveDiscriminator(Schema model, BeanDescription bean, ModelConverterContext context, JsonView jsonViewAnnotation) {
io.swagger.v3.oas.annotations.media.Schema declaredSchemaAnnotation = AnnotationsUtils.getSchemaDeclaredAnnotation(bean.getType().getRawClass());

io.swagger.v3.oas.annotations.media.Schema declaredSchemaAnnotation = AnnotationsUtils.getSchemaDeclaredAnnotation(type.getRawClass());
if (declaredSchemaAnnotation != null) {
String propertyName = declaredSchemaAnnotation.discriminatorProperty();

String disc = (declaredSchemaAnnotation == null) ? "" : declaredSchemaAnnotation.discriminatorProperty();

if (disc.isEmpty()) {
// longer method would involve AnnotationIntrospector.findTypeResolver(...) but:
JsonTypeInfo typeInfo = type.getRawClass().getDeclaredAnnotation(JsonTypeInfo.class);
if (typeInfo != null) {
disc = typeInfo.property();
if (StringUtils.isBlank(propertyName)) {
TypeSerializer serializer = getTypeSerializerForDiscriminatorInference(bean.getType());
if (serializer == null) {
return null;
}
propertyName = serializer.getPropertyName();
}
}
if (!disc.isEmpty()) {
Discriminator discriminator = new Discriminator()
.propertyName(disc);
if (declaredSchemaAnnotation != null) {

if (StringUtils.isNotBlank(propertyName)) {
Discriminator discriminator = new Discriminator().propertyName(propertyName);

DiscriminatorMapping[] mappings = declaredSchemaAnnotation.discriminatorMapping();
if (mappings != null && mappings.length > 0) {
for (DiscriminatorMapping mapping : mappings) {
Expand All @@ -2747,11 +2792,62 @@ protected Discriminator resolveDiscriminator(JavaType type, ModelConverterContex
}
}
}

return discriminator;
}
}

return discriminator;
TypeSerializer serializer = getTypeSerializerForDiscriminatorInference(bean.getType());
if (serializer == null) {
return null;
}
return null;
String propertyName = serializer.getPropertyName();
if (StringUtils.isBlank(propertyName)) {
return null;
}

Discriminator discriminator = new Discriminator().propertyName(propertyName);

// Use same approach to finding subtypes as resolveSubtypes, mimicking
final List<NamedType> subTypes = _intr().findSubtypes(bean.getClassInfo());
if (subTypes == null) {
return null;
}

removeSuperClassAndInterfaceSubTypes(subTypes, bean);

final Class<?> beanClass = bean.getClassInfo().getAnnotated();
if (!subTypes.isEmpty()) {
TypeIdResolver resolver = serializer.getTypeIdResolver();

for (NamedType subType : subTypes) {
final Class<?> subtypeType = subType.getType();
if (!beanClass.isAssignableFrom(subtypeType)) {
continue;
}

String subTypeName = resolver.idFromValueAndType(null, subType.getType());

final Schema subtypeModel = context.resolve(new AnnotatedType()
.type(subtypeType)
.jsonViewAnnotation(jsonViewAnnotation)
.subtype(true));

if (StringUtils.isBlank(subtypeModel.getName()) ||
subtypeModel.getName().equals(model.getName())) {
subtypeModel.setName(_typeNameResolver.nameForType(_mapper.constructType(subtypeType),
TypeNameResolver.Options.SKIP_API_MODEL));
}

// 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(subtypeModel.getName())) {
discriminator.mapping(subTypeName, RefUtils.constructRef(subtypeModel.getName()));
}
}
}

return discriminator;
}

protected XML resolveXml(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@

import io.swagger.v3.core.converter.ModelConverters;
import io.swagger.v3.core.matchers.SerializationMatchers;
import io.swagger.v3.core.oas.models.composition.AbstractBaseModelWithoutFields;
import io.swagger.v3.core.oas.models.composition.Animal;
import io.swagger.v3.core.oas.models.composition.AnimalClass;
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.*;
import io.swagger.v3.core.oas.models.composition.discriminator.ModelWithDefaultPropertyName;
import io.swagger.v3.core.oas.models.composition.discriminator.ModelWithDiscriminatorMapping;
import io.swagger.v3.core.oas.models.composition.discriminator.ModelWithProvidedDiscriminatorMapping;
import io.swagger.v3.core.oas.models.composition.discriminator.ModelWithoutTypeInfo;
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 +47,26 @@ 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");
}

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

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

@Test(description = "create a createModelWithProvidedDiscriminatorMapping")
public void createModelWithProvidedDiscriminatorMapping() throws IOException {
compareAsJson(ModelWithProvidedDiscriminatorMapping.class, "ModelWithProvidedDiscriminatorMapping.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,29 @@
package io.swagger.v3.core.oas.models.composition.discriminator;

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)
@JsonSubTypes({
@JsonSubTypes.Type(value = ModelWithDefaultPropertyName.First.class),
@JsonSubTypes.Type(value = ModelWithDefaultPropertyName.Second.class, name = "SecondType"),
})
public class ModelWithDefaultPropertyName {

public static class First extends ModelWithDefaultPropertyName {
private String name;

public String getName() { return name; }

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

public static class Second extends ModelWithDefaultPropertyName {
private String value;

public String getValue() { return value; }

public void setValue(String value) { this.value = value; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.swagger.v3.core.oas.models.composition.discriminator;

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; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package io.swagger.v3.core.oas.models.composition.discriminator;

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.swagger.v3.oas.annotations.media.DiscriminatorMapping;
import io.swagger.v3.oas.annotations.media.Schema;

@JsonTypeInfo(use= JsonTypeInfo.Id.SIMPLE_NAME, include = JsonTypeInfo.As.PROPERTY, property="kind")
@JsonSubTypes({
@JsonSubTypes.Type(value = ModelWithProvidedDiscriminatorMapping.First.class),
@JsonSubTypes.Type(value = ModelWithProvidedDiscriminatorMapping.Second.class),
})
@Schema(
discriminatorMapping = {
@DiscriminatorMapping(schema = ModelWithProvidedDiscriminatorMapping.First.class, value = "FirstOne"),
@DiscriminatorMapping(schema = ModelWithProvidedDiscriminatorMapping.Second.class, value = "SecondOne"),
}
)
public class ModelWithProvidedDiscriminatorMapping {

public static class First extends ModelWithProvidedDiscriminatorMapping {
private String name;

public String getName() { return name; }

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

public static class Second extends ModelWithProvidedDiscriminatorMapping {
private String value;

public String getValue() { return value; }

public void setValue(String value) { this.value = value; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.swagger.v3.core.oas.models.composition.discriminator;

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

@JsonTypeInfo(use= JsonTypeInfo.Id.NONE)
@JsonSubTypes({
@JsonSubTypes.Type(value = ModelWithoutTypeInfo.First.class),
@JsonSubTypes.Type(value = ModelWithoutTypeInfo.Second.class, name = "SecondType"),
})
public class ModelWithoutTypeInfo {

public static class First extends ModelWithoutTypeInfo {
private String name;

public String getName() { return name; }

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

public static class Second extends ModelWithoutTypeInfo {
private String value;

public String getValue() { return value; }

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