Неизменяемый ломбокский аннотированный класс с Джексоном

Каков предпочтительный способ создания класса, который

  • Неизменный
  • Может быть сериализован/десериализован с помощью Jackson
  • Человекочитаемый и с низким уровнем шаблонности

Предпочтительно, мне бы хотелось, чтобы что-то вроде этого работало:

@Data(onConstructor = @__(@JsonCreator))

а затем все поля должны быть private final. Однако это даже не компилируется (и я не уверен, почему). С использованием

@AllArgsConstructor(onConstructor = @__(@JsonCreator))

будет компилироваться, но только дает

InvalidDefinitionException: No serializer found for class

person Christian Neverdal    schedule 24.04.2018    source источник


Ответы (9)


добавить Свойства конструктора:

  • Создайте файл lombok.config в соответствующем месте со строкой: lombok.anyConstructor.addConstructorProperties = true
  • Добавьте аннотацию lombok @Value в свой класс, чтобы сделать его неизменным.

Затем сериализация и десериализация Джексоном работают, как и ожидалось.

Этот способ:

  • соответствует критериям
  • имеет меньше места, чем предыдущий главный ответ
  • работает в v1.16.20 (9 января 2018 г.) и более поздних версиях.

Редактировать: 16 августа 2020 г.

  • Примечание. Использование @Builder с @Value приводит к сбою этого решения. (Спасибо за комментарий от @guilherme-blanco ниже.) Однако, если вы также добавите, например. @AllArgsConstructor он по-прежнему работает, как ожидалось.
person robSE13    schedule 29.11.2018
comment
ВНИМАНИЕ: этот подход не работает в сочетании с @Builder. - person Guilherme Blanco; 12.05.2020

Вы можете использовать аннотацию Lombok @Builder для создания построителя для вашего неизменного класса POJO. Но сделать билдер, сгенерированный Lombok, пригодным для использования с помощью десериализации Джексона, довольно сложно.

  • Вам необходимо аннотировать свой класс POJO с помощью @JsonDeserialize(builder = ...) чтобы сообщить Джексону, какой класс строителя использовать.
  • Вам необходимо аннотировать класс построителя с помощью @JsonPOJOBuilder(withPrefix = ""), чтобы сообщить Джексону, что его методы установки не начинаются с with.

Пример:

Неизменяемый класс POJO:

@Data
@Builder(builderClassName = "PointBuilder")
@JsonDeserialize(builder = Point.PointBuilder.class)
public class Point {

    private final int x;

    private final int y;

    @JsonPOJOBuilder(withPrefix = "")
    public static class PointBuilder {
        // Lombok will add constructor, setters, build method
    }
}

Контур POJO

Вот тест JUnit для проверки сериализации/десериализации:

public class PointTest extends Assert {

    private ObjectMapper objectMapper = new ObjectMapper();

    @Test
    public void testSerialize() throws IOException {
        Point point = new Point(10, 20);
        String json = objectMapper.writeValueAsString(point);
        assertEquals("{\"x\":10,\"y\":20}", json);
    }

    @Test
    public void testDeserialize() throws IOException {
        String json = "{\"x\":10,\"y\":20}";
        Point point = objectMapper.readValue(json, Point.class);
        assertEquals(new Point(10, 20), point);
    }
}
person Thomas Fritsch    schedule 25.04.2018
comment
Я уже давно использую этот подход, и он очень, очень эффективен. Одно очень небольшое улучшение заключается в том, что я всегда называю свои классы строителей _Builder. Таким образом, когда я копирую и вставляю для создания нового класса и т. д., мне не нужно помнить об изменении строки в builderClassName. Раньше я называл классы Builder (без подчеркивания), но это имеет странные конфликты с классом аннотаций @Builder, когда вы делаете это с внутренними статическими классами. - person Kevin Day; 10.05.2018

По состоянию на 15 октября 2020 г. (Lombok v1.18.16) вы должны просто иметь возможность использовать аннотацию @Jacksonized.

@Jacksonized @Builder
@JsonIgnoreProperties(ignoreUnknown = true)
public class JacksonExample {
  private List<Foo> foos;
}

Как описано в связанной документации, эта аннотация:

  • настраивает Джексона на использование построителя для десериализации,
  • копирует специфичную для поля конфигурацию из аннотированного класса в сгенерированный построитель (например, @JsonIgnoreProperties) и
  • выравнивает префикс Джексона, используемый в методах построения (например, builder().withField(field) против builder.field(field), с тем, который настроен в Lombok.
person Reinstate Monica 2331977    schedule 12.11.2020
comment
Можем ли мы поставить этот ответ на первое место? Работает как часы :) - person Pawel Dobierski; 29.01.2021

Ссылаясь на ответ Джозефа К. Штрауса, я нашел следующее решение.

Простые аннотации ломбока, которые у меня сработали, выглядят так. Следующие аннотации дают вам неизменяемые классы с построителем, которые могут быть сериализованы и десериализованы Джексоном.

    @Data
    @Setter(AccessLevel.NONE)
    @Builder(toBuilder = true)
    @AllArgsConstructor
    @NoArgsConstructor(access = AccessLevel.PRIVATE)
    public class Clazz {
        private String field;
    } 

Я предпочитаю это решение, потому что оно не требует дополнительных специальных аннотаций Джексона и дополнительных специфические файлы ломбока

person Streamline27    schedule 14.05.2019
comment
Спасибо за это. Но кажется: - person Breton F.; 11.12.2020

Другая альтернатива, которая гораздо менее подробна:

@Data
@Setter(AccessLevel.NONE)
public class Clazz {
    private String field;
} 

Конечно, у вас все еще может быть какой-то закрытый метод, который напрямую изменяет поле, но очень маловероятно, что в @Data POJO будет даже какой-то реальный код, так что, надеюсь, этого не произойдет.

Отказ от ответственности: это будет иметь побочный эффект (возможно, полезный), не позволяя обычному коду Java создавать объект, поскольку существует только конструктор по умолчанию без мутаторов. Для нормального построения вам понадобятся еще 2 аннотации:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Setter(AccessLevel.NONE)
public class Clazz {
    private String field;
} 
person Joseph K. Strauss    schedule 06.11.2018

Попробуйте следующий набор аннотаций для вашего pojo:

@Value
@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
@AllArgsConstructor
person Zon    schedule 24.06.2020

Самый простой способ настроить неизменяемый класс для Джексона — использовать аннотацию ломбока: @Value и @Jacksonized:

    @Jacksonized
    @Builder
    @Value
    class Foo {

        
    }
person MariuszS    schedule 25.11.2020

Я только что решил это, используя этот способ:

@Value
@Builder(setterPrefix = "with")
@JsonDeserialize(builder = Clazz.ClazzBuilder.class)
public class Clazz {
    private String field;
}

В этом случае вы должны использовать методы построения, такие как withField(...), что является поведением по умолчанию, используемым jackson.

person Pablo    schedule 20.09.2020

Ответ Томаса Фрича отлично работал с Spring Boot после добавления зависимости Jackson Dataformat в pom.

@Data
@Builder(builderClassName = "PointBuilder")
@JsonDeserialize(builder = Point.PointBuilder.class)
public class Point {

    private final int x;

    private final int y;

    @JsonPOJOBuilder(withPrefix = "")
    public static class PointBuilder {
        // Lombok will add constructor, setters, build method
    }
}

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-xml -->
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>
person Ajay Mathur    schedule 13.02.2020