在动物园示例中使用旋转:
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 = **???**)
})
public class Animal {
...
}
翼展值可以是任何东西,如果没有特定匹配的东西,杰克逊会回到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是否会在以后正确序列化。
有没有办法用多态类型处理做我想做的事情?我似乎可以使用自己的TypeResolver或TypeIdResolver,但这似乎比仅仅反序列化原始json更多的工作。或者,可能有一种方法可以定位动物园笔的类型,即使它位于父对象中。有什么想法吗?
修改
TypeResolver和TypeIdResolver似乎本质上假设类型信息是序列化的,所以这些不好用。是否可以实现我自己的JsonDeserializer,它挂钩到生命周期来指定类型,但仍然使用基本的jackson注释处理功能?我一直在查看JsonDeserializer.deserializeWithType(...)
,但这似乎完全将反序列化委托给TypeDeserializer。在我知道要使用哪种类型之前,我还需要对某些对象进行反序列化。
答案 0 :(得分:2)
Jackson 2.12 添加了“deduction-based polymorphism”以根据与特定子类型不同的属性的存在自动推断子类型。从 Jackson 2.12.2 开始,当没有子类型特定属性唯一可识别的子类型时,可以指定要使用的类型。
这些功能可用于在 Jackson 2.12.2 或更高版本中完成请求的反序列化。为此,请在 @JsonTypeInfo(use=Id.DEDUCTION, defaultImpl = Animal.class)
提供的受支持子类型的完整列表旁边使用 @JsonSubTypes
:
@JsonTypeInfo(use=Id.DEDUCTION, defaultImpl = Animal.class)
@JsonSubTypes({@Type(Bird.class)})
public class Animal {
public String name;
public int age;
}
基于推导的多态性功能是按照 jackson-databind#43 实现的,并在 2.12 release notes 中进行了总结:
<块引用>它基本上允许省略实际的 Type Id 字段或值,只要可以从字段的存在中推导出子类型 (@JsonTypeInfo(use=DEDUCTION)
)。也就是说,每个子类型都包含一组不同的字段,因此在反序列化期间可以唯一且可靠地检测到类型。
此行为由 Jackson 2.12.2 中的 jackson-databind#3055 改进:
<块引用>在没有一个候选人的情况下,无论是否合适,defaultImpl
都应该是目标类型。
Jackson 创建者撰写的 Jackson 2.12 Most Wanted (1/5): Deduction-Based Polymorphism 文章中对基于演绎的多态性进行了稍长的解释。
答案 1 :(得分:0)
如果你没与杰克逊结婚,我相信使用FlexJSON可以实现与此类似的东西。
http://flexjson.sourceforge.net/javadoc/flexjson/JSONDeserializer.html
我不熟悉Jackson执行类似操作的方法,但我可以说FlexJSON非常高效,并且在序列化/反序列化步骤中通常很直观。
答案 2 :(得分: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解串器完全相同的原因而失败,即强制转换异常。对于复杂的反序列化,我更喜欢这种处理方式,因为它使代码更易于理解和修改。
答案 3 :(得分:0)
编辑:如果您可以使用最新的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版本,这是我可能会做的事情:
向后移植合并请求,或者
这样,您可以利用Jackson的全部功能,而无需处理低级映射逻辑
最诚挚的问候,
Marius
动物
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;
}
}
ZooPen
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);
}
}
答案 4 :(得分: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>
答案 5 :(得分:-1)
您好,Shaun,使用Jackson继承,您可以很容易地实现这种行为。我在这里为动物和鸟类场景建模。
Imp 中的构造函数允许实例化Animal的正确实例(即,如果存在名称和年龄,则为Animal;如果存在年龄和翼展名称,则为Bird)。使用泽西(Jersey)之类的方法通过API检索值的工作原理相同
x = ['CPC', 'AID CHECKS']
counts = [208, 28]
print (x)
print (counts)
source = ColumnDataSource(data=dict(x=x, counts=counts))
p = figure(x_range=FactorRange(*x), plot_height=600, plot_width=990, title="NPS Locations by Security Checks")
p.xaxis.axis_label_text_font_size = "5pt"
p.xaxis.axis_label_text_font_style='bold'
p.vbar(x='x', top='counts', width=0.9, source=source)
p.add_tools(HoverTool(tooltips=[("LOCATION", "@x"), ("TOTAL", "@counts")]))
p.y_range.start = 0
p.x_range.range_padding = 0.1
p.xaxis.major_label_orientation = 1
p.xgrid.grid_line_color = None
show(p)