Processando JSON com Kotlin e Klaxson
1. Visão geral
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.