Как десериализовать класс с перегруженными конструкторами с помощью JsonCreator


83

Я пытаюсь десериализовать экземпляр этого класса с помощью 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 ()}]

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

благодаря


4
Как указано в ответе, нет, вы должны указать один-единственный конструктор. В вашем случае оставьте тот, который принимает несколько аргументов, он будет работать нормально. «Отсутствующие» аргументы будут иметь значение null (для объектов) или значение по умолчанию (для примитивов).
— StaxMan

Благодарю. Однако было бы неплохо разрешить использование нескольких конструкторов. На самом деле мой пример немного надуманный. У объекта, который я пытаюсь использовать, на самом деле есть совершенно разные списки аргументов, один создается нормально, другой создается с помощью Throwable ... Я посмотрю, что я могу сделать, возможно, имея пустой конструктор и получатель / установщик для Throwable
— geejay

Да, я уверен, что это было бы хорошо, но правила могут стать довольно сложными с различными перестановками. Всегда можно подать RFE для новых функций, возможностей.
— StaxMan

Ответы:


119

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


72

РЕДАКТИРОВАТЬ : вот, в сообщении в блоге разработчиков Джексона, кажется, что 2.12 может увидеть улучшения в отношении внедрения конструктора. (Текущая версия на момент редактирования - 2.11.1)

Улучшение автоматического обнаружения создателей конструкторов, включая решение / смягчение проблем с неоднозначными конструкторами с одним аргументом (делегирование по сравнению со свойствами)


Это все еще верно для базы данных Джексона 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)никогда не используется.


8
ИМХО, это должен быть принятый ответ, потому что он дает полное объяснение с хорошим примером
— matiou

7

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

Если вы просто хотите поместить нулевые значения в атрибуты, которых нет в JSON или Map, вы можете сделать следующее:

@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
Лучший ответ, имхо
— Якоб

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");
            }
        }
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.