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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 22:35:39  来源:igfitidea点击:

Why does Hymanson polymorphic serialization not work in lists?

javajsonserializationHymanson

提问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 Collectionor Map) contains. It chooses to use a simple serializer that doesn't interpret your annotations.

此处此处讨论了发生这种情况的各种原因。我不一定与理由同意,但Hyman逊,因为类型擦除,并不蝙蝠知道元素的类型List(或CollectionMap)含有。它选择使用不解释您的注释的简单序列化程序。

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 Catis not lost. Hymanson has access to it and uses it.

泛型类型参数Cat不会丢失。Hymanson 可以访问并使用它。

Second, you can instantiate and use an ObjectWriterfor 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 @JsonTypeInfoto 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.writeValueAsStringto 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"}]