Модуль Json позволяет создавать производительные и без использования рефлексии читатели и писатели для классов приложения посредствам разметки классов аннотациями.
===! ":fontawesome-brands-java: Java"
[Зависимость](general.md#_4) `build.gradle`:
```groovy
implementation "ru.tinkoff.kora:json-module"
```
Модуль:
```java
@KoraApp
public interface Application extends JsonModule { }
```
=== ":simple-kotlin: Kotlin"
[Зависимость](general.md#_4) `build.gradle.kts`:
```groovy
implementation("ru.tinkoff.kora:json-module")
```
Модуль:
```kotlin
@KoraApp
interface Application : JsonModule
```
Можно воспользоваться @JsonWriter для создания только писателя:
===! ":fontawesome-brands-java: Java"
```java
@JsonWriter
public record Dto(String field1, int field2) { }
```
=== ":simple-kotlin: Kotlin"
```kotlin
@JsonWriter
data class Dto(val field1: String, val field2: Int)
```
Можно воспользоваться @JsonReader для создания только читателя:
===! ":fontawesome-brands-java: Java"
```java
@JsonReader
public record Dto(String field1, int field2) { }
```
=== ":simple-kotlin: Kotlin"
```kotlin
@JsonReader
data class Dto(val field1: String, val field2: Int)
```
Можно воспользоваться @Json для создания сразу читателя и писателя.
В большинстве случаев предпочтительнее использовать именно аннотацию @Json:
===! ":fontawesome-brands-java: Java"
```java
@Json
public record Dto(String field1, int field2) { }
```
=== ":simple-kotlin: Kotlin"
```kotlin
@Json
data class Dto(val field1: String, val field2: Int)
```
===! ":fontawesome-brands-java: Java"
По умолчанию все поля объявленные в объекте считаются **обязательными** (*NotNull*).
```java
@Json
public record Dto(String field1, int field2) { }
```
=== ":simple-kotlin: Kotlin"
По умолчанию все поля объявленные в объекте которые не используют [Kotlin Nullability](https://kotlinlang.ru/docs/null-safety.html) синтаксис считаются **обязательными** (*NotNull*).
```kotlin
@Json
data class Dto(val field1: String, val field2: Int)
```
===! ":fontawesome-brands-java: Java"
В случае если поле в Json является необязательным, то есть может отсутствовать то,
можно использовать аннотацию `@Nullable` для соответствия поля в Json и DTO:
```java
@Json
public record Dto(@Nullable String field1, //(1)!
int field2) { }
```
1. Подойдет любая аннотация `@Nullable`, такие как `javax.annotation.Nullable` / `jakarta.annotation.Nullable` / `org.jetbrains.annotations.Nullable` / и т.д.
=== ":simple-kotlin: Kotlin"
Предполагается использовать [Kotlin Nullability](https://kotlinlang.ru/docs/null-safety.html) синтаксис и помечать такой параметр как Nullable:
```kotlin
@Json
data class Dto(
val field1: String?,
val field2: Int
)
```
В случае если поле в Json называется иначе от того что требуется использовать в классе,
можно использовать аннотацию @JsonField для соответствия поля в Json и DTO.
===! ":fontawesome-brands-java: Java"
```java
@Json
public record Dto(@JsonField("field_1") String field1,
int field2) { }
```
=== ":simple-kotlin: Kotlin"
```kotlin
@Json
data class Dto(
@field:JsonField("field_1") val field1: String,
val field2: Int
)
```
В случае если поле в DTO не хочется читать/писать,
можно использовать аннотацию @JsonSkip и проигнорировать такое поле.
===! ":fontawesome-brands-java: Java"
```java
@Json
public record Dto(String field1,
@JsonSkip int field2) { }
```
=== ":simple-kotlin: Kotlin"
```kotlin
@Json
data class Dto(
val field1: String,
@field:JsonSkip val field2: Int
)
```
Поведение по умолчанию не подразумевает запись полей с null значениями. (1)
{ .annotate }
IncludeType.NON_NULL- включать поле в запись если неnull
В случае если хочется изменить поведение записи в этих моментах то предлагается использовать аннотацию @JsonInclude.
Аннотацию можно использовать не только над полем, но также над классом и тогда правило будет действовать на все поля сразу.
Доступны различные варианты использования:
IncludeType.ALWAYS- включать поле в запись всегдаIncludeType.NON_NULL- включать поле в запись если неnullIncludeType.NON_EMPTY- включать поле в запись если это неnullи не пустая коллекция
Пример использования аннотации:
===! ":fontawesome-brands-java: Java"
```java
@Json
@JsonInclude(IncludeType.NOT_NULL)
public record Dto(@JsonInclude(IncludeType.ALWAYS) @Nullable String field1,
int field2) { }
```
=== ":simple-kotlin: Kotlin"
```kotlin
@Json
data class Dto(
@field:JsonInclude(IncludeType.ALWAYS) val field1: String?,
val field2: Int
)
```
В случае если хочется использовать определенный конструктор для сериализации,
то это можно сделать с указанием над конструктором аннотации @JsonReader либо аннотации которая имеет меньший приоритет @Json:
===! ":fontawesome-brands-java: Java"
```java
@Json
public record Dto(String field1, int field2) {
@JsonReader
public Dto(String field1) {
this(field1, 0);
}
}
```
=== ":simple-kotlin: Kotlin"
```kotlin
@Json
data class Dto(val field1: String, val field2: Int) {
@JsonReader
constructor(field1: String) : this(field1, 0)
}
```
В случае если во время десериализации, хочется отличать отсутствующее поле от указанного null значения,
предполагается использовать специальный тип JsonNullable, который позволяет отражать все состояния поля после десериализации.
===! ":fontawesome-brands-java: Java"
```java
@Json
public record Dto(String field1, JsonNullable<Integer> field2) { }
```
=== ":simple-kotlin: Kotlin"
```kotlin
@Json
data class Dto(val field1: String, val field2: JsonNullable<Int>)
```
В случае если требуется писать различные Json объекты в зависимости от значения в конкретном поле, предполагается использовать изолированный класс/интерфейс для представления таких объектов.
Для поддержки изолированных классов добавлены две аннотации:
@JsonDiscriminatorField- указывает поле дискриминатора в DTO, которым помечается sealed класс/интерфейс@JsonDiscriminatorValue- значение для вышеуказанного поля, помечает класс-наследник sealed класса/интерфейса
===! ":fontawesome-brands-java: Java"
```java
@Json
@JsonDiscriminatorField("type")
public sealed interface Event {
@JsonDiscriminatorValue("firstType")
record FirstTypeEvent(String id, String type) implements Event {}
@JsonDiscriminatorValue("secondType")
record SecondTypeEvent(String id, Integer code) implements Event {}
@JsonDiscriminatorValue("thirdType")
record ThirdTypeEvent(String id, Boolean status) implements Event {}
}
```
=== ":simple-kotlin: Kotlin"
```kotlin
@Json
@JsonDiscriminatorField("type")
sealed interface Event {
@JsonDiscriminatorValue("firstType")
data class FirstTypeEvent(val id: String, val type: String) : Event
@JsonDiscriminatorValue("secondType")
data class SecondTypeEvent(val id: String, val code: Integer) : Event
@JsonDiscriminatorValue("thirdType")
data class ThirdTypeEvent(val id: String, val status: Boolean) : Event
}
```
Для классов-наследников будут созданы JsonReader и JsonWriter по тем же правилам, как если бы на них была аннотация @Json и создастся JsonReader и JsonWriter для самого sealed класса/интерфейса.
Json объект ниже будет записан в класс FirstTypeEvent:
{
"id": "1",
"type": "firstType",
"data": {
"megaData": "megaValue"
}
}Модуль предоставляет обширный список поддерживаемых из коробки типов которые покрывают большую часть того что может понадобиться.
??? abstract "Список поддерживаемых типов"
* Boolean
* boolean
* Short
* short
* Integer
* int
* Long
* long
* Double
* double
* Float
* float
* byte[]
* String
* UUID
* BigInteger
* BigDecimal
* List<Integer>
* Set<Integer>
* LocalDate
* LocalTime
* LocalDateTime
* OffsetTime
* OffsetDateTime
* ZonedDateTime
* Year
* YearMonth
* MonthDay
* Month
* DayOfWeek
* ZoneId
* Duration
В случае если требуется писать/читать собственный тип, то предлагается зарегистрировать собственную фабрику для JsonReader / JsonWriter:
Пример регистрации собственно JsonWriter:
===! ":fontawesome-brands-java: Java"
```java
@KoraApp
public interface Application {
default JsonWriter<ZoneOffset> zoneOffsetJsonWriter() {
return (generator, value) -> {
if(value != null) {
generator.writeString(value.getId());
}
};
}
}
```
=== ":simple-kotlin: Kotlin"
```kotlin
@KoraApp
interface Application {
fun zoneOffsetJsonWriter(): JsonWriter<ZoneOffset> {
return JsonWriter { generator, value ->
if (value != null) {
generator.writeString(value.id)
}
}
}
}
```
В случае если хочется использовать Jackson для записи/чтения, то можно самому зарегистрировать фабрику
предоставляющую ObjectMapper и соответствующие Mappers которые требуются в других Kora модулях будут предоставлены зависимостью ниже:
===! ":fontawesome-brands-java: Java"
[Зависимость](general.md#_4) `build.gradle`:
```groovy
annotationProcessor "ru.tinkoff.kora:json-annotation-processor"
implementation "ru.tinkoff.kora:jackson-module"
```
Модуль:
```java
@KoraApp
public interface Application extends JacksonModule { }
```
=== ":simple-kotlin: Kotlin"
[Зависимость](general.md#_4) `build.gradle.kts`:
```groovy
ksp("ru.tinkoff.kora:json-annotation-processor")
implementation("ru.tinkoff.kora:jackson-module")
```
Модуль:
```kotlin
@KoraApp
interface Application : JacksonModule
```