Gymterview
middle

Как кастомизировать сериализацию в Jackson?

Jackson предоставляет несколько механизмов кастомизации: пользовательские сериализаторы/десериализаторы, аннотации для управления созданием объектов и mix-ins для работы с классами, которые нельзя модифицировать.

Custom Serializer / Deserializer

Наследуются от StdSerializer / StdDeserializer и применяются через аннотацию @JsonSerialize / @JsonDeserialize.

Пример кастомного сериализатора и десериализатора
// Сериализатор: Money -> "100.50 RUB"
public class MoneySerializer extends StdSerializer<Money> {
    public MoneySerializer() { super(Money.class); }

    @Override
    public void serialize(Money value, JsonGenerator gen,
                          SerializerProvider provider) throws IOException {
        gen.writeString(value.getAmount().toPlainString() + " " + value.getCurrency());
    }
}

// Десериализатор: "100.50 RUB" -> Money
public class MoneyDeserializer extends StdDeserializer<Money> {
    public MoneyDeserializer() { super(Money.class); }

    @Override
    public Money deserialize(JsonParser p, DeserializationContext ctxt)
            throws IOException {
        String text = p.getText(); // "100.50 RUB"
        String[] parts = text.split(" ");
        return new Money(new BigDecimal(parts[0]), parts[1]);
    }
}

// Применение
public class Order {
    @JsonSerialize(using = MoneySerializer.class)
    @JsonDeserialize(using = MoneyDeserializer.class)
    private Money totalPrice;
}

Аннотации для управления созданием объектов

Аннотация Назначение
@JsonCreator Десериализация через конструктор/фабричный метод (для иммутабельных объектов)
@JsonValue Объект сериализуется как одно значение (часто для enum)
@JsonUnwrapped Развёртывание вложенного объекта на уровень выше
Примеры: JsonCreator, JsonValue, JsonUnwrapped
// @JsonCreator — иммутабельный объект без конструктора по умолчанию
public class Address {
    private final String city;
    private final String street;

    @JsonCreator
    public Address(
            @JsonProperty("city") String city,
            @JsonProperty("street") String street) {
        this.city = city;
        this.street = street;
    }
    // Только getters
}

// @JsonValue — enum сериализуется как строка
public enum Status {
    ACTIVE("active"), INACTIVE("inactive");
    private final String code;
    Status(String code) { this.code = code; }

    @JsonValue
    public String getCode() { return code; }

    @JsonCreator
    public static Status fromCode(String code) {
        return Arrays.stream(values())
                .filter(s -> s.code.equals(code))
                .findFirst().orElseThrow();
    }
}

// @JsonUnwrapped — развёртывание вложенного объекта
public class Person {
    private String name;

    @JsonUnwrapped
    private Address address;
    // Вместо {"name":"Иван","address":{"city":"Москва"}}
    // Получим: {"name":"Иван","city":"Москва"}
}

Mix-ins для сторонних классов

Позволяют добавить аннотации Jackson к классам, исходный код которых нельзя изменить.

Пример
// Mix-in — абстрактный класс с аннотациями
public abstract class ThirdPartyUserMixin {
    @JsonProperty("username")
    abstract String getName();

    @JsonIgnore
    abstract String getSecret();
}

// Регистрация
mapper.addMixIn(ThirdPartyUser.class, ThirdPartyUserMixin.class);

Частые ошибки

  • Забытая @JsonProperty в параметрах @JsonCreator — Jackson не знает, какой JSON-параметр передать в какой аргумент
  • @JsonUnwrapped не работает с коллекциями и Map — только с POJO
  • Конфликт @JsonValue с другими аннотациями — @JsonValue определяет единственное представление объекта

На собеседовании: назовите StdSerializer/StdDeserializer, @JsonCreator для иммутабельных объектов и mix-ins для сторонних классов. Java record-классы (16+) поддерживаются Jackson из коробки, что делает @JsonCreator менее нужным для простых DTO.