Processando JSON com Kotlin e Klaxson

Processando JSON com Kotlin e Klaxson

1. Visão geral

Klaxon é uma das bibliotecas de código aberto que podemos usar para analisar JSON emKotlin.

Neste tutorial, vamos dar uma olhada em seus recursos.

2. Dependência do Maven

Primeiro, precisamos adicionar a dependência da biblioteca ao nosso projeto Maven:


    com.beust
    klaxon
    3.0.4

A versão mais recente pode ser encontrada emjcenter ou emSpring Plugins Repository.

3. Recursos de API

O Klaxon possui quatro APIs para trabalhar com documentos JSON. Exploraremos isso nas seções a seguir.

4. API Object Binding

Com esta API,we can bind JSON documents to Kotlin objects and vice-versa. Para começar, vamos definir o seguinte documento JSON:

{
    "name": "HDD"
}

A seguir, criaremos a classeProduct para vinculação:

class Product(val name: String)

Agora, podemos testar a serialização:

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

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

E podemos testar a desserialização:

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

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

Essa API também suporta o trabalho com classes de dados, bem como classes mutáveis ​​e imutáveis.

Klaxon nos permitecustomize the mapping process with the @Json annotation. Esta anotação possui duas propriedades:

  • name - para definir um nome diferente para os campos

  • ignored - para ignorar campos do processo de mapeamento

Vamos criar uma classeCustomProduct para ver como funcionam:

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

Agora, vamos verificar com um teste:

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

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

Como podemos ver, a propriedadename é serializada comoproductName, e a propriedadeid é ignorada.

5. API de streaming

Com a API de streaming, podemos lidar com grandes documentos JSON lendo um fluxo. Este recursoallows our code to process JSON values while it is still reading.

Precisamos usar a classeJsonReader da API para ler um fluxo JSON. Esta classe possui duas funções especiais para lidar com o streaming:

  • beginObject() - certifica-se de que o próximo token é o início de um objeto

  • beginArray() - garante que o próximo token é o início de uma matriz

Com essas funções, podemos ter certeza de que o fluxo está posicionado corretamente e que é fechado após consumir o objeto ou matriz.

Vamos testar a API de streaming em uma matriz das seguintes classesProductData:

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. API JSON Path Query

O Klaxon suporta o recurso de localização do elemento da especificação JSON Path. Com esta API, podemosdefine path matchers to locate specific entries in our documents.

Observe que esta API também é streaming e seremos notificados depois que um elemento for encontrado e analisado.

Precisamos usar a interfacePathMatcher. Essa interface é chamada quando o caminho JSON encontrou correspondências da expressão regular.

Para usar isso, precisamos implementar seus métodos:

  • pathMatches() - retorna verdadeiro se quisermos observar este caminho

  • onMatch() - disparado quando o caminho é encontrado; observe que o valor só pode ser um tipo básico (por exemplo,int,String) e nuncaJsonObject ouJsonArray

Vamos fazer um teste para vê-lo em ação.

Primeiro, vamos definir um documento JSON de inventário como uma fonte de dados:

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

Agora, implementamos a interfacePathMatcher da seguinte maneira:

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")
        }
    }
}

Observe que definimos a regex para corresponder ao tipo de disco do nosso documento de inventário.

Agora, estamos prontos para definir nosso teste:

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

7. API de baixo nível

Com o Klaxon, podemos processar documentos JSON comoMap ouList.. Para fazer isso, podemos usar as classesJsonObjecteJsonArray da API.

Vamos fazer um teste para ver oJsonObject em ação:

@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)
}

Agora, vamos fazer um teste para ver a funcionalidade deJsonArray:

@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)
    })
}

Como podemos ver nos dois casos, fizemos as conversões sem a definição de classes específicas.

8. Conclusão

Neste artigo, exploramos a biblioteca Klaxon e suas APIs para lidar com documentos JSON.

Como sempre, o código-fonte está disponívelover on Github.