当同时使用多态类型和构建器时,Jackson 反序列化不起作用
Jackson de-serialization doesn't work when both polymorphic types and builders are used
给定的测试失败,但我认为它不应该。
当数据对象转换为常规非生成器时 类,测试通过(来源:https://pastebin.com/pBkTb6HW)。
要使构建器对象通过测试,必须在 Animal
接口上添加 @JsonTypeInfo
注释。这意味着 Zoo
不能完全通用,但需要所有动物的通用超类型。
好像不应该存在这种差异?
杰克逊版本:2.10
错误:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `JacksonTest$Animal` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: (String)"{"animals":[{"@type":"Dog","name":"doggo"},{"@type":"Cat","name":"cat"}]}"; line: 1, column: 13] (through reference chain: JacksonTest$Zoo$Builder["animals"]->java.util.ArrayList[0])
完整测试用例:
public class JacksonTest {
@Test
void test() throws JsonProcessingException {
ObjectMapper m = new ObjectMapper();
m.findAndRegisterModules();
List<Animal> animals = List.of(
Dog.builder().name("doggo").build(),
Cat.builder().name("cat").build()
);
Zoo z = Zoo.builder().animals(animals).build();
String json = m.writeValueAsString(z);
Zoo deser = m.readValue(json, Zoo.class);
assertThat(z).isEqualTo(deser);
}
@Value
@Builder(builderClassName = "Builder")
@JsonDeserialize(builder = Zoo.Builder.class)
static class Zoo {
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
List<Animal> animals;
@JsonPOJOBuilder(withPrefix = "")
public static class Builder {}
}
interface Animal {
String getName();
}
@Value
@Builder(builderClassName = "Builder")
@JsonDeserialize(builder = Dog.Builder.class)
static class Dog implements Animal {
String name;
@JsonPOJOBuilder(withPrefix = "")
public static class Builder {}
}
@Value
@Builder(builderClassName = "Builder")
@JsonDeserialize(builder = Cat.Builder.class)
static class Cat implements Animal {
String name;
@JsonPOJOBuilder(withPrefix = "")
public static class Builder {}
}
}
由于我们想避免添加到Animals
接口,我们可以通过添加接口ZooBuilder
来解决这个问题,然后我们将@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
注释添加到animals
增变器方法并让 Zoo.Builder
实现 ZooBuilder
。
版本:
- 采用OpenJDK 14
- 日食:2020-03 (4.15.0)
- junit: 5.6.2
- log4j2: 2.13.3
- 杰克逊:2.11.0
- 龙目岛:1.18.12
package io.jeffmaxwell.Whosebug;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import lombok.Builder;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
@Log4j2
public class Q62193465 {
static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@BeforeAll
static void findAndRegistrerModules() {
OBJECT_MAPPER.findAndRegisterModules();
}
@Test
void test() throws JsonProcessingException {
List<Animal> animals = List.of(Dog.builder()
.name("doggo")
.build(),
Cat.builder()
.name("cat")
.build());
var zoo = Zoo.builder()
.animals(animals)
.build();
var zooAsJsonString = OBJECT_MAPPER.writeValueAsString(zoo);
LOGGER.info("zooAsJsonString {}", zooAsJsonString);
var zooFromJsonString = OBJECT_MAPPER.readValue(zooAsJsonString, Zoo.class);
assertEquals(zoo, zooFromJsonString);
}
//Added
interface ZooBuilder {
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
Zoo.Builder animals(final List<Animal> animals);
}
@Value
@Builder(builderClassName = "Builder")
@JsonDeserialize(builder = Zoo.Builder.class)
public static class Zoo {
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
List<Animal> animals;
@JsonPOJOBuilder(withPrefix = "")
public static class Builder implements ZooBuilder {
}
}
interface Animal {
String getName();
}
@Value
@Builder(builderClassName = "Builder")
@JsonDeserialize(builder = Dog.Builder.class)
static class Dog implements Animal {
String name;
@JsonPOJOBuilder(withPrefix = "")
public static class Builder {
}
}
@Value
@Builder(builderClassName = "Builder")
@JsonDeserialize(builder = Cat.Builder.class)
static class Cat implements Animal {
String name;
@JsonPOJOBuilder(withPrefix = "")
public static class Builder {
}
}
}
备选方案
这避免了接口,但风险更大,因为它与更多 Lombok 生成的代码交互。
package io.jeffmaxwell.Whosebug;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import lombok.Builder;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
@Log4j2
public class Q62193465 {
static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@BeforeAll
static void findAndRegistrerModules() {
OBJECT_MAPPER.findAndRegisterModules();
}
@Test
void test() throws JsonProcessingException {
List<Animal> animals = List.of(Dog.builder()
.name("doggo")
.build(),
Cat.builder()
.name("cat")
.build());
var zoo = Zoo.builder()
.animals(animals)
.build();
var zooAsJsonString = OBJECT_MAPPER.writeValueAsString(zoo);
LOGGER.info("zooAsJsonString {}", zooAsJsonString);
var zooFromJsonString = OBJECT_MAPPER.readValue(zooAsJsonString, Zoo.class);
assertEquals(zoo, zooFromJsonString);
}
@Value
@Builder(builderClassName = "Builder")
@JsonDeserialize(builder = Zoo.Builder.class)
public static class Zoo {
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
List<Animal> animals;
@JsonPOJOBuilder(withPrefix = "")
public static class Builder {
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
public Builder animals(List<Animal> animals) {
this.animals = animals;
return this;
}
}
}
interface Animal {
String getName();
}
@Value
@Builder(builderClassName = "Builder")
@JsonDeserialize(builder = Dog.Builder.class)
static class Dog implements Animal {
String name;
@JsonPOJOBuilder(withPrefix = "")
public static class Builder {
}
}
@Value
@Builder(builderClassName = "Builder")
@JsonDeserialize(builder = Cat.Builder.class)
static class Cat implements Animal {
String name;
@JsonPOJOBuilder(withPrefix = "")
public static class Builder {
}
}
}
给定的测试失败,但我认为它不应该。
当数据对象转换为常规非生成器时 类,测试通过(来源:https://pastebin.com/pBkTb6HW)。
要使构建器对象通过测试,必须在 Animal
接口上添加 @JsonTypeInfo
注释。这意味着 Zoo
不能完全通用,但需要所有动物的通用超类型。
好像不应该存在这种差异?
杰克逊版本:2.10
错误:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `JacksonTest$Animal` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: (String)"{"animals":[{"@type":"Dog","name":"doggo"},{"@type":"Cat","name":"cat"}]}"; line: 1, column: 13] (through reference chain: JacksonTest$Zoo$Builder["animals"]->java.util.ArrayList[0])
完整测试用例:
public class JacksonTest {
@Test
void test() throws JsonProcessingException {
ObjectMapper m = new ObjectMapper();
m.findAndRegisterModules();
List<Animal> animals = List.of(
Dog.builder().name("doggo").build(),
Cat.builder().name("cat").build()
);
Zoo z = Zoo.builder().animals(animals).build();
String json = m.writeValueAsString(z);
Zoo deser = m.readValue(json, Zoo.class);
assertThat(z).isEqualTo(deser);
}
@Value
@Builder(builderClassName = "Builder")
@JsonDeserialize(builder = Zoo.Builder.class)
static class Zoo {
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
List<Animal> animals;
@JsonPOJOBuilder(withPrefix = "")
public static class Builder {}
}
interface Animal {
String getName();
}
@Value
@Builder(builderClassName = "Builder")
@JsonDeserialize(builder = Dog.Builder.class)
static class Dog implements Animal {
String name;
@JsonPOJOBuilder(withPrefix = "")
public static class Builder {}
}
@Value
@Builder(builderClassName = "Builder")
@JsonDeserialize(builder = Cat.Builder.class)
static class Cat implements Animal {
String name;
@JsonPOJOBuilder(withPrefix = "")
public static class Builder {}
}
}
由于我们想避免添加到Animals
接口,我们可以通过添加接口ZooBuilder
来解决这个问题,然后我们将@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
注释添加到animals
增变器方法并让 Zoo.Builder
实现 ZooBuilder
。
版本:
- 采用OpenJDK 14
- 日食:2020-03 (4.15.0)
- junit: 5.6.2
- log4j2: 2.13.3
- 杰克逊:2.11.0
- 龙目岛:1.18.12
package io.jeffmaxwell.Whosebug;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import lombok.Builder;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
@Log4j2
public class Q62193465 {
static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@BeforeAll
static void findAndRegistrerModules() {
OBJECT_MAPPER.findAndRegisterModules();
}
@Test
void test() throws JsonProcessingException {
List<Animal> animals = List.of(Dog.builder()
.name("doggo")
.build(),
Cat.builder()
.name("cat")
.build());
var zoo = Zoo.builder()
.animals(animals)
.build();
var zooAsJsonString = OBJECT_MAPPER.writeValueAsString(zoo);
LOGGER.info("zooAsJsonString {}", zooAsJsonString);
var zooFromJsonString = OBJECT_MAPPER.readValue(zooAsJsonString, Zoo.class);
assertEquals(zoo, zooFromJsonString);
}
//Added
interface ZooBuilder {
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
Zoo.Builder animals(final List<Animal> animals);
}
@Value
@Builder(builderClassName = "Builder")
@JsonDeserialize(builder = Zoo.Builder.class)
public static class Zoo {
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
List<Animal> animals;
@JsonPOJOBuilder(withPrefix = "")
public static class Builder implements ZooBuilder {
}
}
interface Animal {
String getName();
}
@Value
@Builder(builderClassName = "Builder")
@JsonDeserialize(builder = Dog.Builder.class)
static class Dog implements Animal {
String name;
@JsonPOJOBuilder(withPrefix = "")
public static class Builder {
}
}
@Value
@Builder(builderClassName = "Builder")
@JsonDeserialize(builder = Cat.Builder.class)
static class Cat implements Animal {
String name;
@JsonPOJOBuilder(withPrefix = "")
public static class Builder {
}
}
}
备选方案
这避免了接口,但风险更大,因为它与更多 Lombok 生成的代码交互。
package io.jeffmaxwell.Whosebug;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import lombok.Builder;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
@Log4j2
public class Q62193465 {
static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@BeforeAll
static void findAndRegistrerModules() {
OBJECT_MAPPER.findAndRegisterModules();
}
@Test
void test() throws JsonProcessingException {
List<Animal> animals = List.of(Dog.builder()
.name("doggo")
.build(),
Cat.builder()
.name("cat")
.build());
var zoo = Zoo.builder()
.animals(animals)
.build();
var zooAsJsonString = OBJECT_MAPPER.writeValueAsString(zoo);
LOGGER.info("zooAsJsonString {}", zooAsJsonString);
var zooFromJsonString = OBJECT_MAPPER.readValue(zooAsJsonString, Zoo.class);
assertEquals(zoo, zooFromJsonString);
}
@Value
@Builder(builderClassName = "Builder")
@JsonDeserialize(builder = Zoo.Builder.class)
public static class Zoo {
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
List<Animal> animals;
@JsonPOJOBuilder(withPrefix = "")
public static class Builder {
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
public Builder animals(List<Animal> animals) {
this.animals = animals;
return this;
}
}
}
interface Animal {
String getName();
}
@Value
@Builder(builderClassName = "Builder")
@JsonDeserialize(builder = Dog.Builder.class)
static class Dog implements Animal {
String name;
@JsonPOJOBuilder(withPrefix = "")
public static class Builder {
}
}
@Value
@Builder(builderClassName = "Builder")
@JsonDeserialize(builder = Cat.Builder.class)
static class Cat implements Animal {
String name;
@JsonPOJOBuilder(withPrefix = "")
public static class Builder {
}
}
}