34

在动物园示例中使用旋转:

public class ZooPen {
    public String type;
    public List<Animal> animals;
}

public class Animal {
    public String name;
    public int age;
}

public class Bird extends Animal {
    public double wingspan;
}

Animal如果没有指定翼展,我想使用多态反序列化来构造实例,如果是,则使用Bird实例。在 Jackson 中,无类型反序列化通常看起来像这样:

@JsonTypeInfo( 
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = "wingspan",
    visible = true,
    defaultImpl = Animal.class
)
@JsonSubTypes({
    @Type(value = Bird.class, name = "bird")
})  
public class Animal {
    ...
}

翼展值可以是任何值,如果没有具体匹配的值,Jackson 将使用 defaultImpl 类。

我可能会使用@JsonCreator

@JsonCreator
public static Animal create(Map<String,Object> jsonMap) 
        throws JsonProcessingException {

    ObjectMapper mapper = new ObjectMapper();
    if (jsonMap.get("wingspan") == null) {
        // Construct and return animal
    } else {
        // Construct and return bird
    }
}

但是,然后我必须手动处理额外的值并抛出一致的异常,并且不清楚Animal以后是否会正确序列化。

似乎我可以使用我自己的TypeResolveror TypeIdResolver,但这似乎比我自己反序列化原始 json 更有效。此外,TypeResolver并且TypeIdResolver似乎本质上假设类型信息是序列化的,所以这些不好用。

实现我自己JsonDeserializer的挂钩到生命周期以指定类型但仍使用基本的杰克逊注释处理功能是否可行?我一直在看JsonDeserializer.deserializeWithType(...),但这似乎将反序列化完全委托给TypeDeserializer. 还有一个问题是在我知道要使用哪种类型之前,我需要反序列化一些对象。

或者,可能有一种方法可以定位动物园围栏的类型,即使它位于父对象中。

有没有办法通过多态类型处理来做我想做的事情?

4

6 回答 6

8

从 Jackson 2.12.2 开始,以下使用“基于演绎的多态性”功能实现了目标。如果存在与子类型不同的属性Bird(即wingspan),则反序列化类型将为Bird; 否则它将是Animal

@JsonTypeInfo(use=Id.DEDUCTION, defaultImpl = Animal.class)
@JsonSubTypes({@Type(Bird.class)})
public class Animal {
    public String name;
    public int age;
}

基于演绎的多态性

基于演绎的多态性特征根据与特定亚型不同的属性的存在来推断亚型。如果没有子类型特定属性唯一可识别的子类型,defaultImpl则将使用 value 指定的类型。

基于推论的多态性功能在 Jackson 2.12 中根据jackson-databind#43实现,并在2.12 发行说明中进行了总结:

它基本上允许省略实际的 Type Id 字段或值,只要可以@JsonTypeInfo(use=DEDUCTION)从字段的存在中推断出 () 子类型。也就是说,每个子类型都包含一组不同的字段,因此在反序列化期间可以唯一且可靠地检测到类型。

Jackson 2.12.2 中的jackson-databind#3055添加了这种在没有唯一可识别子类型时指定默认类型而不是抛出异常的能力:

在没有单一候选人的情况下,defaultImpl无论是否适合,都应该是目标类型。

在Jackson 2.12 Most Wanted (1/5): Deduction-Based Polymorphism文章中对基于演绎的多态性进行了稍长的解释,该文章由 Jackson 创建者撰写。

于 2021-02-12T07:16:34.067 回答
1

编辑:如果您可以使用最新的 Jackson 候选版本,您的问题就解决了。我在这里组装了一个快速演示https://github.com/MariusSchmidt/de.denktmit.stackoverflow/tree/main/de.denktmit.jackson

您应该查看此线程https://github.com/FasterXML/jackson-databind/issues/1627,因为它讨论了您的问题并提出了解决方案。有一个合并,这对我来说很有希望https://github.com/FasterXML/jackson-databind/pull/2813。所以你可能会尝试遵循@JsonTypeInfo(use = DEDUCTION) 的路径。

