Kora фреймворк для написания Java / Kotlin приложений с упором на производительность, эффективность, прозрачность сделанный разработчиками Т-Банк / Тинькофф

Kora is a framework for writing Java / Kotlin applications with a focus on performance, efficiency, transparency made by T-Bank / Tinkoff developers

Перейти к содержанию

Json

Модуль Json позволяет создавать производительные и без использования рефлексии читатели и писатели для классов приложения посредствам разметки классов аннотациями.

Подключение

Зависимость build.gradle:

implementation "ru.tinkoff.kora:json-module"

Модуль:

@KoraApp
public interface Application extends JsonModule { }

Зависимость build.gradle.kts:

implementation("ru.tinkoff.kora:json-module")

Модуль:

@KoraApp
interface Application : JsonModule

Запись

Можно воспользоваться @JsonWriter для создания только писателя:

@JsonWriter
public record Dto(String field1, int field2) { }
@JsonWriter
data class Dto(val field1: String, val field2: Int)

Чтение

Можно воспользоваться @JsonReader для создания только читателя:

@JsonReader
public record Dto(String field1, int field2) { }
@JsonReader
data class Dto(val field1: String, val field2: Int)

Чтение & Запись

Можно воспользоваться @Json для создания сразу читателя и писателя. В большинстве случаев предпочтительнее использовать именно аннотацию @Json:

@Json
public record Dto(String field1, int field2) { }
@Json
data class Dto(val field1: String, val field2: Int)

Обязательные поля

По умолчанию все поля объявленные в объекте считаются обязательными (NotNull).

@Json
public record Dto(String field1, int field2) { }

По умолчанию все поля объявленные в объекте которые не используют Kotlin Nullability синтаксис считаются обязательными (NotNull).

@Json
data class Dto(val field1: String, val field2: Int)

Необязательное поля

В случае если поле в Json является необязательным, то есть может отсутствовать то, можно использовать аннотацию @Nullable для соответствия поля в Json и DTO:

@Json
public record Dto(@Nullable String field1, //(1)!
                  int field2) { }
  1. Подойдет любая аннотация @Nullable, такие как javax.annotation.Nullable / jakarta.annotation.Nullable / org.jetbrains.annotations.Nullable / и т.д.

Предполагается использовать Kotlin Nullability синтаксис и помечать такой параметр как Nullable:

@Json
data class Dto(
    val field1: String?,
    val field2: Int
)

Именование поля

В случае если поле в Json называется иначе от того что требуется использовать в классе, можно использовать аннотацию @JsonField для соответствия поля в Json и DTO.

@Json
public record Dto(@JsonField("field_1") String field1, 
                  int field2) { }
@Json
data class Dto(
    @field:JsonField("field_1") val field1: String,
    val field2: Int
)

Игнорирование поля

В случае если поле в DTO не хочется читать/писать, можно использовать аннотацию @JsonSkip и проигнорировать такое поле.

@Json
public record Dto(String field1, 
                  @JsonSkip int field2) { }
@Json
data class Dto(
    val field1: String,
    @field:JsonSkip val field2: Int
)

Уровни записи

Поведение по умолчанию не подразумевает запись полей с null значениями. (1)

  1. IncludeType.NON_NULL - включать поле в запись если не null

В случае если хочется изменить поведение записи в этих моментах то предлагается использовать аннотацию @JsonInclude. Аннотацию можно использовать не только над полем, но также над классом и тогда правило будет действовать на все поля сразу.

Доступны различные варианты использования:

  • IncludeType.ALWAYS - включать поле в запись всегда
  • IncludeType.NON_NULL - включать поле в запись если не null
  • IncludeType.NON_EMPTY - включать поле в запись если это не null и не пустая коллекция

Пример использования аннотации:

@Json
@JsonInclude(IncludeType.NOT_NULL)
public record Dto(@JsonInclude(IncludeType.ALWAYS) @Nullable String field1, 
                  int field2) { }
@Json
data class Dto(
    @field:JsonInclude(IncludeType.ALWAYS) val field1: String?,
    val field2: Int
)

Указание конструктора

В случае если хочется использовать определенный конструктор для сериализации, то это можно сделать с указанием над конструктором аннотации @JsonReader либо аннотации которая имеет меньший приоритет @Json:

@Json
public record Dto(String field1, int field2) {

