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

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

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

Желательно, чтобы работало что-то вроде этого:

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

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

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

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

InvalidDefinitionException: No serializer found for class
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
26
0
21 663
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

Вы можете использовать аннотацию 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 outline

Вот тест 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);
    }
}

Я уже некоторое время использую этот подход, и он очень и очень эффективен. Одно очень небольшое улучшение состоит в том, что я всегда называю свои классы построителей _Builder. Таким образом, когда я копирую вставку для создания нового класса и т. д., Мне не нужно помнить об изменении строки в builderClassName. Раньше я называл классы Builder (без подчеркивания), но это вызывает странные конфликты с классом аннотации @Builder, когда вы делаете это с внутренними статическими классами.

Kevin Day 10.05.2018 03:13

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

@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;
} 
Ответ принят как подходящий

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

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

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

Этот способ:

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

Обновлено: 2020-08-16

  • Примечание. Использование @Builder с @Value приводит к сбою этого решения. (Спасибо за комментарий от @ guilherme-blanco ниже.) Однако, если вы также добавите, например, @AllArgsConstructor все еще работает как положено.

ВНИМАНИЕ: этот подход не работает вместе с @Builder.

Guilherme Blanco 12.05.2020 17:21

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

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

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

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

Спасибо за это. Но кажется:

Breton F. 11.12.2020 17:34

Ответ Томаса Фрича отлично работал с 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>

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

@Value
@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
@AllArgsConstructor

Я только что решил это следующим образом:

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

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

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

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

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

  • настраивает Джексона на использование построителя для десериализации,
  • копирует специфичную для поля конфигурацию из аннотированного класса в сгенерированный построитель (например, @JsonIgnoreProperties) и
  • выравнивает префикс Джексона, используемый в методах компоновщика (например, builder().withField(field) vs builder.field(field), с префиксом, настроенным в Lombok.

Можем ли мы поставить этот ответ на первое место? Работает как часы :)

Pawel Dobierski 29.01.2021 02:08

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

    @Jacksonized
    @Builder
    @Value
    class Foo {

        
    }

Другие вопросы по теме