Обработка JSON с Котлиным и Клаксоном

Обработка JSON с Котлиным и Клаксоном

1. обзор

Klaxon - одна из библиотек с открытым исходным кодом, которую мы можем использовать для анализа JSON вKotlin.

В этом руководстве мы рассмотрим его особенности.

2. Maven Dependency

Во-первых, нам нужно добавить библиотечную зависимость в наш проект Maven:


    com.beust
    klaxon
    3.0.4

Последнюю версию можно найти вjcenter или вSpring Plugins Repository.

3. Возможности API

Клаксон имеет четыре API для работы с документами JSON. Мы рассмотрим их в следующих разделах.

4. API привязки объектов

С этим APIwe can bind JSON documents to Kotlin objects and vice-versa. Для начала давайте определим следующий документ JSON:

{
    "name": "HDD"
}

Затем мы создадим классProduct для привязки:

class Product(val name: String)

Теперь мы можем протестировать сериализацию:

@Test
fun givenProduct_whenSerialize_thenGetJsonString() {
    val product = Product("HDD")
    val result = Klaxon().toJsonString(product)

    assertThat(result).isEqualTo("""{"name" : "HDD"}""")
}

И мы можем проверить десериализацию:

@Test
fun givenJsonString_whenDeserialize_thenGetProduct() {
    val result = Klaxon().parse(
    """
        {
            "name" : "RAM"
        }
    """)

    assertThat(result?.name).isEqualTo("RAM")
}

Этот API также поддерживает работу с классами данных, а также с изменяемыми и неизменяемыми классами.

Klaxon позволяет намcustomize the mapping process with the @Json annotation. Эта аннотация имеет два свойства:

  • name - для установки другого имени для полей

  • ignored - для игнорирования полей процесса отображения

Давайте создадим классCustomProduct, чтобы увидеть, как они работают:

class CustomProduct(
    @Json(name = "productName")
    val name: String,
    @Json(ignored = true)
    val id: Int)

Теперь давайте проверим это с помощью теста:

@Test
fun givenCustomProduct_whenSerialize_thenGetJsonString() {
    val product = CustomProduct("HDD", 1)
    val result = Klaxon().toJsonString(product)

    assertThat(result).isEqualTo("""{"productName" : "HDD"}""")
}

Как мы видим, свойствоname сериализуется какproductName, а свойствоid игнорируется.

5. Потоковый API

С помощью Streaming API мы можем обрабатывать огромные документы JSON, читая из потока. Эта функцияallows our code to process JSON values while it is still reading.

Нам нужно использовать классJsonReader из API для чтения потока JSON. Этот класс имеет две специальные функции для обработки потоковой передачи:

  • beginObject() - следит за тем, чтобы следующий токен был началом объекта

  • beginArray() - следит за тем, чтобы следующий токен был началом массива

С помощью этих функций мы можем быть уверены, что поток расположен правильно и что он закрывается после использования объекта или массива.

Давайте протестируем потоковый API с массивом следующего классаProductData:

data class ProductData(val name: String, val capacityInGb: Int)
@Test
fun givenJsonArray_whenStreaming_thenGetProductArray() {
    val jsonArray = """
    [
        { "name" : "HDD", "capacityInGb" : 512 },
        { "name" : "RAM", "capacityInGb" : 16 }
    ]"""
    val expectedArray = arrayListOf(
      ProductData("HDD", 512),
      ProductData("RAM", 16))
    val klaxon = Klaxon()
    val productArray = arrayListOf()
    JsonReader(StringReader(jsonArray)).use {
        reader -> reader.beginArray {
            while (reader.hasNext()) {
                val product = klaxon.parse(reader)
                productArray.add(product!!)
            }
        }
    }

    assertThat(productArray).hasSize(2).isEqualTo(expectedArray)
}

6. JSON Path Query API

Klaxon поддерживает функцию определения местоположения элемента из спецификации JSON Path. С помощью этого API мы можемdefine path matchers to locate specific entries in our documents.

Обратите внимание, что этот API также является потоковым, и мы будем уведомлены после того, как элемент будет найден и проанализирован.

Нам нужно использовать интерфейсPathMatcher. Этот интерфейс вызывается, когда путь JSON обнаруживает совпадения регулярного выражения.

Чтобы использовать это, нам нужно реализовать его методы:

  • pathMatches() - вернуть истину, если мы хотим наблюдать этот путь

  • onMatch() - запускается при нахождении пути; обратите внимание, что значение может быть только базового типа (например,int,String) и никогдаJsonObject илиJsonArray

Давайте проведем тест, чтобы увидеть его в действии.

Сначала давайте определим документ JSON инвентаря как источник данных:

{
    "inventory" : {
        "disks" : [
            {
                "type" : "HDD",
                "sizeInGb" : 1000
            },
            {
                "type" : "SDD",
                "sizeInGb" : 512
            }
        ]
    }
}

Теперь мы реализуем интерфейсPathMatcher следующим образом:

val pathMatcher = object : PathMatcher {
    override fun pathMatches(path: String)
      = Pattern.matches(".*inventory.*disks.*type.*", path)

    override fun onMatch(path: String, value: Any) {
        when (path) {
            "$.inventory.disks[0].type"
              -> assertThat(value).isEqualTo("HDD")
            "$.inventory.disks[1].type"
              -> assertThat(value).isEqualTo("SDD")
        }
    }
}

Обратите внимание, что мы определили регулярное выражение в соответствии с типом диска нашего документа инвентаризации.

Теперь мы готовы определить наш тест:

@Test
fun givenDiskInventory_whenRegexMatches_thenGetTypes() {
    val jsonString = """..."""
    val pathMatcher = //...
    Klaxon().pathMatcher(pathMatcher)
      .parseJsonObject(StringReader(jsonString))
}

7. Низкоуровневый API

С Klaxon мы можем обрабатывать документы JSON, такие какMap илиList.. Для этого мы можем использовать классыJsonObject иJsonArray из API.

Давайте проведем тест, чтобы увидетьJsonObject в действии:

@Test
fun givenJsonString_whenParser_thenGetJsonObject() {
    val jsonString = StringBuilder("""
        {
            "name" : "HDD",
            "capacityInGb" : 512,
            "sizeInInch" : 2.5
        }
    """)
    val parser = Parser()
    val json = parser.parse(jsonString) as JsonObject

    assertThat(json)
      .hasSize(3)
      .containsEntry("name", "HDD")
      .containsEntry("capacityInGb", 512)
      .containsEntry("sizeInInch", 2.5)
}

Теперь давайте проведем тест, чтобы увидеть функциональностьJsonArray:

@Test
fun givenJsonStringArray_whenParser_thenGetJsonArray() {
    val jsonString = StringBuilder("""
    [
        { "name" : "SDD" },
        { "madeIn" : "Taiwan" },
        { "warrantyInYears" : 5 }
    ]""")
    val parser = Parser()
    val json = parser.parse(jsonString) as JsonArray

    assertSoftly({
        softly ->
            softly.assertThat(json).hasSize(3)
            softly.assertThat(json[0]["name"]).isEqualTo("SDD")
            softly.assertThat(json[1]["madeIn"]).isEqualTo("Taiwan")
            softly.assertThat(json[2]["warrantyInYears"]).isEqualTo(5)
    })
}

Как мы видим в обоих случаях, мы сделали преобразования без определения конкретных классов.

8. Заключение

В этой статье мы исследовали библиотеку Klaxon и ее API для обработки документов JSON.

Как всегда доступен исходный кодover on Github.