Cách deserialize một lớp có các hàm tạo bị quá tải bằng JsonCreator


82

Tôi đang cố gắng giải mã hóa một phiên bản của lớp này bằng cách sử dụng 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
}
}

Khi tôi thử điều này, tôi nhận được những thứ sau

Người tạo dựa trên thuộc tính xung đột: đã có ... {interface org.codehaus.jackson.annotate.JsonCreator @ org.codehaus.jackson.annotate.JsonCreator ()}], đã gặp ..., chú thích: {interface org.codehaus. jackson.annotate.JsonCreator @ org.codehaus.jackson.annotate.JsonCreator ()}]

Có cách nào để giải mã hóa một lớp có các hàm tạo quá tải bằng cách sử dụng Jackson không?

Cảm ơn


4
Như câu trả lời đã chỉ ra, không, bạn phải chỉ định một và duy nhất hàm tạo. Trong trường hợp của bạn, hãy để lại cái có nhiều đối số, cách đó sẽ hoạt động tốt. Các đối số "thiếu" sẽ nhận giá trị null (đối với Đối tượng) hoặc giá trị mặc định (đối với nguyên thủy).
StaxMan

Cảm ơn. Cho phép nhiều hàm tạo sẽ là một tính năng hay. Trên thực tế, ví dụ của tôi là một chút giả tạo. Đối tượng mà tôi đang cố gắng sử dụng thực sự có danh sách đối số hoàn toàn khác nhau, một đối số được tạo bình thường, đối tượng còn lại được tạo bằng Throwable ... Tôi sẽ xem tôi có thể làm gì, có thể có một hàm tạo trống và getter / setter cho Throwable
geejay

Vâng, tôi chắc rằng nó sẽ tốt, nhưng các quy tắc có thể trở nên khá phức tạp với các hoán vị khác nhau. Luôn có thể gửi RFE cho các chức năng, tính năng mới.
StaxMan

Câu trả lời:


118

Mặc dù nó không được ghi chép chính xác, bạn chỉ có thể có một người tạo cho mỗi loại. Bạn có thể có bao nhiêu hàm tạo tùy thích trong kiểu của mình, nhưng chỉ một trong số chúng nên có @JsonCreatorchú thích trên đó.


69

CHỈNH SỬA: Kìa, trong một bài đăng trên blog của những người bảo trì Jackson, có vẻ như 2.12 có thể thấy những cải tiến liên quan đến việc tiêm hàm tạo. (Phiên bản hiện tại tại thời điểm chỉnh sửa này là 2.11.1)

Cải thiện khả năng tự động phát hiện các trình tạo Hàm tạo, bao gồm giải quyết / giảm bớt các vấn đề với các trình tạo 1 đối số không rõ ràng (ủy quyền so với thuộc tính)


Điều này vẫn đúng với Jackson databind 2.7.0.

The Jackson @JsonCreatorchú thích 2,5 javadoc hoặc Jackson chú thích tài liệu ngữ pháp ( constructor s và nhà máy phương pháp s ) Hãy tin rằng thực sự mà người ta có thể đánh dấu nhiều nhà xây dựng.

Chú thích điểm đánh dấu có thể được sử dụng để xác định các phương thức khởi tạo và phương thức gốc để sử dụng cho việc khởi tạo các phiên bản mới của lớp được liên kết.

Nhìn vào đoạn mã nơi các hàm tạo được xác định, có vẻ như Jackson CreatorCollectorđang bỏ qua các hàm tạo bị quá tải vì nó chỉ kiểm tra đối số đầu tiên của hàm tạo .

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 là trình tạo phương thức khởi tạo được xác định đầu tiên.
  • newOne là trình tạo hàm tạo quá tải.

Điều đó có nghĩa là mã như vậy sẽ không hoạt động

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

Nhưng mã này sẽ hoạt động:

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

Đây là một chút hacky và có thể không phải là bằng chứng trong tương lai .


Tài liệu mơ hồ về cách tạo đối tượng hoạt động; Tuy nhiên, từ những gì tôi thu thập được từ mã, có thể kết hợp các phương pháp khác nhau:

Ví dụ, người ta có thể có một phương thức nhà máy tĩnh được chú thích bằng @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");

Nó hoạt động nhưng nó không phải là lý tưởng. Cuối cùng, nó có thể có ý nghĩa, ví dụ: nếu json là động đó thì có lẽ người ta nên sử dụng một hàm tạo ủy nhiệm để xử lý các biến thể trọng tải một cách thanh lịch hơn nhiều so với nhiều hàm tạo chú thích.

Cũng lưu ý rằng Jackson sắp xếp thứ tự ưu tiên cho người sáng tạo , chẳng hạn như trong mã này:

// 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");

Lần này Jackson sẽ không tăng một lỗi, nhưng Jackson sẽ chỉ sử dụng các đại biểu constructor Phone(Map<String, Object> properties), điều đó có nghĩa là Phone(@JsonProperty("value") String value)không bao giờ được sử dụng.


7
IMHO này nên là câu trả lời được chấp nhận vì nó cung cấp giải thích đầy đủ với ví dụ đẹp
matiou

7

Nếu tôi hiểu đúng những gì bạn đang cố gắng đạt được, bạn có thể giải quyết nó mà không bị quá tải hàm tạo .

Nếu bạn chỉ muốn đặt giá trị null trong các thuộc tính không có trong JSON hoặc Bản đồ, bạn có thể làm như sau:

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

Đó là trường hợp của tôi khi tôi tìm thấy câu hỏi của bạn. Tôi đã mất một thời gian để tìm ra cách giải quyết nó, có lẽ đó là những gì bạn đang cố gắng làm. @Giải pháp giá không hoạt động với tôi.


1
Best anwer imho
Jakob

3

Nếu bạn không ngại làm thêm một chút công việc, bạn có thể giải mã hóa thực thể theo cách thủ công:

@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");
            }
        }
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.