Десериализация Джексона в обход конечных полей

Вот код

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.ToString;

public class Main {
    public static void main(String[] args) throws Exception {
        Fields f1 = new Fields(1);
        System.out.println(f1);

        ObjectMapper mapper = new ObjectMapper();
        String str = mapper.writeValueAsString(f1);
        System.out.println(str);

        Fields f2 = mapper.readValue(str, Fields.class);
        System.out.println(f2);
    }

    @Data
    @ToString
    public static class Fields {
        private final long value1;
        private final long value2;

        public Fields(@JsonProperty("blah") long value) {
            this.value1 = value++;
            this.value2 = value++;
            System.out.println(this);
        }
    }
}

Выход

Main.Fields(value1=1, value2=2)
Main.Fields(value1=1, value2=2)
{"value1":1,"value2":2}
Main.Fields(value1=0, value2=1)
Main.Fields(value1=1, value2=2)

Мои вопросы:

  • Почему Джексон изменил закрытые конечные поля, у которых нет сеттеров, после завершения их создания? Если так и задумано, как отключить?
  • Если Джексон может устанавливать поля напрямую, почему требуется, чтобы я аннотировал конструктор с помощью @JsonProperty? (Удаление @JsonProperty из полей приводит к ошибке, и мне даже не нужно было комментировать правильные свойства)

Спасибо


person surasak    schedule 20.05.2015    source источник


Ответы (2)


Почему требуется, чтобы я аннотировал конструктор с помощью @JsonProperty?

Это не. Что требуется, так это доступный конструктор. Вы можете иметь конструктор без параметров

public Fields() {
    this.value1 = 0;
    this.value2 = 0;
    System.out.println("cons: " + this);
}

(который обязательно инициализирует поля, поскольку они final), или у вас может быть конструктор, который Джексон попытается разрешить на основе объявленного имени @JsonProperty. Обратите внимание, что JsonProperty#required по умолчанию равно false.

Почему Джексон изменил закрытые конечные поля, у которых нет сеттеров, после завершения их создания? Если так и задумано, как отключить?

Потому что может. Таким образом, это позволяет вам использовать неизменяемые типы с десериализацией. Я не знаю встроенного способа, с помощью которого вы можете отключить эту функцию.

person Sotirios Delimanolis    schedule 20.05.2015
comment
Потому что может. - Это заставило меня улыбнуться. - person Kenny Cason; 31.03.2016
comment
Но как или с помощью какого базового механизма ObjectMapper изменяет окончательные поля? Я знаю, что он использует отражение, но делает ли это поле неокончательным и устанавливает ли оно его? - person flow2k; 09.09.2017
comment
Конечно, если существует аннотированный конструктор, то Джексон не должен изменять поля? Должно быть и то, и другое. - person mjaggard; 05.01.2018
comment
@mjaggard Я не могу проверить это в данный момент, но я предполагаю, что Джексон пытается присвоить каждое свойство в JSON, будь то через конструктор или через мутаторы (сеттеры или поля). - person Sotirios Delimanolis; 05.01.2018

Почему Джексон изменил закрытые конечные поля, у которых нет сеттеров, после завершения их создания? Если так и задумано, как отключить?

Вы можете установить для свойства MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS значение false (по умолчанию true) при настройке вашего картографа.

Пример:

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Temp {

private static final String RAW = "{\"value1\": \"aabbcc\",\"value2\":\"zzzzz\"}";

public static void main(String[] args) throws Exception {
    System.out.println(new ObjectMapper().readValue(RAW, TestClass.class));

    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // you will receive UnrecognizedPropertyException without this line
    mapper.configure(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS, false);

    System.out.println(mapper.readValue(RAW, TestClass.class));
}

public static class TestClass {

    private final String value1;
    private final String value2;

    @JsonCreator
    public static TestClass createTestClass(
            @JsonProperty("value1") String value1,
            @JsonProperty("blah") String value2) {

        return new TestClass(value1, value2);
    }

    private TestClass(String value1, String value2) {
        this.value1 = value1;
        this.value2 = value2;
    }

    public String getValue1() {
        return value1;
    }

    public String getValue2() {
        return value2;
    }

    @Override
    public String toString() {
        return "TestClass{" + "value1=" + value1 + ", value2=" + value2 + '}';
    }
}

}

Выход:

TestClass{value1=aabbcc, value2=zzzzz}
TestClass{value1=aabbcc, value2=null}
person Oleksandr Matiash    schedule 09.02.2017