JsonCreatorを使用してオーバーロードされたコンストラクターでクラスを逆シリアル化する方法


83

Jackson1.9.10を使用してこのクラスのインスタンスを逆シリアル化しようとしています。

public class Person {

@JsonCreator
public Person(@JsonProperty("name") String name,
        @JsonProperty("age") int age) {
    // ... person with both name and age
}

@JsonCreator
public Person(@JsonProperty("name") String name) {
    // ... person with just a name
}
}

これを試してみると、次のようになります

競合するプロパティベースの作成者:すでに... {interface org.codehaus.jackson.annotate.JsonCreator @ org.codehaus.jackson.annotate.JsonCreator()}]、検出されました...、アノテーション:{interfaceorg.codehaus。 jackson.annotate.JsonCreator @ org.codehaus.jackson.annotate.JsonCreator()}]

Jacksonを使用してオーバーロードされたコンストラクターでクラスを逆シリアル化する方法はありますか?

ありがとう


4
答えが指摘しているように、いいえ、唯一のコンストラクターを指定する必要があります。あなたの場合、複数の引数を取るものを残してください、それはうまくいくでしょう。「欠落している」引数は、null(オブジェクトの場合)またはデフォルト値(プリミティブの場合)を取ります。
StaxMan 2013

ありがとう。ただし、複数のコンストラクターを許可することは素晴らしい機能です。実際、私の例は少し不自然です。私が使用しようとしているオブジェクトには、実際には完全に異なる引数リストがあります。1つは通常どおりに作成され、もう1つはThrowableで作成されます...何ができるかを確認します。 Throwable
geejay

ええ、それは素晴らしいことだと確信していますが、ルールはさまざまな順列でかなり複雑になる可能性があります。新しい機能、機能のためにRFEを提出することは常に可能です。
StaxMan 2013

回答:


119

適切に文書化されていませんが、タイプごとに作成者を1人だけ持つことができます。タイプには必要な数のコンストラクターを含めることができますが、@JsonCreatorアノテーションを付ける必要があるのはそのうちの1つだけです。


72

編集:見よ、ジャクソンのメンテナによるブログ投稿では、2.12はコンストラクタインジェクションに関して改善が見られるようです。(この編集時の現在のバージョンは2.11.1です)

あいまいな1引数コンストラクターの問題を解決/軽減するなど、コンストラクター作成者の自動検出を改善します(委任とプロパティ)


これは、Jackson databind2.7.0にも当てはまります。

ジャクソン@JsonCreator注釈2.5 javadocのか、ジャクソン注釈文書の文法(コンストラクタ工場法)1ができることを本当に信じて離すと自然にマークする複数のコンストラクタを。

コンストラクターとファクトリメソッドを、関連付けられたクラスの新しいインスタンスをインスタンス化するために使用するものとして定義するために使用できるマーカーアノテーション。

作成者が識別されるコードを見ると、JacksonCreatorCollectorコンストラクターの最初の引数のみをチェックするため、オーバーロードされたコンストラクターを無視しているように見えます。

Class<?> oldType = oldOne.getRawParameterType(0);
Class<?> newType = newOne.getRawParameterType(0);

if (oldType == newType) {
    throw new IllegalArgumentException("Conflicting "+TYPE_DESCS[typeIndex]
           +" creators: already had explicitly marked "+oldOne+", encountered "+newOne);
}
  • oldOne 最初に識別されたコンストラクター作成者です。
  • newOne オーバーロードされたコンストラクター作成者です。

つまり、そのようなコードは機能しません

@JsonCreator
public Phone(@JsonProperty("value") String value) {
    this.value = value;
    this.country = "";
}

@JsonCreator
public Phone(@JsonProperty("country") String country, @JsonProperty("value") String value) {
    this.value = value;
    this.country = country;
}

assertThat(new ObjectMapper().readValue("{\"value\":\"+336\"}", Phone.class).value).isEqualTo("+336"); // raise error here
assertThat(new ObjectMapper().readValue("{\"value\":\"+336\"}", Phone.class).value).isEqualTo("+336");

しかし、このコードは機能します:

@JsonCreator
public Phone(@JsonProperty("value") String value) {
    this.value = value;
    enabled = true;
}

@JsonCreator
public Phone(@JsonProperty("enabled") Boolean enabled, @JsonProperty("value") String value) {
    this.value = value;
    this.enabled = enabled;
}

assertThat(new ObjectMapper().readValue("{\"value\":\"+336\"}", Phone.class).value).isEqualTo("+336");
assertThat(new ObjectMapper().readValue("{\"value\":\"+336\",\"enabled\":true}", Phone.class).value).isEqualTo("+336");

