Jacksonを使用して、注釈を使用せず、クラスを変更せずに、プライベートフィールドとカスタム引数コンストラクターを持つクラスに逆シリアル化することは可能ですか?
この組み合わせを使用すると、ジャクソンで可能になることを知っています:1)Java 8、2)「-parameters」オプションを使用してコンパイルし、3)パラメーター名がJSONと一致します。ただし、GSONでは、これらすべての制限がなくてもデフォルトで可能です。
例えば:
public class Person {
private final String firstName;
private final String lastName;
private final int age;
public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public static void main(String[] args) throws IOException {
String json = "{firstName: \"Foo\", lastName: \"Bar\", age: 30}";
System.out.println("GSON: " + deserializeGson(json)); // works fine
System.out.println("Jackson: " + deserializeJackson(json)); // error
}
public static Person deserializeJackson(String json) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
return mapper.readValue(json, Person.class);
}
public static Person deserializeGson(String json) {
Gson gson = new GsonBuilder().create();
return gson.fromJson(json, Person.class);
}
}
WichはGSONで正常に動作しますが、ジャクソンは以下をスローします。
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `jacksonParametersTest.Person` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (String)"{firstName: "Foo", lastName: "Bar", age: 30}"; line: 1, column: 2]
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.Java:67)
GSONでは可能ですので、Personクラスを変更せず、Java 8を使用せず、明示的なカスタムデシリアライザーを使用せずに、ジャクソンに何らかの方法があるはずです。誰もが解決策を知っていますか?
Gsonは引数コンストラクターをスキップしているように見えるため、リフレクションを使用して、舞台裏で引数なしのコンストラクターを作成する必要があります。
また、 Kotlin Jacksonモジュール が存在します。これは、 "-parameters"コンパイラフラグがなくても、Kotlinデータクラスに対してこれを行うことができます。したがって、そのようなソリューションがJava Jacksonには存在しないように見えるのは奇妙です。
これは、Kotlin Jacksonで利用可能な(すてきできれいな)ソリューションです(IMOは、カスタムモジュールを介してJava Jacksonでも利用可能になるはずです)。
val mapper = ObjectMapper()
.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES)
.registerModule(KotlinModule())
val person: Person = mapper.readValue(json, Person::class.Java)
mix-in annotationsを使用できます。クラスを変更することがオプションではない場合、これは素晴らしい選択肢です。静的に定義されたアノテーションを強化するために、ランタイム中にアノテーションを追加する一種のアスペクト指向の方法と考えることができます。
Person
クラスが次のように定義されていると仮定します。
public class Person {
private final String firstName;
private final String lastName;
private final int age;
public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
// Getters omitted
}
最初に、ミックスインアノテーション抽象クラスを定義します。
public abstract class PersonMixIn {
PersonMixIn(@JsonProperty("firstName") String firstName,
@JsonProperty("lastName") String lastName,
@JsonProperty("age") int age) {
}
}
次に、ObjectMapper
を構成して、定義済みのクラスをPOJOのミックスインとして使用します。
ObjectMapper mapper = new ObjectMapper();
mapper.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
mapper.addMixIn(Person.class, PersonMixIn.class);
JSONをデシリアライズします:
String json = "{firstName: \"Foo\", lastName: \"Bar\", age: 30}";
Person person = mapper.readValue(json, Person.class);
Jacksonは、問題を解決するためのモジュール jackson-modules-Java8 を提供しています。
次のようにObjectMapper
を構築する必要があります。
ObjectMapper mapper = new ObjectMapper()
.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES)
.registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES));
-parameters
をコンパイラー引数として追加する必要があります。
Mavenの例:
<plugin>
<groupId>org.Apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<!--somecode-->
<compilerArgument>-parameters</compilerArgument>
</configuration>
</plugin>
Gradleの場合:
compileJava {
options.compilerArgs << "-parameters"
}
デフォルトのコンストラクターがないため、jacksonまたはgsonは独自のインスタンスを作成する必要があります。カスタムのデシリアライズを提供して、そのようなインスタンスを作成する方法をAPIに伝える必要があります。
ここにスニペットコード
public class PersonDeserializer extends StdDeserializer<Person> {
public PersonDeserializer() {
super(Person.class);
}
@Override
public Person deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
try {
final JsonNode node = jp.getCodec().readTree(jp);
final ObjectMapper mapper = new ObjectMapper();
final Person person = (Person) mapper.readValue(node.toString(),
Person.class);
return person;
} catch (final Exception e) {
throw new IOException(e);
}
}
}
次に、タイプを処理するための単純なモジュールを登録します
final ObjectMapper mapper = jacksonBuilder().build();
SimpleModule module = new SimpleModule();
module.addDeserializer(Person.class, new PersonDeserializer());