Spring-boot 和 Jackson 属性中的多态性

huangapple go评论72阅读模式
英文:

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.

huangapple
  • 本文由 发表于 2020年8月7日 01:29:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/63288861.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定