これは少しハッキーであり、将来の証拠ではないかもしれません


ドキュメントは、オブジェクトの作成がどのように機能するかについてあいまいです。しかし、私がコードから収集したものから、さまざまな方法を混在させることが可能であるということです:

たとえば、静的ファクトリメソッドに注釈を付けることができます @JsonCreator

@JsonCreator
public Phone(@JsonProperty("value") String value) {
    this.value = value;
    enabled = true;
}

@JsonCreator
public Phone(@JsonProperty("enabled") Boolean enabled, @JsonProperty("value") String value) {
    this.value = value;
    this.enabled = enabled;
}

@JsonCreator
public static Phone toPhone(String value) {
    return new Phone(value);
}

assertThat(new ObjectMapper().readValue("\"+336\"", Phone.class).value).isEqualTo("+336");
assertThat(new ObjectMapper().readValue("{\"value\":\"+336\"}", Phone.class).value).isEqualTo("+336");
assertThat(new ObjectMapper().readValue("{\"value\":\"+336\",\"enabled\":true}", Phone.class).value).isEqualTo("+336");

それは機能しますが、理想的ではありません。結局、それは理にかなっているかもしれません。たとえば、JSONがそれほど動的である場合、複数の注釈付きコンストラクターを使用するよりもはるかにエレガントにペイロードのバリエーションを処理するためにデリゲートコンストラクターを使用することを検討する必要があります。

また、Jacksonは、たとえば次のコードで、作成者を優先度順に並べていることに注意してください。

// Simple
@JsonCreator
public Phone(@JsonProperty("value") String value) {
    this.value = value;
}

// more
@JsonCreator
public Phone(Map<String, Object> properties) {
    value = (String) properties.get("value");
    
    // more logic
}

assertThat(new ObjectMapper().readValue("\"+336\"", Phone.class).value).isEqualTo("+336");
assertThat(new ObjectMapper().readValue("{\"value\":\"+336\"}", Phone.class).value).isEqualTo("+336");
assertThat(new ObjectMapper().readValue("{\"value\":\"+336\",\"enabled\":true}", Phone.class).value).isEqualTo("+336");

今回、Jacksonはエラーを発生させませんが、Jacksonはデリゲートコンストラクターのみを使用します。Phone(Map<String, Object> properties)つまり、Phone(@JsonProperty("value") String value)は使用されません。


8
私見これは良い例で完全な説明を提供するので受け入れられた答えであるはずです
matiou 2017年

7

あなたが達成しようとしていることを正しく理解できれば、コンストラクターのオーバーロードなしでそれ解決できます。

JSONまたはマップに存在しない属性にnull値を入れたいだけの場合は、次のようにすることができます。

@JsonIgnoreProperties(ignoreUnknown = true)
public class Person {
    private String name;
    private Integer age;
    public static final Integer DEFAULT_AGE = 30;

    @JsonCreator
    public Person(
        @JsonProperty("name") String name,
        @JsonProperty("age") Integer age) 
        throws IllegalArgumentException {
        if(name == null)
            throw new IllegalArgumentException("Parameter name was not informed.");
        this.age = age == null ? DEFAULT_AGE : age;
        this.name = name;
    }
}

私があなたの質問を見つけたとき、それは私の場合でした。それを解決する方法を理解するのに少し時間がかかりました、多分それはあなたがやろうとしていたことです。 @Briceソリューションは私にはうまくいきませんでした。


1
ベストanwerの私見
ヤコブ

3

もう少し作業を行ってもかまわない場合は、エンティティを手動で逆シリアル化できます。

@JsonDeserialize(using = Person.Deserializer.class)
public class Person {

    public Person(@JsonProperty("name") String name,
            @JsonProperty("age") int age) {
        // ... person with both name and age
    }

    public Person(@JsonProperty("name") String name) {
        // ... person with just a name
    }

    public static class Deserializer extends StdDeserializer<Person> {
        public Deserializer() {
            this(null);
        }

        Deserializer(Class<?> vc) {
            super(vc);
        }

        @Override
        public Person deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
            JsonNode node = jp.getCodec().readTree(jp);
            if (node.has("name") && node.has("age")) {
                String name = node.get("name").asText();
                int age = node.get("age").asInt();
                return new Person(name, age);
            } else if (node.has("name")) {
                String name = node.get("name").asText();
                return new Person("name");
            } else {
                throw new RuntimeException("unable to parse");
            }
        }
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.