Open ivan-zaitsev opened 2 years ago
It is also possible to implement similar logic on client side, but it uses reflection and is not so good as native implementation.
registerSubtypes(Subtype.class, ...);
private void registerSubtypes(Class<?>... classes) {
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new ExtendedJacksonAnnotationIntrospector(mapper));
mapper.registerSubtypes(Subtype.class);
new ModelConverterRegistrar().register(new ModelResolver(mapper));
}
import java.util.List;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotatedClass;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
import com.fasterxml.jackson.databind.jsontype.NamedType;
public class ExtendedJacksonAnnotationIntrospector extends JacksonAnnotationIntrospector {
private final ObjectMapper mapper;
public ExtendedJacksonAnnotationIntrospector(ObjectMapper mapper) {
this.mapper = mapper;
}
@Override
public List<NamedType> findSubtypes(Annotated baseType) {
DeserializationConfig config = mapper.getDeserializationConfig().with(new JacksonAnnotationIntrospector());
return (List<NamedType>) mapper.getSubtypeResolver().collectAndResolveSubtypesByClass(config, (AnnotatedClass) baseType);
}
}
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import io.swagger.v3.core.converter.ModelConverter;
import io.swagger.v3.core.converter.ModelConverters;
import io.swagger.v3.core.jackson.ModelResolver;
public class ModelConverterRegistrar {
private static final ModelConverters MODEL_CONVERTERS_INSTANCE = ModelConverters.getInstance();
public void register(ModelResolver modelResolver) {
findRegisteredConverterSameAs(modelResolver).ifPresent(MODEL_CONVERTERS_INSTANCE::removeConverter);
MODEL_CONVERTERS_INSTANCE.addConverter(modelResolver);
}
private Optional<ModelConverter> findRegisteredConverterSameAs(ModelConverter modelConverter) {
return findRegisteredConverters().stream()
.filter(registeredModelConverter -> modelConverter.getClass().equals(registeredModelConverter.getClass()))
.findFirst();
}
@SuppressWarnings("unchecked")
private List<ModelConverter> findRegisteredConverters() {
try {
Field field = MODEL_CONVERTERS_INSTANCE.getClass().getDeclaredField("converters");
field.setAccessible(true);
return (List<ModelConverter>) field.get(MODEL_CONVERTERS_INSTANCE);
} catch (Exception e) {
return Collections.emptyList();
}
}
}
If you are using Spring you can just create bean of ModelResolver and ModelConverterRegistrar will be used by Spring.
Hi,
I have the same problem, and tried to use the ExtendedJacksonAnnotationIntrospector as suggested above. For your information, this workaround did not work in my context. I try to produce an openapi.json with the following model :
// no JsonType annotation, no need to be in the openapi.json
public interface RootInterface {}
Referenced in a REST resource, to include in openapi.json :
@JsonTypeName("IntermediateInterface ")
public interface IntermediateInterface extends RootInterface {}
In a seperate module, to include in openapi.json as well :
@JsonTypeName("ImplementationClass")
public class ImplementationClass implements IntermediateInterface {...}
While IntermediateInterface is being resolved, we can see in ModelResolver#resolveSubtypes that the subtype ImplementationClass is found at the begining. But it is removed by the method ModelResolver#removeSuperClassAndInterfaceSubTypes, with the following explaination :
/**
* As the introspector will find @JsonSubTypes for a child class that are present on its super classes, the
* code segment below will also run the introspector on the parent class, and then remove any sub-types that are
* found for the parent from the sub-types found for the child. The same logic all applies to implemented
* interfaces, and is accounted for below.
*/
I am not sure to understand well this explaination, but it seems that ModelResolver#resolveSubtypes expects some @JsonSubTypes annotations that the solution suggested in the previous message does not provide.
Sometimes it is not possible to use annotatios (for example subtypes should be in different modules).
By default
ModelConverters
usesnew ModelResolver(Json.mapper())
.ModelResolver
usesmapper.getSerializationConfig().getAnnotationIntrospector().findSubtypes(parentClass)
.Jackson implementation of
mapper.getSerializationConfig().getAnnotationIntrospector().findSubtypes()
returns subtypes relying on@JsonSubTypes
annotation of parent class, but not include subtypes added manually usingmapper.registerSubtypes()
.To include subtypes added manually - class
ModelResolver
might be changed to useinstead of
Also to achieve registering subtypes using
mapper.registerSubtypes()
configuring ofObjectMapper
forModelResolver
should be implemented.