KotlinとKlaxsonによるJSONの処理

KotlinとKlaxsonでJSONを処理する

1. 概要

Klaxonは、KotlinのJSONを解析するために使用できるオープンソースライブラリの1つです。

このチュートリアルでは、その機能を見ていきます。

2. メーベン依存

最初に、ライブラリの依存関係をMavenプロジェクトに追加する必要があります。


    com.beust
    klaxon
    3.0.4

最新バージョンは、jcenterまたはSpring Plugins Repositoryにあります。

3. API機能

Klaxonには、JSONドキュメントを操作するための4つのAPIがあります。 これらについては、次のセクションで説明します。

4. オブジェクトバインディングAPI

このAPIでは、we 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が可能です。 この注釈には2つのプロパティがあります。

  • 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

ストリーミングAPIを使用すると、ストリームから読み取ることで巨大なJSONドキュメントを処理できます。 この機能はallows our code to process JSON values while it is still readingです。

JSONストリームを読み取るには、APIのJsonReaderクラスを使用する必要があります。 このクラスには、ストリーミングを処理する2つの特別な関数があります。

  • beginObject() –次のトークンがオブジェクトの先頭であることを確認します

  • beginArray() –次のトークンが配列の先頭であることを確認します

これらの関数を使用すると、ストリームが正しく配置され、オブジェクトまたは配列を消費した後に閉じられていることを確認できます。

次のProductDataクラスの配列に対してストリーミングAPIをテストしてみましょう。

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パスクエリAPI

Klaxonは、JSON Path仕様の要素の場所機能をサポートしています。 このAPIを使用すると、define path matchers to locate specific entries in our documentsを実行できます。

このAPIもストリーミング中であり、要素が検出されて解析された後に通知されることに注意してください。

PathMatcherインターフェースを使用する必要があります。 このインターフェイスは、JSONパスが正規表現の一致を検出したときに呼び出されます。

これを使用するには、そのメソッドを実装する必要があります。

  • pathMatches() –このパスを監視する場合はtrueを返します

  • onMatch() –パスが見つかったときに発生します。値は基本タイプ(例:intString)のみであり、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を使用すると、MapList.などのJSONドキュメントを処理できます。これを行うには、APIのクラスJsonObjectJsonArrayを使用できます。

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ライブラリーとJSONドキュメントを処理するためのAPIについて説明しました。

いつものように、ソースコードはover on Githubで利用できます。