【问题标题】:Why does Jackson polymorphic serialization not work in lists?为什么杰克逊多态序列化在列表中不起作用?
【发布时间】:2016-03-15 13:46:51
【问题描述】:

杰克逊正在做一些真正奇怪的事情,我找不到任何解释。我正在做多态序列化,当一个对象独立时它可以完美地工作。但是,如果您将相同的对象放入一个列表并序列化该列表,它会删除类型信息。

它丢失类型信息的事实会导致人们怀疑类型擦除。但这是在列表的 contents 序列化期间发生的; Jackson 所要做的就是检查它正在序列化的当前对象以确定其类型。

我使用 Jackson 2.5.1 创建了一个示例:

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.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)));
  }
}

这是输出:

[{"name":null}]
{"@type":"cat","name":null}

如您所见,当对象在列表中时,Jackson 不会添加类型信息。有谁知道为什么会这样?

【问题讨论】:

    标签: java json serialization jackson


    【解决方案1】:

    herehere 讨论了导致这种情况发生的各种原因。我不一定同意这些原因,但杰克逊,因为类型擦除,不立即知道List(或CollectionMap)的元素类型包含。它选择使用不解释您的注释的简单序列化程序。

    您在这些链接中建议了两个选项:

    首先,你可以创建一个实现List&lt;Cat&gt;的类,适当地实例化它并序列化实例。

    class CatList implements List<Cat> {...}
    

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

    其次,您可以为List&lt;Cat&gt; 类型实例化并使用ObjectWriter。例如

    System.out.println(new ObjectMapper().writerFor(new TypeReference<List<Cat>>() {}).writeValueAsString(list));
    

    将打印

    [{"@type":"cat","name":"heyo"}]
    

    【讨论】:

    • 也可以像new ObjectMapper().writerFor( mapper.getTypeFactory().constructCollectionType(List.class, Animal.class)).writeValueAsString(list)一样使用
    • 这真的很奇怪,我无法想象他们为什么会做出这样的选择。无论列表的类型如何,您都会认为其中的对象将始终以相同的方式序列化。
    • mapper.writerWithType(mapper.getTypeFactory().constructCollectionType(List.class, Animal.class)).writeValueAsString(list)
    【解决方案2】:

    Sotirios Delimanolis 给出的答案是正确的。但是,我认为将此解决方法作为单独的答案发布会很好。如果您处于无法为需要返回的每种类型的事物(例如 Jersey/SpringMVC webapp)更改 ObjectMapper 的环境中,则可以使用另一种方法。

    您可以简单地在包含该类型的类中包含一个私有 final 字段。该字段对类外的任何内容都不可见,但如果您使用@JsonProperty("@type")(或“@class”或您的类型字段名称)对其进行注释,无论对象位于何处,Jackson 都会对其进行序列化。

    @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;
      }
    }
    

    【讨论】:

    • 我使用接口而不是类(Spring Projection)。这对我不起作用。请问有什么建议吗?
    • 它对我有用,但是当我序列化一个元素时,@type 字段是重复的。
    • 序列化可以工作,但你不能反序列化它。
    【解决方案3】:

    我也遇到过这个问题,这是我更喜欢的解决方法(我使用的是 Kotlin,但使用 Java 几乎相同)

    父类配置@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 jacksonMarker: String
            @JsonProperty("@type")
            get
    
        // ... rest of the class
    }
    

    子类:

    class Bob: Person {
    
        override val jacksonMarker: String
            get() = "bob"
    
        // ... rest of the class
    
    }
    
    
    class Alice: Person {
    
        override val jacksonMarker: String
            get() = "alice"
    
        // ... rest of the class
    
    }
    

    你已经准备好了。

    【讨论】:

    • @saw303 如果您序列化Person 的列表,您将获得具有@type 鉴别器属性的对象列表,这将允许您正确反序列化它们。我不知道你为什么说这不能回答问题,我在生产中使用这样的代码已经有一段时间了。
    • 啊,现在我明白了。也许您将这个额外的解释添加到您的答案中。当我阅读您的答案时,我并不清楚。
    • 这适用于我在 Java 中。不要忘记在注解中添加, name = "bob" 以与@JsonProperty("@type") getter 建立链接。
    【解决方案4】:

    与上面的 @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";
    }
    

    【讨论】:

      【解决方案5】:

      Sotirios Delimanolis 的答案是正确的。如果您使用的是 Kotlin,您可以像这样简单地创建一个新类型:

      class CatList: List<Cat> by listOf()
      

      【讨论】:

        【解决方案6】:

        由于数组不使用类型擦除,您可以通过覆盖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);
                    }
        }
        

        在你的 Test.main 中使用它:

        System.out.println(new CustomObjectMapper().writeValueAsString(list));
        

        输出(猫狗列表):

        [{"@type":"cat","name":"Gardfield"},{"@type":"dog","name":"Snoopy"}]
        

        【讨论】:

          猜你喜欢
          • 2014-08-13
          • 1970-01-01
          • 1970-01-01
          • 2023-03-30
          • 2015-05-13
          • 1970-01-01
          • 2014-03-11
          • 1970-01-01
          相关资源
          最近更新 更多