1. 概要

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

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

2. Mavenの依存関係

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

<dependency>
    <groupId>com.beust</groupId>
    <artifactId>klaxon</artifactId>
    <version>3.0.4</version>
</dependency>

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

3. API機能

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

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

このAPIを使用すると、 JSONドキュメントをKotlinオブジェクトにバインドしたり、その逆を行うことができます。 まず、次の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<Product>(
    """
        {
            "name" : "RAM"
        }
    """)

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

このAPIは、データクラス、および可変クラスと不変クラスの操作もサポートします。

Klaxonを使用すると、@Jsonアノテーションを使用してマッピングプロセスをカスタマイズできます。 この注釈には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

Streaming APIを使用すると、ストリームから読み取ることで巨大なJSONドキュメントを処理できます。 この機能を使用すると、コードがを読み取っている間にJSON値を処理できます。

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<ProductData>()
    JsonReader(StringReader(jsonArray)).use { 
        reader -> reader.beginArray {
            while (reader.hasNext()) {
                val product = klaxon.parse<ProductData>(reader)
                productArray.add(product!!)
            }
        }
    }

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

6. JSONパスクエリAPI

Klaxonは、JSONパス仕様の要素ロケーション機能をサポートしています。 このAPIを使用すると、パスマッチャーを定義してドキュメント内の特定のエントリを見つけることができます

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

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

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

  • pathMatches() –このパスを監視する場合はtrueを返します
  • 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を使用すると、 MapListなどのJSONドキュメントを処理できます。これを行うには、クラス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<JsonObject>

    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. 結論

この記事では、JSONドキュメントを処理するためのKlaxonライブラリとそのAPIについて説明しました。

いつものように、ソースコードはGithubから入手できます。