为什么杰克逊多态序列化在列表中不起作用?

时间:2015-12-10 03:26:22

标签: java json serialization 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不会添加类型信息。有谁知道为什么会这样?

6 个答案:

答案 0 :(得分:17)

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

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

首先,您可以创建一个实现List<Cat>的类,适当地实例化它并序列化实例。

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

泛型类型参数Cat不会丢失。杰克逊可以使用它并使用它。

其次,您可以为ObjectWriter类型实例化并使用List<Cat>。例如

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

将打印

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

答案 1 :(得分:11)

Sotirios Delimanolis给出的答案是正确的。但是,我认为将此解决方法作为单独的答案发布是很好的。如果您所处的环境中无法为需要返回的每种类型的东西更改ObjectMapper(如Jersey / SpringMVC webapp),则可以选择其他方式。

您可以在包含该类型的类中包含一个私有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;
  }
}

答案 2 :(得分:0)

由于数组不使用类型擦除,您可以解决它覆盖ObjectMapper.writeValueAsString以将Collection转换为数组。

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"}]

答案 3 :(得分:0)

我也遇到了这个问题,这是我更喜欢的解决方法(我使用的是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

}

您已设置好。

答案 4 :(得分:0)

与上述@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 :(得分:0)

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

class CatList: List<Cat> by listOf()