91

Jackson 1.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()}]、遭遇した ... 、注釈: {interface org.codehaus. jackson.annotate.JsonCreator @org.codehaus.jackson.annotate.JsonCreator()}]

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

ありがとう

4

4 に答える 4

124

適切に文書化されていませんが、タイプごとにクリエーターを 1 つだけ持つことができます。型には必要な数のコンストラクターを含めることができますが、そのうちの 1 つだけに@JsonCreator注釈を付ける必要があります。

于 2013-04-10T16:32:17.883 に答える
74

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

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


これは、Jackson データバインド 2.7.0 でも当てはまります。

Jackson@JsonCreatorアノテーション 2.5 javadocまたはJackson アノテーション ドキュメント文法 (コンストラクターとファクトリー メソッド) は、複数のコンストラクターマークできると実際に信じさせます。

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

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

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)は使用されません。

于 2016-07-01T14:32:49.420 に答える
7

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

JSON または Map に存在しない属性に 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 ソリューションはうまくいきませんでした。

于 2018-04-05T23:53:11.807 に答える
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");
            }
        }
    }
}
于 2018-03-29T02:13:30.893 に答える