[java] JsonCreator를 사용하여 오버로드 된 생성자로 클래스를 역 직렬화하는 방법

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을 사용하여 오버로드 된 생성자로 클래스를 역 직렬화하는 방법이 있습니까?

감사



답변

제대로 문서화되어 있지는 않지만 유형 당 한 명의 작성자 만 가질 수 있습니다. 유형에 원하는만큼 생성자를 가질 수 있지만 그 중 하나에 만 @JsonCreator주석이 있어야 합니다.


답변

편집 : Jackson의 관리자가 작성한 블로그 게시물 에서 2.12는 생성자 주입과 관련하여 개선 사항을 볼 수 있습니다. (이 편집 당시의 현재 버전은 2.11.1입니다.)

모호한 1- 인수 생성자 (위임 대 속성)로 문제 해결 / 완화를 포함하여 생성자 생성자의 자동 감지 기능을 개선합니다.


이것은 Jackson databind 2.7.0의 경우에도 마찬가지입니다.

잭슨 @JsonCreator주석 2.5의 javadoc 이나 잭슨 주석 문서의 문법 ( 생성자 공장 방법 ) 사람이 할 수있는 참으로 믿게 표시 여러 생성자를.

생성자 및 팩토리 메서드를 연결된 클래스의 새 인스턴스를 인스턴스화하는 데 사용할 하나로 정의하는 데 사용할 수있는 마커 주석입니다.

생성자 가 식별 되는 코드를 보면 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");

이 시간 잭슨은 오류가 발생하지 않지만 잭슨에만 사용 위임 생성자 Phone(Map<String, Object> properties)는 그 수단 Phone(@JsonProperty("value") String value)사용되지 않습니다를.


답변

달성하려는 것을 올바르게 얻었다면 생성자 오버로드없이 해결할 수 있습니다 .

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 솔루션 이 나를 위해 작동하지 않았습니다.


답변

조금 더 작업해도 괜찮다면 엔터티를 수동으로 역 직렬화 할 수 있습니다.

@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");
            }
        }
    }
}


답변