但是,如果您不能使用即将推出的最新 Jackson 版本,我可能会这样做:

向后移植合并请求,或者

  1. 使用 Jackson 将输入反序列化为通用 JsonNode
  2. 使用https://github.com/json-path/JsonPath检查是否存在一个或多个属性。一些容器类可以包装唯一标识类类型所需的所有路径。
  3. 将 JsonNode 映射到确定的类,如此处所述将 JsonNode 转换为 POJO

这样,您可以充分利用 Jackson 的全部功能,而无需处理低级映射逻辑

此致,

马吕斯

动物

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.denktmit.stackoverflow.jackson.polymorphic.deductive.Bird;
import de.denktmit.stackoverflow.jackson.polymorphic.deductive.Fish;
import org.junit.jupiter.api.Test;

import java.util.List;

import static com.fasterxml.jackson.annotation.JsonTypeInfo.Id.DEDUCTION;
import static org.assertj.core.api.Assertions.assertThat;

@JsonTypeInfo(use = DEDUCTION)
@JsonSubTypes( {@JsonSubTypes.Type(Bird.class), @JsonSubTypes.Type(Fish.class)})
public class Animal {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

public class Bird extends de.denktmit.stackoverflow.jackson.polymorphic.deductive.Animal {
    private double wingspan;

    public double getWingspan() {
        return wingspan;
    }

    public void setWingspan(double wingspan) {
        this.wingspan = wingspan;
    }
}

public class Fish extends de.denktmit.stackoverflow.jackson.polymorphic.deductive.Animal {

    private boolean freshwater;

    public boolean isFreshwater() {
        return freshwater;
    }

    public void setFreshwater(boolean freshwater) {
        this.freshwater = freshwater;
    }
}

动物园围栏

public class ZooPen {

    private String type;
    private List<de.denktmit.stackoverflow.jackson.polymorphic.deductive.Animal> animals;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public List<de.denktmit.stackoverflow.jackson.polymorphic.deductive.Animal> getAnimals() {
        return animals;
    }

    public void setAnimals(List<de.denktmit.stackoverflow.jackson.polymorphic.deductive.Animal> animals) {
        this.animals = animals;
    }
}

考试

import com.fasterxml.jackson.databind.ObjectMapper;
        import de.denktmit.stackoverflow.jackson.polymorphic.deductive.Animal;
        import de.denktmit.stackoverflow.jackson.polymorphic.deductive.Bird;
        import de.denktmit.stackoverflow.jackson.polymorphic.deductive.Fish;
        import de.denktmit.stackoverflow.jackson.polymorphic.deductive.ZooPen;
        import org.junit.jupiter.api.Test;

        import static org.assertj.core.api.Assertions.assertThat;

public class DeductivePolymorphicDeserializationTest {

    private static final String birdString = "{\n" +
            "      \"name\": \"Tweety\",\n" +
            "      \"age\": 79,\n" +
            "      \"wingspan\": 2.9\n" +
            "    }";

    private static final String fishString = "{\n" +
            "      \"name\": \"Nemo\",\n" +
            "      \"age\": 16,\n" +
            "      \"freshwater\": false\n" +
            "    }";

    private static final String zooPenString = "{\n" +
            "  \"type\": \"aquaviary\",\n" +
            "  \"animals\": [\n" +
            "    {\n" +
            "      \"name\": \"Tweety\",\n" +
            "      \"age\": 79,\n" +
            "      \"wingspan\": 2.9\n" +
            "    },\n" +
            "    {\n" +
            "      \"name\": \"Nemo\",\n" +
            "      \"age\": 16,\n" +
            "      \"freshwater\": false\n" +
            "    }\n" +
            "  ]\n" +
            "}";
    private final ObjectMapper mapper = new ObjectMapper();

