Spring Boot - OpenAPI - Tell API to use certain subclass for example - java

I have an class hierarchy as below
public class Car {
private String make;
private String model;
#Schema(example = "MANUAL")
private TransmissionType transmissionType;
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "transmissionType")
private Transmission transmission;
}
#JsonSubTypes({
#JsonSubTypes.Type(value = AutomaticTransmission.class, name = "AUTOMATIC"),
#JsonSubTypes.Type(value = ManualTransmission.class, name = "MANUAL"))
})
public abstract class Transmission {
}
public class AutomaticTransmission {
#Schema(example = "DCT")
public Technology technology;
}
public class ManualTransmission {
#Schema(example = "5")
public int numGears;
}
Now, when swagger is generated, I see that for car model,
{
"transmissionType": "MANUAL"
"transmission": {
"technology": "DCT"
}
}
Here transmission type is manual but example given of automatic. Requirement is give example of manual transmission. How do I link these two properties.
I know that I can create an example json and put it in #Schema(example = "{\"numGears\": 5}") but this will create maintenance overhead of modifying json when class is modified.

Related

Unable to map json response to Object because of multiple implementations

I have this class structure:
ResponseBody.java:
public class ResponseBody {
private String type;
private A a;
public static class A {
private List<B> b;
}
}
B.java:
public class B {
private String c;
private DynamicJson dynamicJson;
}
DynamicJson.java:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = Child1.class, name = "child-1"),
#JsonSubTypes.Type(value = Child2.class, name = "child-2"),
#JsonSubTypes.Type(value = Child3.class, name = "child-3")
})
public abstract class DynamicJson {
private String onlyKnownField;
}
Child1.java
#JsonTypeName("child-1")
public class Child1 extends DynamicJson {
// Number of fields and field names here change based on which implementation of Dynamic Json
}
Likewise, Child2 and Child3 classes.
Removed constructors, setters, getters.
I want to cast/deserialize below json to ResponseBody.java class.
{
"type": "child-1",
"a": {
"b": [
{
"c": "guid",
"dynamicJson": {
"onlyKnownField": "guid", // only field that always comes
/*
dynamic number of fields based on "type", say 2 fields, if type is
something else then number of fields here is different
*/
}
}
]
}
}
The field "dynamicJson" can have different number of fields with different names inside it based on "type" value. Which seems to be the problem,
This is the code being used:
ObjectMapper objectMapper = new ObjectMapper();
ResponseBody responseBody = objectMapper.readValue(json.getBody(), ResponseBody.class);
However, above code give this error:
Missing type id when trying to resolve subtype of [simple type, class DynamicJson]
What am I doing wrong here?
Let me know if other info is needed.

Deserializing JSON to polymorphic object model using #JsonTypeInfo & #JsonSubTypes not working?

I am trying to have a REST endpoint create a subtype of Widget when POSTing to it,
here is the base class for all Widgets
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "widgetType")
#JsonSubTypes({
#JsonSubTypes.Type(value = TextWidget.class, name = WidgetType.Constants.TEXT),
#JsonSubTypes.Type(value = ImageWidget.class, name = WidgetType.Constants.IMAGE),
#JsonSubTypes.Type(value = IndicatorWidget.class, name = WidgetType.Constants.INDICATOR),
#JsonSubTypes.Type(value = MapWidget.class, name = WidgetType.Constants.MAP),
#JsonSubTypes.Type(value = ChartWidget.class, name = WidgetType.Constants.CHART)
})
#Data
#Slf4j
public abstract class Widget {
...
}
this is the WidgetType enum:
public enum WidgetType {
TEXT(Constants.TEXT),
IMAGE(Constants.IMAGE),
INDICATOR(Constants.INDICATOR),
MAP(Constants.MAP),
CHART(Constants.CHART);
private final String type;
WidgetType(final String type) {
this.type = type;
}
public static class Constants {
public static final String TEXT = "TEXT";
public static final String IMAGE = "IMAGE";
public static final String INDICATOR = "INDICATOR";
public static final String MAP = "MAP";
public static final String CHART = "CHART";
}
}
and this is my Spring endpoint:
#RequestMapping(method = RequestMethod.POST)
public Optional<Widget> createWidget(#Valid final Widget widget) {
...
}
when hitting that endpoint it throws this exception:
{
"timestamp": 1493029336774,
"status": 500,
"error": "Internal Server Error",
"exception": "org.springframework.beans.BeanInstantiationException",
"message": "Failed to instantiate [....models.Widget]: Is it an abstract class?; nested exception is java.lang.InstantiationException",
"path": "...."
}
skimming through few solutions for my problem, I might have to manually register the subtypes, I might be wrong, but I think there must be a way to make it work with annotations, maybe I am missing something?
problem solved,
I was annotating my classes with Jackson annotation and forgot that I was sending multipart POST requests, that wasn't even going to Jackson.
The solution is as simple as this:
#RequestMapping(method = RequestMethod.POST)
public Optional<Widget> createWidget(#RequestBody final Widget widget) {
...
}

How to deserialize a json array containing different types of objects

