英文:
How to auto detect concrete class based on the JSON provided using Spring/Jackson
问题
我有两个实现了Value接口的类,RangeValue和FileValue。
RangeValue的代码如下:
public class RangeValue implements Value {
private int min;
private int max;
public RangeValue(int min, int max) {
this.min = min;
this.max = max;
}
public int getMin() {
return min;
}
public int getMax() {
return max;
}
}
FileValue的代码如下:
public class FileValue implements Value {
private String contentType;
private String value;
public FileValue(String contentType, String value) {
this.contentType = contentType;
this.value = value;
}
public String getContentType() {
return contentType;
}
public String getValue() {
return value;
}
}
RangeValue的JSON如下:
{
"min": 200,
"max": 300
}
FileValue的JSON如下:
{
"contentType": "application/octet-stream",
"value": "fileValue"
}
现在,我希望这些JSON的RequestType参数仅为Value类型,但我不能更改JSON文件,即JSON将保持不变,用户应使用上述请求体中的相同JSON。
我通过使用@JsonTypeInfo和@JsonSubTypes解决了这个问题,通过向上述JSON添加额外属性(即类型),但规范不允许我添加该属性。
如何在不更改JSON的情况下,根据上述JSON来实例化适当的具体类?
英文:
I have 2 implementations of Value interface, RangeValue, FileValue.
RangeValue looks like below:
public class RangeValue implements Value {
private int min;
private int max;
public RangeValue(int min, int max) {
this.min = min;
this.max = max;
}
public int getMin() {
return min;
}
public int getMax() {
return max;
}
}
FileValue looks like below:
public class FileValue implements Value {
private String contentType;
private String value;
public FileValue(String contentType, String value) {
this.contentType = contentType;
this.value = value;
}
public String getContentType() {
return contentType;
}
public String getValue() {
return value;
}
}
the json for RangeValue looks like :
{
"min": 200,
"max": 300
}
The json for FileValue looks:
{
"contentType": "application/octet-stream",
"value": "fileValue"
}
Now I want the RequestType parameter for these json to be of type Value only, I can't change the JSON files i.e. the json would look like the same and user should use the same JSON in request body as stated above.
I solved this by using @JsonTypeInfo & @JsonSubTypes by adding extra attributes to the above JSON i.e. type but the spec doesn't allow me to add that.
How can the appropriate concrete class could be instantiated based on the JSON above without altering?
答案1
得分: 1
选项 1: 自定义反序列化器。算法可以如下:
- 解析为 JsonNode。
- 使用节点中的属性找到正确的类进行反序列化。
- 将节点转换为实际类的实例。
简化示例:
public class ValueDeserializer extends StdDeserializer<Value> {
public ValueDeserializer() {
super(Value.class);
}
@Override
public Value deserialize(JsonParser parser, DeserializationContext context) throws IOException {
JsonNode root = parser.readValueAsTree();
if (root instanceof ObjectNode objectNode) {
JsonNode valueNode = objectNode.get("somePropertyName");
Class<? extends Value> clazz = valueNode == null ? RangeValue.class : FileValue.class;
return context.readTreeAsValue(objectNode, clazz);
}
throw new JsonParseException(parser, "not an object");
//处理当 JSON 是 JSON 数组
//或其他无法反序列化为对象的情况
}
}
使用 JsonDeserialize 在接口上注册反序列化器:
@JsonDeserialize(using = ValueDeserializer.class)
在 RangeValue
和 FileValue
上放置相同的注解,不指定反序列化器,否则会出现 StackOverflowError
。
选项 2: 使用 JsonTypeInfo.Id.DEDUCTION
@JsonSubTypes({
@JsonSubTypes.Type(FileValue.class),
@JsonSubTypes.Type(RangeValue.class)
})
@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
public interface Value {
}
Jackson 将使用属性名称推断出正确的类。请注意,如果推断失败,将抛出异常。
> 表示未使用序列化的类型属性。类型根据可用字段推断。推断仅限于字段的名称(不是它们的值或因此任何嵌套后代)。如果没有足够的唯一信息以选择单个子类型,将抛出异常。如果正在使用推断,注解属性 visible、property 和 include 将被忽略。
英文:
Option 1: custom deserializer. Algorithm can be as follows:
- Parse to JsonNode.
- Use the properties in the node to find the correct class to deserialize into.
- Convert the node to instance of the actual class.
Simplified example:
public class ValueDeserializer extends StdDeserializer<Value> {
public ValueDeserializer() {
super(Value.class);
}
@Override
public Value deserialize(JsonParser parser, DeserializationContext context) throws IOException {
JsonNode root = parser.readValueAsTree();
if (root instanceof ObjectNode objectNode) {
JsonNode valueNode = objectNode.get("somePropertyName");
Class<? extends Value> clazz = valueNode == null ? RangeValue.class : FileValue.class;
return context.readTreeAsValue(objectNode, clazz);
}
throw new JsonParseException(parser, "not an object");
//handling the case, when json is json array
//or something else which can't be deserialized into object
}
}
Register the deserializer with JsonDeserialize on the interface:
@JsonDeserialize(using = ValueDeserializer.class)
Put the same annotation on RangeValue
and FileValue
, without specifying a deserializer, otherwise you will get StackOverflowError
.
Option 2: use JsonTypeInfo.Id.DEDUCTION
@JsonSubTypes({
@JsonSubTypes.Type(FileValue.class),
@JsonSubTypes.Type(RangeValue.class)
})
@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
public interface Value {
}
Jackson will deduce the correct class using the property names. Keep in mind exception will be thrown if it fails deduction.
> Means that no serialized typing-property is used. Types are deduced based on the fields available. Deduction is limited to the names of fields (not their values or, consequently, any nested descendants). Exceptions will be thrown if not enough unique information is present to select a single subtype.
If deduction is being used annotation properties visible, property and include are ignored.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论