    @Test
    void deserializeBird() throws Exception {
        de.denktmit.stackoverflow.jackson.polymorphic.deductive.Animal animal = mapper.readValue(birdString, de.denktmit.stackoverflow.jackson.polymorphic.deductive.Animal.class);
        assertThat(animal).isInstanceOf(de.denktmit.stackoverflow.jackson.polymorphic.deductive.Bird.class);
    }

    @Test
    void deserializeFish() throws Exception {
        de.denktmit.stackoverflow.jackson.polymorphic.deductive.Animal animal = mapper.readValue(fishString, de.denktmit.stackoverflow.jackson.polymorphic.deductive.Animal.class);
        assertThat(animal).isInstanceOf(de.denktmit.stackoverflow.jackson.polymorphic.deductive.Fish.class);
    }

    @Test
    void deserialize() throws Exception {
        de.denktmit.stackoverflow.jackson.polymorphic.deductive.ZooPen zooPen = mapper.readValue(zooPenString, de.denktmit.stackoverflow.jackson.polymorphic.deductive.ZooPen.class);
        assertThat(zooPen).isInstanceOf(de.denktmit.stackoverflow.jackson.polymorphic.deductive.ZooPen.class);
    }
}
于 2020-11-22T12:24:47.200 回答
0

虽然没有直接回答您的问题,但我确实认为值得指出的是使用它并不过分繁琐@JsonCreator

@JsonCreator
public static Animal create(Map<String,Object> jsonMap) {
    String name = (String) jsonMap.get("name");
    int age = (int) jsonMap.get("age");
    if (jsonMap.keySet().contains("wingspan")) {
        double wingspan = (double) jsonMap.get("wingspan");
        return new Bird(name, age, wingspan);
    } else {
        return new Animal(name, age);
    }
}

没必要扔JsonProcessingException。这个自定义反序列化器会因为与内置 Jackson 反序列化器完全相同的原因而失败,即强制转换异常。对于复杂的反序列化,我更喜欢这种处理方式,因为它使代码更容易理解和修改。

于 2019-04-29T15:14:43.580 回答
0

您可以使用pretius-jddl反序列化来实现您的目标。我稍微扩展了类层次结构以显示它是如何工作的。这是一个示例代码:

public class SOAnswer3 {

    @ToString @Getter @Setter
    @AllArgsConstructor @NoArgsConstructor
    public static class Animal {
        String name;
        int age;
    }

    @ToString(callSuper = true) @Getter @Setter
    @AllArgsConstructor @NoArgsConstructor
    public static class Bird extends Animal {
        double wingspan;
    }

    @ToString(callSuper = true) @Getter @Setter
    @AllArgsConstructor @NoArgsConstructor
    public static class Elephant extends Animal {
        double trunkLength;
    }

