使用Jackson序列化java对象时保持子类型信息,而不使用包装器类

时间:2014-12-11 15:24:35

标签: java json serialization polymorphism jackson

我正在尝试使用Jackson在Java中使用两个子类在JSON文件和抽象类之间进行转换。理想情况下,我想使用JSON如下:

没有包装的Json文档

[ {
  "type" : "lion",
  "name" : "Simba",
  "endangered" : true,
  "action" : "running"
}, {
  "type" : "elephant",
  "name" : "Dumbo",
  "endangered" : false,
  "table" : [ 1.0, 2.0, 3.0 ]
} ]

我在http://www.studytrails.com/java/json/java-jackson-Serialization-polymorphism.jsp

上注明了Animal抽象类
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.PROPERTY, property = "type")
@JsonSubTypes({ @Type(value = Lion.class, name = "lion"),
    @Type(value = Elephant.class, name = "elephant") })
public abstract class Animal {
    String name;
    boolean endangered;

//Skipped constructor/getters/setters

}

我可以使用包装器对象成功读取/写入它,但生成的JSON文件将包含一个额外的animals对象。

带有包装器的JSON文档

{
  "animals" : [ {
    "type" : "lion",
    "name" : "Simba",
    "endangered" : true,
    "action" : "running"
  }, {
    "type" : "elephant",
    "name" : "Dumbo",
    "endangered" : false,
    "table" : [ 1.0, 2.0, 3.0 ]
  } ]
}

当我使用普通的Java列表时,我可以成功地从 Json文档中读取没有包装器但是如果我尝试编写它,输出文件将如下所示:

output.json

[ {
  "name" : "Simba",
  "endangered" : true,
  "action" : "running"
}, {
  "name" : "Dumbo",
  "endangered" : false,
  "table" : [ 1.0, 2.0, 3.0 ]
} ]

Java代码

// Test reading using raw list
JavaType listType = mapper.getTypeFactory()
        .constructCollectionType(List.class, Animal.class);
List<Animal> jsonList = mapper.readValue(new FileInputStream(
        "demo.json"), listType);
jsonDocument = new File(outputFile);
// Test writing using raw list
mapper.writerWithDefaultPrettyPrinter().writeValue(jsonDocument,
        jsonList);

在将对象序列化为JSON时如何包含类型信息的任何想法?

可以在以下位置找到完整的Eclipse项目: https://github.com/nkatsar/json-subclass

编辑: 简化 Java代码以使用JavaType代替TypeReference。 GitHub上的代码也已使用正确的解决方案进行更新

编辑2: 正如评论中所提到的,我最终使用Object数组进行序列化/反序列化,如下所示:

// Test reading using array
Animal[] jsonArray = mapper.readValue(
        new FileInputStream(demoFile), Animal[].class);
System.out.println("Reading using array:\nObject: "
        + Arrays.toString(jsonArray));

// Test writing using array
outputJson = mapper.writeValueAsString(jsonArray);
System.out.println("Writing using array:\nJSON: " + outputJson);

1 个答案:

答案 0 :(得分:5)

这里的问题是Java类型擦除,这意味着List对象的名义类型是List。由于java.lang.Object没有@JsonTypeInfo,因此无法启用它。

但是您可以将Jackson配置为使用特定的Java类型来转换List元素:

JavaType listJavaType = mapper.getTypeFactory().constructCollectionType(List.class, Animal.class);
String outputJson = mapper.writerWithType(listJavaType).writeValueAsString(myList);

修改了你的代码:

public static void main(String [] args){

List<Animal> myList = new ArrayList<Animal>();
myList.add(new Lion("Simba"));
// myList.add(new Lion("Nala"));
myList.add(new Elephant("Dumbo"));
// myList.add(new Elephant("Lucy"));
AnimalList wrapperWrite = new AnimalList(myList);

try {
    ObjectMapper mapper = new ObjectMapper();
    JavaType listJavaType = mapper.getTypeFactory().constructCollectionType(List.class, Animal.class);

    String outputJson = mapper.writerWithType(listJavaType).writeValueAsString(myList);
    System.out.println(outputJson);

    List wrapperRead = mapper.readValue(outputJson, List.class);
    System.out.println("Read recently generated data: " + wrapperRead);

    // Test reading using raw list
    List<Animal> jsonList = mapper.readValue(new FileInputStream( "demo.json"), listJavaType);
    System.out.println("Read from demo.json \ndata: " + jsonList);

    outputJson = mapper.writerWithType(listJavaType).writeValueAsString(jsonList);
    System.out.println(outputJson);

} catch (JsonGenerationException e) {
    System.out.println("Could not generate JSON: " + e.getMessage());
} catch (JsonMappingException e) {
    System.out.println("Invalid JSON Mapping: " + e.getMessage());
} catch (IOException e) {
    System.out.println("File I/O error: ");
}

}

结果:

[{"type":"lion","name":"Simba","endangered":false,"action":null},{"type":"elephant","name":"Dumbo","endangered":false,"table":null}]
Read recently generated data: [{type=lion, name=Simba, endangered=false, action=null}, {type=elephant, name=Dumbo, endangered=false, table=null}]
Read from demo.json 
data: [Animal(name=Nala, endangered=true, action=running}, Animal(name=Lucy, endangered=false, table=[1.0, 2.0, 3.0]}]
[{"type":"lion","name":"Nala","endangered":true,"action":"running"},{"type":"elephant","name":"Lucy","endangered":false,"table":[1.0,2.0,3.0]}]

另一种解决方案: 您可以使用自定义TypeReference来通知Jackson需要使用哪个类来转换List:

mapper.writerWithType(new TypeReference<List<Animal>>() {}).writeValueAsString(myList)

该解决方案也可以使用,但我更喜欢主要解决方案,因为它更干净,您不需要实例化抽象&#39; TypeReference&#39;类...