Как десериализовать класс с перегруженными конструкторами с помощью 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
}
}

когда я пытаюсь это я получаю следующее

конфликтующие создатели на основе свойств :уже было... {организация интерфейса.codehaus.Джексон.комментировать.JsonCreator @org.codehaus.Джексон.комментировать.JsonCreator()}], встречаются ... , аннотации: {interface org.codehaus.Джексон.комментировать.JsonCreator @org.codehaus.Джексон.комментировать.JsonCreator ()}]

там способ десериализации класса с перегруженными конструкторами с помощью Jackson?

спасибо

4 57

4 ответа:

хотя он не задокументирован должным образом, у вас может быть только один создатель для каждого типа. Вы можете иметь столько конструкторов, сколько хотите в своем типе, но только один из них должен иметь @JsonCreator аннотация на нем.

это по-прежнему верно для Jackson databind 2.7.0.

Джексон @JsonCreator аннотация 2.5 javadoc или Джексон аннотации документация грамматика (конструкторs иs) пусть верят воистину, что можно Марк несколько конструкторов.

маркер аннотация, которая может быть использована для определения конструкторов и заводских методов как один для использования создание новых экземпляров связанного класса.

глядя на код, где создатели идентифицированы, похоже на Джексона 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-это динамический тогда, возможно, следует использовать конструктор делегата для обработки вариаций полезной нагрузки гораздо более элегантно, чем с несколькими аннотированными конструкторами.

также обратите внимание, что Джексон заказывает создателей по приоритету, например в этом коде :

// 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 или карте, вы можете сделать следующее:

@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 solution не работает для меня.

Если вы не возражаете делать немного больше работы, вы можете десериализовать объект вручную:

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