【问题标题】:Jackson polymorphic deserialisation without using annotations不使用注释的杰克逊多态反序列化
【发布时间】:2019-07-17 14:17:35
【问题描述】:

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

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

    // getters and setters
}

类型参数可以是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 字段反序列化为的类列表?

请注意,type 字段的值设置为相应的 DogCat 类的“狗”或“猫”。

【问题讨论】:

  • 您找到解决问题的方法了吗?二选一有帮助吗?

标签: java json serialization jackson json-deserialization


【解决方案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 class naughty 将是null 如果是Cat class goodBoy 将是null

【讨论】:

  • 但是如果我事先不知道它是用于狗还是猫的 JSON 怎么办。我希望能够在运行时执行此操作。
  • 不能使用某种 Mixin/自定义反序列化器来做到这一点吗?
  • DogCat 有不同的变量,我想说自定义反序列化器是最好的选择@OneMoreError
  • 如何配置这些自定义反序列化器?对不起,我是杰克逊的新手,所以任何起点都会有很大帮助。
  • 检查我更新的代码@OneMoreError,自定义反序列化器还有很长的路要走
【解决方案2】:

因为您无法更改 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}}

另见:

【讨论】:

  • Op 声明目标类在外部 jar 中,不能修改。因此,不能删除类型属性
  • @SharonBenAsher,说得好。我将删除我的答案,直到我找到更好的东西,它可以在不更改源类的情况下工作。好点子。 +1
  • 1)我不是OP 2)这个话题是4个月前的,何必呢?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-05-18
  • 2015-05-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-13
相关资源
最近更新 更多