我们有一个目前使用JSON的服务。我们想稍微重新构建这个JSON(将一个属性向上移动一级),但也实现优雅的迁移,以便我们的服务可以处理旧结构和新结构。我们使用Jackson进行JSON反序列化。
在与杰克逊进行反序列化之前,我们如何重组JSON?
这是一个MCVE。
假设我们的旧JSON看起来如下:
{"reference": {"number" : "one", "startDate" : [2016, 11, 16], "serviceId" : "0815"}}
我们希望将serviceId
提升一级:
{"reference": {"number" : "one", "startDate" : [2016, 11, 16]}, "serviceId" : "0815"}
这是我们想要从两个旧的新JSON反序列化的类:
public final static class Container {
public final Reference reference;
public final String serviceId;
@JsonCreator
public Container(@JsonProperty("reference") Reference reference, @JsonProperty("serviceId") String serviceId) {
this.reference = reference;
this.serviceId = serviceId;
}
}
public final static class Reference {
public final String number;
public final LocalDate startDate;
@JsonCreator
public Reference(@JsonProperty("number") String number, @JsonProperty("startDate") LocalDate startDate) {
this.number = number;
this.startDate = startDate;
}
}
我们只需要serviceId
中的Container
,而不是两个类。
我工作的是以下解串器:
public static class ServiceIdMigratingContainerDeserializer extends JsonDeserializer<Container> {
private final ObjectMapper objectMapper;
{
objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
@Override
public Container deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
ObjectNode node = p.readValueAsTree();
migrate(node);
return objectMapper.treeToValue(node, Container.class);
}
private void migrate(ObjectNode containerNode) {
TreeNode referenceNode = containerNode.get("reference");
if (referenceNode != null && referenceNode.isObject()) {
TreeNode serviceIdNode = containerNode.get("serviceId");
if (serviceIdNode == null) {
TreeNode referenceServiceIdNode = referenceNode.get("serviceId");
if (referenceServiceIdNode != null && referenceServiceIdNode.isValueNode()) {
containerNode.set("serviceId", (ValueNode) referenceServiceIdNode);
}
}
}
}
}
这个反序列化器首先检索树,对其进行操作,然后使用自己的ObjectMapper
实例对其进行反序列化。它有效,但我们真的不喜欢这里有ObjectMapper
的另一个实例。如果我们不创建它并以某种方式使用系统范围的ObjectMapper
实例,我们会得到一个无限循环,因为当我们尝试调用objectMapper.treeToValue
时,我们的反序列化器会被递归调用。所以这是有效的(使用自己的ObjectMapper
实例),但它不是最佳解决方案。
我尝试过的另一种方法是使用BeanDeserializerModifier
和自己的JsonDeserializer
来“包装”默认序列化程序:
public static class ServiceIdMigrationBeanDeserializerModifier extends BeanDeserializerModifier {
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc,
JsonDeserializer<?> defaultDeserializer) {
if (beanDesc.getBeanClass() == Container.class) {
return new ModifiedServiceIdMigratingContainerDeserializer((JsonDeserializer<Container>) defaultDeserializer);
} else {
return defaultDeserializer;
}
}
}
public static class ModifiedServiceIdMigratingContainerDeserializer extends JsonDeserializer<Container> {
private final JsonDeserializer<Container> defaultDeserializer;
public ModifiedServiceIdMigratingContainerDeserializer(JsonDeserializer<Container> defaultDeserializer) {
this.defaultDeserializer = defaultDeserializer;
}
@Override
public Container deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
ObjectNode node = p.readValueAsTree();
migrate(node);
return defaultDeserializer.deserialize(new TreeTraversingParser(node, p.getCodec()), ctxt);
}
private void migrate(ObjectNode containerNode) {
TreeNode referenceNode = containerNode.get("reference");
if (referenceNode != null && referenceNode.isObject()) {
TreeNode serviceIdNode = containerNode.get("serviceId");
if (serviceIdNode == null) {
TreeNode referenceServiceIdNode = referenceNode.get("serviceId");
if (referenceServiceIdNode != null && referenceServiceIdNode.isValueNode()) {
containerNode.set("serviceId", (ValueNode) referenceServiceIdNode);
}
}
}
}
}
“包装”默认的反序列化器似乎是一种更好的方法,但是NPE失败了:
java.lang.NullPointerException
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:157)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:150)
at de.db.vz.rikernpushadapter.migration.ServiceIdMigrationTest$ModifiedServiceIdMigratingContainerDeserializer.deserialize(ServiceIdMigrationTest.java:235)
at de.db.vz.rikernpushadapter.migration.ServiceIdMigrationTest$ModifiedServiceIdMigratingContainerDeserializer.deserialize(ServiceIdMigrationTest.java:1)
at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:1623)
at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1217)
at ...
整个MCVE代码位于以下PasteBin。它是一个单一的全包测试案例,演示了这两种方法。 migratesViaDeserializerModifierAndUnmarshalsServiceId
失败。
所以这给我留下了一个问题:
在与杰克逊进行反序列化之前,我们如何重组JSON?
答案 0 :(得分:1)
在最好的传统中,在发布问题之后,我设法解决了这个问题。
两件事:
newJsonParser.nextToken();
以避免NPE。DelegatingDeserializer
这是一个有效的DelegatingDeserializer
:
public static class ModifiedServiceIdMigratingContainerDeserializer扩展DelegatingDeserializer {
public ModifiedServiceIdMigratingContainerDeserializer(JsonDeserializer<?> defaultDeserializer) {
super(defaultDeserializer);
}
@Override
protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) {
return new ModifiedServiceIdMigratingContainerDeserializer(newDelegatee);
}
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return super.deserialize(restructure(p), ctxt);
}
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt, Object intoValue) throws IOException,
JsonProcessingException {
return super.deserialize(restructure(p), ctxt, intoValue);
}
public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer)
throws IOException, JsonProcessingException {
return super.deserializeWithType(restructure(jp), ctxt, typeDeserializer);
}
public JsonParser restructure(JsonParser p) throws IOException, JsonParseException {
final ObjectNode node = p.readValueAsTree();
migrate(node);
final TreeTraversingParser newJsonParser = new TreeTraversingParser(node, p.getCodec());
newJsonParser.nextToken();
return newJsonParser;
}
private void migrate(ObjectNode containerNode) {
TreeNode referenceNode = containerNode.get("reference");
if (referenceNode != null && referenceNode.isObject()) {
TreeNode serviceIdNode = containerNode.get("serviceId");
if (serviceIdNode == null) {
TreeNode referenceServiceIdNode = referenceNode.get("serviceId");
if (referenceServiceIdNode != null && referenceServiceIdNode.isValueNode()) {
containerNode.set("serviceId", (ValueNode) referenceServiceIdNode);
}
}
}
}
}