現在、JSONを使用しているサービスがあります。このJSONをわずかに再構築し(1つのプロパティを1レベル上に移動する)、サービスが古い構造だけでなく新しい構造も処理できるように、優雅な移行を実装したいと考えています。私たちはJSONの逆シリアル化にJacksonを使用しています。ジャクソンとデシリアライズする前のJSONを再構築する
ジャクソンとの直列化を解除する前にJSONをどのように再構築しますか?
ここにMCVEがあります。
{"reference": {"number" : "one", "startDate" : [2016, 11, 16], "serviceId" : "0815"}}
我々はserviceId
1つ上のレベルに移動したい:
は、次のように私たちの古いJSONが見えたと
{"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
のインスタンスで)動作しますが、最適な解決策ではありません。
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);
}
}
}
}
}
「ラッピング」のデフォルトのデシリアライザは、より良い方法のようですが、これはで失敗します。
私が試したもう一つの方法は、BeanDeserializerModifier
と自身のJsonDeserializer
「ラップ」デフォルトのシリアライザを使用していました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前再構築するにはどうすればよいですか?