英文:
Spring-boot and polymorphism in Jackson properties
问题
I struggle using Spring-boot for creating a REST client for the Synology FileStation API.
Indeed, the API uses the same attribute to store different kind of objects.
The attribute used is data and it can store either the sid or the content of a search result like file shares.
> For search result :
{
"data": {
"offset": 0,
"shares": [
{
"isdir": true,
"name": "config",
"path": "/config"
},
...
],
"total": 19
},
"success": true
}
> For login result :
{
"data": {
"sid": "blablablabla",
},
"success": true
}
> For bad login error :
{
"error": {
"code": 400,
},
"success": false
}
I tried to design model for each type of response, but I cannot find a proper way to use Jackson to deserialize it.
To make it simple, the class that is used for receiving the response is Reponse, in the response there is a success field, and two other fields: Data and Error.
Depending on the response, the Data to be instanciated is either a LoginResponse or ListShares object.
Here is the code of my classes :
@JsonIgnoreProperties(ignoreUnknown = true)
public class Response {
@JsonProperty("success")
private boolean success;
@JsonProperty(value = "data")
private Data data;
@JsonProperty(value = "error")
private Error error;
...
}
@JsonIgnoreProperties(ignoreUnknown = true)
public class Error {
private int code;
private Collection<ErrorInfo> errors;
public Error( int code, Collection<ErrorInfo> errors) {
super();
this.code = code;
this.errors = errors;
}
...
}
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.WRAPPER_OBJECT)
@JsonSubTypes({
@JsonSubTypes.Type(value = LoginResponse.class),
@JsonSubTypes.Type(value = ListShares.class),
})
public abstract class Data {
}
@JsonTypeName("LoginResponse")
public class LoginResponse extends Data {
@JsonProperty("sid")
private String sid;
public LoginResponse() {
super();
}
...
}
@JsonTypeName("ListShares")
public class ListShares extends Data {
@JsonProperty("offset")
private int offset;
@JsonProperty("total")
private int total;
@JsonProperty("shares")
private Set<Share> shares;
public ListShares() {
super();
}
public ListShares(int offset, int total, Set<Share> shares) {
super();
this.offset = offset;
this.total = total;
this.shares = shares;
}
}
I get this exception :
Caused by: com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class com.heavyrage.syno.apis.genericresponses.Response]: missing type id property 'data'
at [Source: (PushbackInputStream); line: 1, column: 38]
at com.fasterxml.jackson.databind.exc.InvalidTypeIdException.from(InvalidTypeIdException.java:43) ~[jackson-databind-2.11.1.jar:2.11.1]
at com.fasterxml.jackson.databind.DeserializationContext.missingTypeIdException(DeserializationContext.java:1794) ~[jackson-databind-2.11.1.jar:2.11.1]
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingTypeId(DeserializationContext.java:1323) ~[jackson-databind-2.11.1.jar:2.11.1]
at com.fasterxml.jackson.databind.jsontype.impl.TypeDeserializerBase._handleMissingTypeId(TypeDeserializerBase.java:303) ~[jackson-databind-2.11.1.jar:2.11.1]
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedUsingDefaultImpl(AsPropertyTypeDeserializer.java:166) ~[jackson-databind-2.11.1.jar:2.11.1]
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:107) ~[jackson-databind-2.11.1.jar:2.11.1]
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeWithType(BeanDeserializerBase.java:1209) ~[jackson-databind-2.11.1.jar:2.11.1]
at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:68) ~[jackson-databind-2.11.1.jar:2.11.1]
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4482) ~[jackson-databind-2.11.1.jar:2.11.1]
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3487) ~[jackson-databind-2.11.1.jar:2.11.1]
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:273) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
... 17 common frames omitted
Does someone know if it is possible to instanciate the Data object correctly (instanciate LoginResponse or ListShare) to populate the "data" property of the Response object ?
英文:
I struggle using Spring-boot for creating a REST client for the Synology FileStation API.
Indeed, the API uses the same attribute to store different kind of objects.
The attribute used is data and it can store either the sid or the content of a search result like file shares.
> For search result :
{
"data": {
"offset": 0,
"shares": [
{
"isdir": true,
"name": "config",
"path": "/config"
},
...
],
"total": 19
},
"success": true
}
> For login result :
{
"data": {
"sid": "blablablabla",
},
"success": true
}
> For bad login error :
{
"error": {
"code": 400,
},
"success": false
}
I tried to design model for each type of response, but I cannot find a proper way to use Jackson to deserialize it.
To make it simple, the class that is used for receiving the response is Reponse, in the response there is a success field, and two other fields: Data and Error.
Depending on the response, the Data to be instanciated is either a LoginResponse or ListShares object.
Here is the code of my classes :
@JsonIgnoreProperties(ignoreUnknown = true)
public class Response {
@JsonProperty("success")
private boolean success;
@JsonProperty(value = "data")
private Data data;
@JsonProperty(value = "error")
private Error error;
...
}
@JsonIgnoreProperties(ignoreUnknown = true)
public class Error {
private int code;
private Collection<ErrorInfo> errors;
public Error( int code, Collection<ErrorInfo> errors) {
super();
this.code = code;
this.errors = errors;
}
...
}
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.WRAPPER_OBJECT)
@JsonSubTypes({
@JsonSubTypes.Type(value = LoginResponse.class),
@JsonSubTypes.Type(value = ListShares.class),
})
public abstract class Data {
}
@JsonTypeName("LoginResponse")
public class LoginResponse extends Data {
@JsonProperty("sid")
private String sid;
public LoginResponse() {
super();
}
...
}
@JsonTypeName("ListShares")
public class ListShares extends Data {
@JsonProperty("offset")
private int offset;
@JsonProperty("total")
private int total;
@JsonProperty("shares")
private Set<Share> shares;
public ListShares() {
super();
}
public ListShares(int offset, int total, Set<Share> shares) {
super();
this.offset = offset;
this.total = total;
this.shares = shares;
}
I get this exception :
Caused by: com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class com.heavyrage.syno.apis.genericresponses.Response]: missing type id property 'data'
at [Source: (PushbackInputStream); line: 1, column: 38]
at com.fasterxml.jackson.databind.exc.InvalidTypeIdException.from(InvalidTypeIdException.java:43) ~[jackson-databind-2.11.1.jar:2.11.1]
at com.fasterxml.jackson.databind.DeserializationContext.missingTypeIdException(DeserializationContext.java:1794) ~[jackson-databind-2.11.1.jar:2.11.1]
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingTypeId(DeserializationContext.java:1323) ~[jackson-databind-2.11.1.jar:2.11.1]
at com.fasterxml.jackson.databind.jsontype.impl.TypeDeserializerBase._handleMissingTypeId(TypeDeserializerBase.java:303) ~[jackson-databind-2.11.1.jar:2.11.1]
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedUsingDefaultImpl(AsPropertyTypeDeserializer.java:166) ~[jackson-databind-2.11.1.jar:2.11.1]
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:107) ~[jackson-databind-2.11.1.jar:2.11.1]
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeWithType(BeanDeserializerBase.java:1209) ~[jackson-databind-2.11.1.jar:2.11.1]
at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:68) ~[jackson-databind-2.11.1.jar:2.11.1]
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4482) ~[jackson-databind-2.11.1.jar:2.11.1]
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3487) ~[jackson-databind-2.11.1.jar:2.11.1]
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:273) ~[spring-web-5.2.8.RELEASE.jar:5.2.8.RELEASE]
... 17 common frames omitted
Does someone know if it is possible to instanciate the Data object correctly (instanciate LoginResponse or ListShare) to populate the "data" property of the Response object ?
答案1
得分: 0
我终于在Jackson问题跟踪器中找到了这个答案。
关键是在实例化Data对象时调用的反序列化程序,因此正确的实现类被返回,Jackson可以实例化它。
https://stackoverflow.com/a/50013090/1030527
无论如何,对于Response类使用组合是一个好主意,因为它反映了它与Data和Error类的HasA关系。
对于Data类本身,使用Java接口进行组合导致了另一个由Jackson尝试实例化接口类引起的异常。
英文:
I finally found this answer from the Jackson issue tracker.
The key is the deserializer to be called when instanciating the Data object, thus the correct implementation class is returned and Jackson can instanciate it.
https://stackoverflow.com/a/50013090/1030527
Anyway, it was a good idea to use composition for the the Response class because it reflects its HasA relationship with both Data and Error classes.
For the Data class itself, using composition with Java interface led to another exception caused by Jackson trying to instanciate the interface class.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论