    @JsonReader
    public Dto(String field1) {
        this(field1, 0);
    }
}
@Json
data class Dto(val field1: String, val field2: Int) {

    @JsonReader
    constructor(field1: String) : this(field1, 0)
}

JsonNullable обертка

В случае если во время десериализации, хочется отличать отсутствующее поле от указанного null значения, предполагается использовать специальный тип JsonNullable, который позволяет отражать все состояния поля после десериализации.

@Json
public record Dto(String field1, JsonNullable<Integer> field2) { }
@Json
data class Dto(val field1: String, val field2: JsonNullable<Int>)

Изолированные классы и интерфейсы

В случае если требуется писать различные Json объекты в зависимости от значения в конкретном поле, предполагается использовать изолированный класс/интерфейс для представления таких объектов.

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

  1. @JsonDiscriminatorField - указывает поле дискриминатора в DTO, которым помечается sealed класс/интерфейс
  2. @JsonDiscriminatorValue - значение для вышеуказанного поля, помечает класс-наследник sealed класса/интерфейса
@Json
@JsonDiscriminatorField("type")
public sealed interface Event {

    @JsonDiscriminatorValue("firstType")
    record FirstTypeEvent(String id, FirstData data) implements Event {}

    @JsonDiscriminatorValue("secondType")
    record SecondTypeEvent(String id, SecondData data) implements Event {}

    @JsonDiscriminatorValue("thirdType")
    record ThirdTypeEvent(String id, ThirdData data) implements Event {}
}
@Json
@JsonDiscriminatorField("type")
sealed interface Event {

    @JsonDiscriminatorValue("firstType")
    data class FirstTypeEvent(val id: String, data: FirstData) : Event

    @JsonDiscriminatorValue("secondType")
    data class SecondTypeEvent(val id: String, data: SecondData) : Event

    @JsonDiscriminatorValue("thirdType")
    data class ThirdTypeEvent(val id: String, data: ThirdData) : Event
}

Для классов-наследников будут созданы JsonReader и JsonWriter по тем же правилам, как если бы на них была аннотация @Json и создастся JsonReader и JsonWriter для самого sealed класса/интерфейса.

Json объект ниже будет записан в класс FirstTypeEvent:

{
    "id": "1",
    "type": "firstType",
    "data": {
        "megaData": "megaValue"
    }
}

Поддерживаемые типы

Модуль предоставляет обширный список поддерживаемых из коробки типов которые покрывают большую часть того что может понадобиться.

Список поддерживаемых типов
  • Boolean
  • boolean
  • Short
  • short
  • Integer
  • int
  • Long
  • long
  • Double
  • double
  • Float
  • float
  • byte[]
  • String
  • UUID
  • BigInteger
  • BigDecimal
  • List
  • Set
  • LocalDate
  • LocalTime
  • LocalDateTime
  • OffsetTime
  • OffsetDateTime
  • ZonedDateTime
  • Year
  • YearMonth
  • MonthDay
  • Month
  • DayOfWeek
  • ZoneId
  • Duration

Собственные типы

В случае если требуется писать/читать собственный тип, то предлагается зарегистрировать собственную фабрику для JsonReader / JsonWriter:

Пример регистрации собственно JsonWriter:

@KoraApp
public interface Application {

    default JsonWriter<ZoneOffset> zoneOffsetJsonWriter() {
        return (generator, value) -> {
            if(value != null) {
                generator.writeString(value.getId());
            }
        };
    }
}
@KoraApp
interface Application {

    fun zoneOffsetJsonWriter(): JsonWriter<ZoneOffset> {
        return JsonWriter { generator, value ->
            if (value != null) {
                generator.writeString(value.id)
            }
        }
    }
}

Jackson

В случае если хочется использовать Jackson для записи/чтения, то можно самому зарегистрировать фабрику предоставляющую ObjectMapper и соответствующие Mappers которые требуются в других Kora модулях будут предоставлены зависимостью ниже:

Зависимость build.gradle:

annotationProcessor "ru.tinkoff.kora:json-annotation-processor"
implementation "ru.tinkoff.kora:jackson-module"

Модуль:

@KoraApp
public interface Application extends JacksonModule { }

Зависимость build.gradle.kts:

ksp("ru.tinkoff.kora:json-annotation-processor")
implementation("ru.tinkoff.kora:jackson-module")

Модуль:

@KoraApp
interface Application : JacksonModule