JSON-линзы в http4k
#
Васильев Андрей Михайлович, 2024
Версии презентации
Поддержка JSON
#
http4k предоставляет отличную поддержку для работы с JSON-документами:
- Удобное конструирование JSON-документов произвольной структуры
- Запись JSON-документов как ответы на HTTP-запросы
- Считывание JSON-документов из тела HTTP-запросов
Причём поддержка существует для множества библиотек, в том числе и для Jackson
Для подключения данных возможностей в проект необходимо добавить зависимость
в build.gradle.kts-файл или build.properties.json-файл
"org.http4k:http4k-format-jackson"
Не забудьте указать версию, совместимую с основной библиотекой http4k-core
Линзы для записи тела
#
Библиотека Jackson предоставляет возможности для преобразования произвольных
объектов в JSON-документы с помощью рефлексии
С использованием данного механизма можно создать линзу
- Которая формирует JSON-документ
- Записывает данный документ как тело объекта ответа
- Указывает корректный тип документа в заголовках ответа
При использовании такой линзы для формирования ответа достаточно сформировать
данные и применить линзу к ответу
Создание линзы
#
Каждое конкретное дополнение для библиотеки предоставляет функцию auto
,
которую можно применить к телу запроса
import org.http4k.format.Jackson.auto
Далее необходимо специфицировать объект, который будет записан тело ответа
data class Message(val subject: String, val from: String, val to: String)
Для данного класса необходимо сформировать линзу:
val messageLens = Body.auto<Message>().toLens()
Теперь эту линзу можно применить внутри обработчика HTTP-запроса
val myMessage = Message("hello", "bob@git.com", "sue@git.com")
val response: Response = messageLens(myMessage, Response(Status.OK))
Применение нескольких линз
#
Для записи значения в целевой объект линза предоставляет функцию
fun invoke(message: Message, response: Response): Response
Если мы захотим применить несколько линз к объекту-ответа, то придётся
оборачивать одну линзу в другую:
firstLens(firstData, secondLens(secondData, Response(Status.OK)))
Для более красивого применения линз http4k предлагает функцию
org.http4k.core.with, которой передаётся набор модификаторов
Для создания модификатора из линзы необходимо использовать функцию of
Response(Status.OK).with(
firstLens of firstData,
secondLens of secondData,
)
- Функция
of
является инфиксной
- Можно объединять линзы, которые работают над одним объектом
Универсальная Jackson-линза для записи
#
Линза, которая создаётся с помощью функции auto
, является двунаправленной,
т.е. может как записывать JSON-документы, так и считывать их
Если мы ограничим сферу её применения только записью данных, то можно довериться
возможностям ObjectMapper для преобразования любого объекта в тело документа
Т.е. можно создать универсальную линзу для записи данных:
val jsonBodyLens = Body.auto<Any>().toLens()
Эту линзу можно использовать с любыми данными:
val myMessage = Message("hello", "bob@git.com", "sue@git.com")
Response(Status.OK).with(
jsonBodyLens of myMessage,
)
Создание JSON-документа из узлов
#
Помимо передачи объектов существующих классов на вход линзы можно подать
низкоуровневое представление JSON-документа в виде узлов, объектов JsonNode
Для создания таких объектов предоставляется удобный интерфейс
import com.fasterxml.jackson.databind.JsonNode
import org.http4k.format.Jackson
val json = Jackson
// Direct JSON library API:
val objectUsingDirectApi: JsonNode = json.obj(
"thisIsAString" to json.string("stringValue"),
"thisIsANumber" to json.number(12345),
"thisIsAList" to json.array(listOf(json.boolean(true)))
)
// DSL JSON library API:
val objectUsingDslApi: JsonNode = json {
obj(
"thisIsAString" to string("stringValue"),
"thisIsANumber" to number(12345),
"thisIsAList" to array(listOf(boolean(true)))
)
}
Объекты, созданные таким образом, можно передать линзе
Считывание JSON-данных из запроса
#
Линзу также можно применить для извлечения данных из тела запроса
- Необходимо использовать типизированную линзу
- В случае невозможности извлечения данных в целевой объект будет
выброшено исключение LensFailure
data class Message(val subject: String, val from: String, val to: String)
val messageLens = Body.auto<Message>().toLens()
Данную линзу можно применить к объекту-запросу в обработчике HTTP-запроса
val message: Message = messageLens(request)
Данный подход не позволяет получить информацию по всем возможным ошибкам в
передаваемом объекте, только о первой