Jackson mixin 在序列化和反序列化时被忽略
Jackson mixin is ignored on serialization and deserialization
当我只有一个无法更改的接口时,我需要能够从 JSON 对象创建 Java POJO。我希望 Mixins 可以帮助实现这一目标。我创建了一个 Mixin,希望它能工作,但无法让 Jackson 使用它。
Jackson 似乎忽略了我为接口和实现定义的 Mixin。如果没有将 Mixin 添加到 ObjectMapper,测试失败是我所期望的。
下面是说明问题的最简单示例。 类 每个都在自己的包中。真正的用例要复杂得多,包括接口列表。我正在使用 Jackson 2.10.3。
对我做错了什么有什么建议吗?
蒂莫西
什么不起作用
接口 reader 测试失败并出现 InvalidDefinitionException:无法构造 model.Level4 的实例(不存在 Creator,如默认构造):抽象类型需要映射到具体类型,具有自定义反序列化器,或包含额外的类型信息
次要的是,Mixin 为名称字段定义了一个新标签 (nameTest),该标签应反映在 writeValueAsString 的输出中。它输出具有标签(名称)原始值的字段。
界面
public interface Level4 {
public Long getId();
public void setId(Long id);
public String getName();
public void setName(String name);
}
实施
public class Level4Impl implements Level4 {
private Long id;
private String name;
@Override
public Long getId() {
return id;
}
@Override
public void setId(Long id) {
this.id = id;
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
}
混音
public abstract class Level4Mixin {
public Level4Mixin(
@JsonProperty("id") Long id,
@JsonProperty("nameTest") String name) { }
}
单元测试
class Level4MixinTest {
private ObjectMapper mapper;
@BeforeEach
void setUp() throws Exception {
mapper = new ObjectMapper();
mapper.addMixIn(Level4.class, Level4Mixin.class);
mapper.addMixIn(Level4Impl.class, Level4Mixin.class);
}
@Test
void test_InterfaceWrite() throws JsonProcessingException {
Level4 lvl4 = new Level4Impl();
lvl4.setId(1L);
lvl4.setName("test");
String json = mapper.writeValueAsString(lvl4);
assertNotNull(json);
assertTrue(json.contains("nameTest"));
}
@Test
void test_InterfaceRead() throws JsonProcessingException {
String json = "{\"id\":1,\"nameTest\":\"test\"}";
assertDoesNotThrow(() -> {
Level4 parsed = mapper.readValue(json, Level4.class);
assertNotNull(parsed);
});
}
@Test
void test_ImplWrite() throws JsonProcessingException {
Level4Impl lvl4 = new Level4Impl();
lvl4.setId(1L);
lvl4.setName("test");
String json = mapper.writeValueAsString(lvl4);
assertNotNull(json);
assertTrue(json.contains("nameTest"));
}
@Test
void test_ImplRead() {
String json = "{\"id\":1,\"nameTest\":\"test\"}";
assertDoesNotThrow(() -> {
Level4Impl parsed = mapper.readValue(json, Level4Impl.class);
assertNotNull(parsed);
});
}
}
要在序列化对象时向该对象添加属性,您可以使用 @JsonAppend。例如:
@JsonAppend(attrs = {@JsonAppend.Attr(value = "nameTest")})
public class Level4Mixin {}
测试:
@BeforeEach
void setUp() throws Exception {
mapper = new ObjectMapper()
.addMixIn(Level4Impl.class, Level4Mixin.class);
}
@Test
void test_ImplWrite() throws JsonProcessingException {
Level4Impl lvl4 = new Level4Impl();
lvl4.setId(1L);
lvl4.setName("test");
String json = mapper.writerFor(Level4Impl.class)
.withAttribute("nameTest", "myValue")
.writeValueAsString(lvl4);
assertNotNull(json);
assertTrue(json.contains("nameTest"));
assertTrue(json.contains("myValue"));
}
同样适用于 test_InterfaceWrite
。
将 json 反序列化为对象的测试不清楚:
@Test
void test_ImplRead() {
String json = "{\"id\":1,\"nameTest\":\"test\"}";
assertDoesNotThrow(() -> {
Level4Impl parsed = mapper.readValue(json, Level4Impl.class);
assertNotNull(parsed);
});
}
class Level4Impl
没有 属性 nameTest
所以反序列化失败。如果您不想抛出异常,您可以将 ObjectMapper
配置为不在未知属性上失败。例如:
@Test
void test_ImplRead() {
String json = "{\"id\":1,\"nameTest\":\"test\"}";
assertDoesNotThrow(() -> {
Level4Impl parsed = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.readValue(json, Level4Impl.class);
assertNotNull(parsed);
});
}
首先你必须让 Jackson 知道它应该实例化你接口的哪个子class。您可以通过向混合 class 添加 @JsonTypeInfo
and/or @JsonSubTypes
注释来实现。对于单个 subclass 以下内容就足够了:
@JsonTypeInfo(use = Id.NAME, defaultImpl = Level4Impl.class)
public abstract class Level4Mixin {
}
对于多个子classes,它会稍微复杂一些,并且需要在 JSON 有效负载中添加额外的字段来识别具体类型。有关详细信息,请参阅 Jackson Polymorphic Deserialization。另外值得一提的是,添加类型信息会导致类型 ID 字段被写入 JSON。仅供参考。
添加新标签就像为所需的 属性 添加一对 getter 和 setter 一样简单。显然,在这种情况下,原始 name
字段也将写入 JSON。要更改它,您可能需要将 @JsonIgnore 放在 subclass 或 mix-in 中的 getter 上。在后一种情况下,所有子 classes.
的名称将被忽略
最后注意:在这种情况下,您应该仅使用超级类型注册您的混入。
以下是满足您测试的 classes 的更改:
Level4Impl
public class Level4Impl implements Level4 {
private Long id;
private String name;
@Override
public Long getId() {
return id;
}
@Override
public void setId(Long id) {
this.id = id;
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
public String getNameTest() {
return name;
}
public void setNameTest(String name) {
this.name = name;
}
}
混音
@JsonTypeInfo(use = Id.NAME, defaultImpl = Level4Impl.class)
public interface Level4Mixin {
@JsonIgnore
String getName();
}
Level4MixinTest 改变
@BeforeEach
void setUp() throws Exception {
mapper = new ObjectMapper();
mapper.addMixIn(Level4.class, Level4Mixin.class);
// remove
//mapper.addMixIn(Level4Impl.class, Level4Mixin.class);
}
当我只有一个无法更改的接口时,我需要能够从 JSON 对象创建 Java POJO。我希望 Mixins 可以帮助实现这一目标。我创建了一个 Mixin,希望它能工作,但无法让 Jackson 使用它。
Jackson 似乎忽略了我为接口和实现定义的 Mixin。如果没有将 Mixin 添加到 ObjectMapper,测试失败是我所期望的。
下面是说明问题的最简单示例。 类 每个都在自己的包中。真正的用例要复杂得多,包括接口列表。我正在使用 Jackson 2.10.3。
对我做错了什么有什么建议吗?
蒂莫西
什么不起作用
接口 reader 测试失败并出现 InvalidDefinitionException:无法构造 model.Level4 的实例(不存在 Creator,如默认构造):抽象类型需要映射到具体类型,具有自定义反序列化器,或包含额外的类型信息
次要的是,Mixin 为名称字段定义了一个新标签 (nameTest),该标签应反映在 writeValueAsString 的输出中。它输出具有标签(名称)原始值的字段。
界面
public interface Level4 {
public Long getId();
public void setId(Long id);
public String getName();
public void setName(String name);
}
实施
public class Level4Impl implements Level4 {
private Long id;
private String name;
@Override
public Long getId() {
return id;
}
@Override
public void setId(Long id) {
this.id = id;
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
}
混音
public abstract class Level4Mixin {
public Level4Mixin(
@JsonProperty("id") Long id,
@JsonProperty("nameTest") String name) { }
}
单元测试
class Level4MixinTest {
private ObjectMapper mapper;
@BeforeEach
void setUp() throws Exception {
mapper = new ObjectMapper();
mapper.addMixIn(Level4.class, Level4Mixin.class);
mapper.addMixIn(Level4Impl.class, Level4Mixin.class);
}
@Test
void test_InterfaceWrite() throws JsonProcessingException {
Level4 lvl4 = new Level4Impl();
lvl4.setId(1L);
lvl4.setName("test");
String json = mapper.writeValueAsString(lvl4);
assertNotNull(json);
assertTrue(json.contains("nameTest"));
}
@Test
void test_InterfaceRead() throws JsonProcessingException {
String json = "{\"id\":1,\"nameTest\":\"test\"}";
assertDoesNotThrow(() -> {
Level4 parsed = mapper.readValue(json, Level4.class);
assertNotNull(parsed);
});
}
@Test
void test_ImplWrite() throws JsonProcessingException {
Level4Impl lvl4 = new Level4Impl();
lvl4.setId(1L);
lvl4.setName("test");
String json = mapper.writeValueAsString(lvl4);
assertNotNull(json);
assertTrue(json.contains("nameTest"));
}
@Test
void test_ImplRead() {
String json = "{\"id\":1,\"nameTest\":\"test\"}";
assertDoesNotThrow(() -> {
Level4Impl parsed = mapper.readValue(json, Level4Impl.class);
assertNotNull(parsed);
});
}
}
要在序列化对象时向该对象添加属性,您可以使用 @JsonAppend。例如:
@JsonAppend(attrs = {@JsonAppend.Attr(value = "nameTest")})
public class Level4Mixin {}
测试:
@BeforeEach
void setUp() throws Exception {
mapper = new ObjectMapper()
.addMixIn(Level4Impl.class, Level4Mixin.class);
}
@Test
void test_ImplWrite() throws JsonProcessingException {
Level4Impl lvl4 = new Level4Impl();
lvl4.setId(1L);
lvl4.setName("test");
String json = mapper.writerFor(Level4Impl.class)
.withAttribute("nameTest", "myValue")
.writeValueAsString(lvl4);
assertNotNull(json);
assertTrue(json.contains("nameTest"));
assertTrue(json.contains("myValue"));
}
同样适用于 test_InterfaceWrite
。
将 json 反序列化为对象的测试不清楚:
@Test
void test_ImplRead() {
String json = "{\"id\":1,\"nameTest\":\"test\"}";
assertDoesNotThrow(() -> {
Level4Impl parsed = mapper.readValue(json, Level4Impl.class);
assertNotNull(parsed);
});
}
class Level4Impl
没有 属性 nameTest
所以反序列化失败。如果您不想抛出异常,您可以将 ObjectMapper
配置为不在未知属性上失败。例如:
@Test
void test_ImplRead() {
String json = "{\"id\":1,\"nameTest\":\"test\"}";
assertDoesNotThrow(() -> {
Level4Impl parsed = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.readValue(json, Level4Impl.class);
assertNotNull(parsed);
});
}
首先你必须让 Jackson 知道它应该实例化你接口的哪个子class。您可以通过向混合 class 添加 @JsonTypeInfo
and/or @JsonSubTypes
注释来实现。对于单个 subclass 以下内容就足够了:
@JsonTypeInfo(use = Id.NAME, defaultImpl = Level4Impl.class)
public abstract class Level4Mixin {
}
对于多个子classes,它会稍微复杂一些,并且需要在 JSON 有效负载中添加额外的字段来识别具体类型。有关详细信息,请参阅 Jackson Polymorphic Deserialization。另外值得一提的是,添加类型信息会导致类型 ID 字段被写入 JSON。仅供参考。
添加新标签就像为所需的 属性 添加一对 getter 和 setter 一样简单。显然,在这种情况下,原始 name
字段也将写入 JSON。要更改它,您可能需要将 @JsonIgnore 放在 subclass 或 mix-in 中的 getter 上。在后一种情况下,所有子 classes.
最后注意:在这种情况下,您应该仅使用超级类型注册您的混入。
以下是满足您测试的 classes 的更改:
Level4Impl
public class Level4Impl implements Level4 {
private Long id;
private String name;
@Override
public Long getId() {
return id;
}
@Override
public void setId(Long id) {
this.id = id;
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
public String getNameTest() {
return name;
}
public void setNameTest(String name) {
this.name = name;
}
}
混音
@JsonTypeInfo(use = Id.NAME, defaultImpl = Level4Impl.class)
public interface Level4Mixin {
@JsonIgnore
String getName();
}
Level4MixinTest 改变
@BeforeEach
void setUp() throws Exception {
mapper = new ObjectMapper();
mapper.addMixIn(Level4.class, Level4Mixin.class);
// remove
//mapper.addMixIn(Level4Impl.class, Level4Mixin.class);
}