    public static void main(String[] args) {
        String json = "[{"
                + "    \"name\": \"Marty\","
                + "    \"age\": 3"
                + "},"
                + "{"
                + "    \"name\": \"Danny\","
                + "    \"age\": 7,"
                + "    \"wingspan\": 1.4159"
                + "},{"
                + "    \"name\": \"King\","
                + "    \"age\": 21,"
                + "    \"trunkLength\": 2.11"
                + "}]";
        
        // create a deserializer instance
        DynamicObjectDeserializer deserializer = new DynamicObjectDeserializer();
        
        // runtime-configure deserialization rules
        deserializer.addRule(DeserializationRuleFactory.newRule(1, // priority 
                DeserializationCriterionFactory.targetClassIsAssignableFrom(Animal.class)
                    .and((e) -> e.getJsonNode().has("wingspan")),
                DeserializationActionFactory.objectToType(Bird.class)));
        
        deserializer.addRule(DeserializationRuleFactory.newRule(1,
                DeserializationCriterionFactory.targetClassIsAssignableFrom(Animal.class)
                    .and((e) -> e.getJsonNode().has("trunkLength")),
                DeserializationActionFactory.objectToType(Elephant.class)));
        
        List<Animal> deserializedObjects = deserializer.deserializeArray(json, Animal.class);
        
        for (Animal obj : deserializedObjects) {
            System.out.println("Deserialized Class: " + obj.getClass().getSimpleName()+";\t value: "+obj.toString());
        }
    }
}

结果:

Deserialized Class: Animal;  value: SOAnswer3.Animal(name=Marty, age=3)
Deserialized Class: Bird;    value: SOAnswer3.Bird(super=SOAnswer3.Animal(name=Danny, age=7), wingspan=1.4159)
Deserialized Class: Elephant;    value: SOAnswer3.Elephant(super=SOAnswer3.Animal(name=King, age=21), trunkLength=2.11)

pretius-jddl 的 Maven 依赖(在maven.org/jddl检查最新版本:

<dependency>
  <groupId>com.pretius</groupId>
  <artifactId>jddl</artifactId>
  <version>1.0.0</version>
</dependency>
于 2020-12-21T09:17:55.907 回答
-1

如果您没有与杰克逊结婚,我相信可以使用 FlexJSON 完成类似的事情。

http://flexjson.sourceforge.net/javadoc/flexjson/JSONDeserializer.html

我不熟悉 Jackson 做类似事情的方法,但我可以说 FlexJSON 的性能非常好,并且通常在序列化/反序列化步骤中使用起来很直观。

于 2013-10-21T01:35:00.300 回答
-1

嗨,肖恩,你可以很容易地通过杰克逊使用继承来实现这种行为。我在这里模拟了动物和鸟类的场景。

Impl中的构造函数允许实例​​化正确的 Animal 实例(即,如果存在名称和年龄,则为动物,如果存在名称年龄和翼展,则为鸟)。这对于使用 Jersey 之类的 API 通过 API 检索值也是一样的

@com.fasterxml.jackson.annotation.JsonSubTypes({
    @com.fasterxml.jackson.annotation.JsonSubTypes.Type(AnimalImpl.class)
})
@com.fasterxml.jackson.databind.annotation.JsonDeserialize(as = AnimalImpl.class)
public interface Animal {

    public String getName();

    public int getAge();
}

public class AnimalImpl implements Animal {

    private final String name;
    private final int age;

    public AnimalImpl(
        @JsonProperty("name") final String name,
        @JsonProperty("age") final int age
    ) {
    this.name = Objects.requireNonNull(name);
    this.age = Objects.requireNonNull(age);
    }

    @Override
    public String getName() {
    return name;
    }

    @Override
    public int getAge() {
    return age;
    }
}

@com.fasterxml.jackson.annotation.JsonSubTypes({
    @com.fasterxml.jackson.annotation.JsonSubTypes.Type(BirdImpl.class)
})
@com.fasterxml.jackson.databind.annotation.JsonDeserialize(as = BirdImpl.class)
public interface Bird extends Animal {

    public double getWingspan();
}

public class BirdImpl extends AnimalImpl implements Bird {

    private final double wingspan;

    public BirdImpl(
        @com.fasterxml.jackson.annotation.JsonProperty("name") final String name,
        @com.fasterxml.jackson.annotation.JsonProperty("age") final int age,
        @com.fasterxml.jackson.annotation.JsonProperty("wingspan") final double wingspan
    ) {
    super(name, age);
    this.wingspan = wingspan;
    }

    @Override
    public double getWingspan() {
    return wingspan;
    }
}

public class Test {

    public static void main(final String[] args) throws java.io.IOException {

    final com.fasterxml.jackson.databind.ObjectMapper objectMapper
        = new com.fasterxml.jackson.databind.ObjectMapper();

    final String animalJson = "{\"name\": \"the name\", \"age\": 42}";
    final Animal animal = objectMapper.readValue(animalJson, Animal.class);

    System.out.println(animal);

    final String birdJson = "{\"name\": \"the name\", \"age\": 42, \"wingspan\": 21}";
    final Bird bird = objectMapper.readValue(birdJson, Bird.class);

    System.out.println(bird);
    }
}
于 2020-02-21T07:38:34.187 回答