不使用注释的Jackson多态反序列化

时间:2019-07-17 14:17:35

标签: java json serialization jackson json-deserialization

我有一个Animal类,它接受通用类型参数T,如下所示:

public class Animal<T> {
    private String type;
    private T details;

    // getters and setters
}

type参数可以是DogCat

public class Dog {
    private String name;
    private boolean goodBoy;

    // no-arg, all-args constructors, getters and setters
}

public class Cat {
    private String name;
    private boolean naughty;

    // no-arg, all-args constructors, getters and setters
}

我正在尝试反序列化以下JSON

{
    "type": "dog",
    "details": {
        "name": "Marley",
        "goodBoy": true
    }
}

但是,当我反序列化时,字段详细信息总是反序列化为LinkedHashMap而不是类的特定实现。

public static void main(String[] args) throws IOException {
    ObjectMapper mapper = new ObjectMapper();

    Animal<Dog> dog = new Animal<>();
    dog.setType("dog");
    dog.setDetails(new Dog("Marley", true));

    String dogJson = mapper.writeValueAsString(dog);
    Animal dogDeserialized = mapper.readValue(dogJson, Animal.class);

    // dogDeserialized's details is LinkedHashMap
}

我不能更改上述类,因此不能在字段上使用注释。有没有一种方法可以指定ObjectMapper可以将details字段反序列化为的类的列表?

请注意,对于各个typeDog类,Cat字段的值设置为“ dog”或“ cat”。

2 个答案:

答案 0 :(得分:1)

使用TypeReference指定要反序列化的类型

Animal<Dog> dogDeserialized = mapper.readValue(
    dogJson, new TypeReference<Animal<Dog>>() {});

或者代替DogCat,您只能拥有一个具有所有属性的类

public class AnimalDetails {

  private String name;
  private Boolean goodBoy;
  private Boolean naughty;

 }

并设置ObjectMapper以忽略JSON中的未知属性:

 ObjectMapper mapper = new ObjectMapper()
  .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

因此,如果它是Dog,则类naughty将是null;如果是Cat,则类goodBoy将是null

答案 1 :(得分:0)

因为您无法更改POJO模型,所以需要实现自定义反序列化器并手动处理类型。自定义反序列化器可能如下所示:

class AnimalJsonDeserializer extends JsonDeserializer<Animal> {

    private Map<String, Class> availableTypes = new HashMap<>();

    public AnimalJsonDeserializer() {
        availableTypes.put("cat", Cat.class);
        availableTypes.put("dog", Dog.class);
    }

    @Override
    public Animal deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException {
        ObjectNode root = parser.readValueAsTree();
        JsonNode type = getProperty(parser, root, "type");

        Animal<Object> animal = new Animal<>();
        animal.setType(type.asText());

        Class<?> pojoClass = availableTypes.get(animal.getType());
        if (pojoClass == null) {
            throw new JsonMappingException(parser, "Class is not found for " + animal.getType());
        }

        JsonNode details = getProperty(parser, root, "details");
        animal.setDetails(parser.getCodec().treeToValue(details, pojoClass));

        return animal;
    }

    private JsonNode getProperty(JsonParser parser, ObjectNode root, String property) throws JsonMappingException {
        JsonNode value = root.get(property);
        if (value.isMissingNode() || value.isNull()) {
            throw new JsonMappingException(parser, "No " + property + " field!");
        }

        return value;
    }
}

我们需要使用SimpleModule类,并为Animal类注册反序列化器。用法示例:

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ObjectNode;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class JsonApp {

    public static void main(String[] args) throws Exception {
        SimpleModule animalModule = new SimpleModule();
        animalModule.addDeserializer(Animal.class, new AnimalJsonDeserializer());

        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(animalModule);

        Animal<Dog> dog = new Animal<>();
        dog.setType("dog");
        dog.setDetails(new Dog("Marley", true));

        Animal<Cat> cat = new Animal<>();
        cat.setType("cat");
        cat.setDetails(new Cat("Tom", false));

        Animal<Dog> husky = new Animal<>();
        husky.setType("husky");
        husky.setDetails(new Dog("Sib", true));

        for (Animal animal : new Animal[]{dog, cat, husky}) {
            String json = mapper.writeValueAsString(animal);
            System.out.println("JSON: " + json);
            System.out.println("Deserialized: " + mapper.readValue(json, Animal.class));
            System.out.println();
        }
    }
}

上面的代码显示:

JSON: {"type":"dog","details":{"name":"Marley","goodBoy":true}}
Deserialized: Animal{type='dog', details=Dog{name='Marley', goodBoy=true}}

JSON: {"type":"cat","details":{"name":"Tom","naughty":false}}
Deserialized: Animal{type='cat', details=Cat{name='Tom', naughty=false}}

JSON: {"type":"husky","details":{"name":"Sib","goodBoy":true}}
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Class is not found for husky
 at [Source: (String)"{"type":"husky","details":{"name":"Sib","goodBoy":true}}"; line: 1, column: 56]
    at AnimalJsonDeserializer.deserialize(JsonApp.java:69)
    at AnimalJsonDeserializer.deserialize(JsonApp.java:50)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3004)
    at com.celoxity.JsonApp.main(JsonApp.java:44)

如果可以更改类,则可以使用MixIn功能。

如果您不能更改源类,则可以始终使用Mix-in功能,该功能允许使用类似的方法创建新接口并适当地注释所有必需的类。对于您来说,我们还需要删除type将自动处理的Jackson属性。更改后,您的示例如下所示:

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JsonApp {

    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        mapper.addMixIn(Animal.class, AnimalMixIn.class);

        Animal<Dog> dog = new Animal<>();
        dog.setDetails(new Dog("Marley", true));

        String dogJson = mapper.writeValueAsString(dog);
        System.out.println(dogJson);
        Animal dogDeserialized = mapper.readValue(dogJson, Animal.class);
        System.out.println(dogDeserialized);
    }
}

interface AnimalMixIn {

    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", include = JsonTypeInfo.As.EXTERNAL_PROPERTY)
    @JsonSubTypes(value = {
            @JsonSubTypes.Type(value = Dog.class, name = "dog"),
            @JsonSubTypes.Type(value = Cat.class, name = "cat")})
    Object getDetails();
}

class Animal<T> {

    private T details;

    public T getDetails() {
        return details;
    }

    public void setDetails(T details) {
        this.details = details;
    }

    @Override
    public String toString() {
        return "Animal{details=" + details + '}';
    }
}

CatDogs类保持不变。上面的代码打印出来:

{"details":{"name":"Marley","goodBoy":true},"type":"dog"}
Animal{details=Dog{name='Marley', goodBoy=true}}

另请参阅: