java 为什么 Jackson 多态序列化在列表中不起作用?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/34193177/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why does Hymanson polymorphic serialization not work in lists?
提问by monitorjbl
Hymanson is doing something truly bizarre and I cannot find any explanation for it. I'm doing polymorphic serialization and it works perfectly when an object is on its own. But if you put the same object into a list and serialize the list instead, it erases the type information.
Hyman逊正在做一些真正奇怪的事情,我找不到任何解释。我正在做多态序列化,当一个对象独立时它可以完美地工作。但是,如果您将同一个对象放入一个列表并序列化该列表,则会删除类型信息。
The fact that it's losing type info would lead one to suspect type erasure. But this is happening during serialization of the contentsof the list; all Hymanson has to do is inspect the current object it's serializing to determine its type.
它丢失类型信息的事实会导致人们怀疑类型擦除。但这发生在列表内容的序列化过程中; Hymanson 所要做的就是检查它正在序列化的当前对象以确定其类型。
I've created an example using Hymanson 2.5.1:
我使用 Hymanson 2.5.1 创建了一个示例:
import com.fasterxml.Hymanson.annotation.JsonIgnoreProperties;
import com.fasterxml.Hymanson.annotation.JsonSubTypes;
import com.fasterxml.Hymanson.annotation.JsonSubTypes.Type;
import com.fasterxml.Hymanson.annotation.JsonTypeInfo;
import com.fasterxml.Hymanson.annotation.JsonTypeName;
import com.fasterxml.Hymanson.core.JsonProcessingException;
import com.fasterxml.Hymanson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.List;
public class Test {
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
@JsonSubTypes({
@Type(value = Dog.class, name = "dog"),
@Type(value = Cat.class, name = "cat")})
public interface Animal {}
@JsonTypeName("dog")
public static class Dog implements Animal {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@JsonTypeName("cat")
public static class Cat implements Animal {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static void main(String[] args) throws JsonProcessingException {
List<Cat> list = new ArrayList<>();
list.add(new Cat());
System.out.println(new ObjectMapper().writeValueAsString(list));
System.out.println(new ObjectMapper().writeValueAsString(list.get(0)));
}
}
Here's the output:
这是输出:
[{"name":null}]
{"@type":"cat","name":null}
As you can see, Hymanson is not adding the type information when the object is in a list. Does anyone know why this is happening?
如您所见,当对象在列表中时,Hymanson 没有添加类型信息。有谁知道为什么会这样?
回答by Sotirios Delimanolis
The various reasons for why this happens are discussed hereand here. I don't necessarily agree with the reasons, but Hymanson, because of type erasure, doesn't off the batknow the type of elements the List
(or Collection
or Map
) contains. It chooses to use a simple serializer that doesn't interpret your annotations.
此处和此处讨论了发生这种情况的各种原因。我不一定与理由同意,但Hyman逊,因为类型擦除,并不蝙蝠知道元素的类型List
(或Collection
或Map
)含有。它选择使用不解释您的注释的简单序列化程序。
You have two options suggested in those links:
您在这些链接中建议了两个选项:
First, you can create a class that implements List<Cat>
, instantiate it appropriately and serialize the instance.
首先,您可以创建一个实现 的类,List<Cat>
适当地实例化它并序列化实例。
class CatList implements List<Cat> {...}
The generic type argument Cat
is not lost. Hymanson has access to it and uses it.
泛型类型参数Cat
不会丢失。Hymanson 可以访问并使用它。
Second, you can instantiate and use an ObjectWriter
for the type List<Cat>
. For example
其次,您可以实例化并使用ObjectWriter
类型List<Cat>
。例如
System.out.println(new ObjectMapper().writerFor(new TypeReference<List<Cat>>() {}).writeValueAsString(list));
will print
将打印
[{"@type":"cat","name":"heyo"}]
回答by monitorjbl
The answer Sotirios Delimanolis gave is the correct one. However, I thought it'd be nice to post this workaround as a separate answer. if you are in an environment in which you cannot change the ObjectMapper for each type of thing you need to return (like a Jersey/SpringMVC webapp), there is an alternative.
Sotirios Delimanolis 给出的答案是正确的。但是,我认为将此解决方法作为单独的答案发布会很好。如果您处于无法为需要返回的每种类型的事物更改 ObjectMapper 的环境中(例如 Jersey/SpringMVC webapp),则有一种替代方法。
You can simply include a private final field on the class that contains the type. The field won't be visible to anything outside the class, but if you annotate it with @JsonProperty("@type")
(or "@class" or whatever your type field is named) Hymanson will serialize it regardless of where the object is located.
您可以简单地在包含该类型的类中包含一个私有 final 字段。该字段对类外的任何内容都不可见,但是如果您使用@JsonProperty("@type")
(或“@class”或您的类型字段命名的任何名称)对其进行注释,Hymanson 将对其进行序列化,而不管对象位于何处。
@JsonTypeName("dog")
public static class Dog implements Animal {
@JsonProperty("@type")
private final String type = "dog";
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
回答by Giordano
I faced this problem as well and this is the workaround that I prefer (I'm using Kotlin but with Java it's pretty much the same)
我也遇到了这个问题,这是我更喜欢的解决方法(我使用的是 Kotlin,但使用 Java 时几乎相同)
The parent class configures @JsonTypeInfo
to use an existing property as the marker to break the ambiguity of the sub types
父类配置@JsonTypeInfo
使用现有属性作为标记,打破子类型的歧义
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY)
@JsonSubTypes(
JsonSubTypes.Type(value = Bob::class, name = "bob"),
JsonSubTypes.Type(value = Alice::class, name = "alice")
)
abstract class Person {
abstract val HymansonMarker: String
@JsonProperty("@type")
get
// ... rest of the class
}
The subclasses:
子类:
class Bob: Person {
override val HymansonMarker: String
get() = "bob"
// ... rest of the class
}
class Alice: Person {
override val HymansonMarker: String
get() = "alice"
// ... rest of the class
}
And you're set.
你已经准备好了。
回答by William
Similar but a little simpler than @monitorjbl above.
与上面的@monitorjbl 类似但稍微简单一些。
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = NAME, include = PROPERTY, property = "type")
@JsonSubTypes({
@Type(value = Dog.class, name = "dog"),
@Type(value = Cat.class, name = "cat")})
public interface Animal {}
public static class Dog implements Animal {
private final String type = "dog";
}
public static class Cat implements Animal {
private final String type = "cat";
}
回答by Fred
Sotirios Delimanolis answer is correct. If you are using Kotlin you can simply create a new type like this:
Sotirios Delimanolis 的答案是正确的。如果您使用 Kotlin,您可以简单地创建一个新类型,如下所示:
class CatList: List<Cat> by listOf()
回答by pdorgambide
As arrays do not use type erasure you can solve it overriding the ObjectMapper.writeValueAsString
to transform the Collection into an Array.
由于数组不使用类型擦除,您可以通过覆盖ObjectMapper.writeValueAsString
将集合转换为数组来解决它。
public class CustomObjectMapper extends ObjectMapper {
@Override
public String writeValueAsString(Object value) throws JsonProcessingException {
//Transform Collection to Array to include type info
if(value instanceof Collection){
return super.writeValueAsString(((Collection)value).toArray());
}
else
return super.writeValueAsString(value);
}
}
Use it into your Test.main:
在你的 Test.main 中使用它:
System.out.println(new CustomObjectMapper().writeValueAsString(list));
Output (cats and dogs list):
输出(猫和狗列表):
[{"@type":"cat","name":"Gardfield"},{"@type":"dog","name":"Snoopy"}]