Here is a json snippet which contains an array(icons) which can contain two different types of objects(application and folder)
{
"icons": [
{
"application": {
"displayName": "Facebook",
"bundleId": "com.facebook.com"
}
},
{
"folder": {
"some": "value",
"num": 3
}
}
]
}
How can I create java POJO's modelling this kind of json and then deserialize the same?
I referred to this question. But I can't change the json I'm getting to include a 'type' as advised there and then use inheritance for the POJO's of the two different objects.
No custom deserializers are required. A smart #JsonTypeInfo will do the trick.
See below what the classes and interfaces can be like:
#JsonTypeInfo(use = Id.NAME, include = As.WRAPPER_OBJECT)
#JsonSubTypes({ #Type(value = ApplicationIcon.class, name = "application"),
#Type(value = FolderIcon.class, name = "folder") })
public interface Icon {
}
#JsonRootName("application")
public class ApplicationIcon implements Icon {
public String displayName;
public String bundleId;
// Getters and setters ommited
}
#JsonRootName("folder")
public class FolderIcon implements Icon {
public String some;
public Integer num;
// Getters and setters ommited
}
public class IconWrapper {
private List<Icon> icons;
// Getters and setters ommited
}
To deserialize your JSON, do as following:
String json = "{\"icons\":[{\"application\":{\"displayName\":\"Facebook\",\"bundleId\":\"com.facebook.com\"}},{\"folder\":{\"some\":\"value\",\"num\":3}}]}";
ObjectMapper mapper = new ObjectMapper();
IconWrapper iconWrapper = mapper.readValue(json, IconWrapper.class);

Jackson XML - deserializing empty classes and polymorphism

I have the following interface:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
#JsonSubTypes({
#JsonSubTypes.Type(value = EmptyProxy.class, name = "empty"),
... other types not included ...
})
public interface Proxy {
}
I have the following implementation:
#JsonTypeName("empty")
public static class EmptyProxy implements Proxy {
}
As you can see, it is just an empty class. I left the other (working) implementations out of this example.
I have the following container data class:
public static class Data {
#JacksonXmlProperty(localName = "name")
private String name;
#JacksonXmlProperty(localName = "proxy")
private Proxy proxy;
}
Deserializing EmptyProxy does not seem to work. For example:
final ObjectMapper mapper = new XmlMapper().registerModule(new JacksonXmlModule());
final Data data = mapper.readValue("<data><name>my-name</name><proxy><empty/></proxy></data>", Data.class);
This gives the following exeption:
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of Test$EmptyProxy out of VALUE_NULL token
at [Source: java.io.StringReader#59ec2012; line: 1, column: 42] (through reference chain: Data["proxy"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:857)
Is this a bug in Jackson? FWIW, when I add a dummy field to EmptyProxy, it works.
update
I tried with JAXB only, and get the same result. Code:
public static class Data {
#XmlElement(name = "name")
private String name;
#XmlElements({
#XmlElement(type = EmptyProxy.class, name = "empty")
})
private Proxy proxy;
}
public interface Proxy {
}
#XmlType(name = "empty")
public static class EmptyProxy implements Proxy {
}
public static void main(String[] a) throws IOException {
final ObjectMapper mapper = new XmlMapper()/*.registerModule(new JacksonXmlModule())*/.registerModule(new JaxbAnnotationModule());
final Data data = mapper.readValue("<data><name>my-name</name><proxy><empty></empty></proxy></data>", Data.class);
}
I have created a bug entry for this. See http://github.com/FasterXML/jackson-dataformat-xml/issues/169.

Spring Json deserialization to Java doesn't work

I currently facing an issue with the deserialization of json into an polymorphic type.
This is my controller which receives a RecommendedVirtualEditionParam.
#RequestMapping(
value = "/sortedEdition",
method = RequestMethod.POST,
headers = { "Content-type=application/json;charset=UTF-8"
})
public String getSortedRecommendedVirtualEdition(
Model model,
#RequestBody RecommendVirtualEditionParam params) {
//Do Stuff
}
RecommendedVirtualEditionParam is a container:
public class RecommendVirtualEditionParam {
private final String acronym;
private final String id;
private final Collection<Property> properties;
public RecommendVirtualEditionParam(
#JsonProperty("acronym") String acronym,
#JsonProperty("id") String id,
#JsonProperty("properties") Collection<Property> properties) {
this.acronym = acronym;
this.id = id;
this.properties = properties;
}
//Getters
}
Property is a polymorphic type and I believe it's the one giving my problems.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = SpecificTaxonomyProperty.class, name = "specific-taxonomy")
})
public abstract class Property {
public Property() {
}
//Other methods
}
The sub type:
public class SpecificTaxonomyProperty extends Property {
private final String acronym;
private final String taxonomy;
public SpecificTaxonomyProperty(
#JsonProperty("acronym") String acronym,
#JsonProperty("taxonomy") String taxonomy) {
this.acronym = acronym;
this.taxonomy = taxonomy;
}
The json being send on requests:
{
acronym: "afs"
id: "167503724747"
properties: [
{
type: "specific-taxonomy",
acronym: "afs",
taxonomy: "afs"
}
]
}
When I run it like this I get a org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json;charset=UTF-8' not supported
org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json;charset=UTF-8' not supported
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:149) ~[spring-webmvc-3.2.2.RELEASE.jar:3.2.2.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:180) ~[spring-webmvc-3.2.2.RELEASE.jar:3.2.2.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:95) ~[spring-webmvc-3.2.2.RELEASE.jar:3.2.2.RELEASE]
I believe there is something wrong with with the way I setup my Property class that makes it unable to deserialize. Any has a clue and give me an hand?
I managed to fix my problem.
Before posting, I had looked around and a lot of answers pointed to the class I wanted to deserialize having multiple setters for the same attribute. I didn't pay much attention to it, since my attributes were final, I didn't have any setters for them.
https://stackoverflow.com/a/19444874/2364671
But I had a method called setUserWeight(RecommendedWeights weights) which didn't set any attributes of Property, but after renaming it, my problem was fixed.
I don't quiet understand the reason for this odd behavior and would love some light about it.

Categories