JSONPath 使い方まとめ

## 目的
先輩「MockMvcでJSONPathを使おうと思ってて…」

@takkii1010 「…？」

となったので、以下を簡単にまとめたい。

1. JSONPathとは何者か
2. JSONPathの構文
3. MockMvcでの使い方

## 1. JSONPathとは何者か
https://goessner.net/articles/JsonPath/

> Data may be interactively found and extracted out of JSON structures on the client without special scripting.
> JSONPath expressions always refer to a JSON structure in the same way as XPath expression are used in combination with an XML document.

* XPath for JSON
  * 特別なスクリプトを書かずとも、JSONを双方向に検出＆抽出できるライブラリ
  * XPathがXMLを参照するのと同様に、JSONPathは常にJSONを参照するようになっている
     * [XPathとは何者か](https://www.techscore.com/tech/XML/XPath/XPath1/xpath01.html/)
* 簡単にいうと、JSONから欲しいkey,value,lengthなどを取得できて便利
* 意外と身近なところで使われている
  * [Oracle Database](https://docs.oracle.com/cd/E57425_01/121/ADXDB/json.htm#GUID-11006CA4-91CA-4D22-AB14-8F25508CFC5F), [Kubectl](https://kubernetes.io/docs/reference/kubectl/jsonpath/) etc.

## 2. JSONPathの構文

[Jayway JsonPath](https://github.com/json-path/JsonPath) を見ながら進めます。
また、以下サイトで、JSONPathのお試しがブラウザから簡単にできます。

[JSONPath Online Evaluator ](https://jsonpath.com/)

### 基本構文

使ったJSONはこちら。

```json
{
  "firstName": "John",
  "lastName" : "doe",
  "age"      : 26,
  "address"  : {
    "streetAddress": "naist street",
    "city"         : "Nara",
    "postalCode"   : "630-0192"
  },
  "phoneNumbers": [
    {
      "type"  : "iPhone",
      "number": "0123-4567-8888"
    },
    {
      "type"  : "home",
      "number": "0123-4567-8910"
    }
  ]
}
```

|JSONPath|説明|例|結果|メモ|
|:--|:--|:--|:--|:--|
|$|ルートの要素|`$.firstName`|`"John"`|`$` だけだと全部取得 |
|@|フィルター結果の現在の要素|`$.phoneNumbers[?(@.type == 'home')]`|`"type": "home",`<br> `"number": "0123-4567-8910"`||
|*|ワイルドカード|`$.address.*`|`"naist street",`<br>`"Nara",`<br>`"630-0192"`||
|..|keyが一致するすべてのvalue|`$..type`|`"iPhone",`<br> `"home"`|
|['\<name>' (, '\<name>')]|[ブラケット演算子]()|`$.['firstName']`|`"John"`||
|[\<number> (, \<number>)]||`$.phoneNumbers[0,1]`|`{`<br>`　"type": "iPhone",`<br>`　"number": "0123-4567-8888"`<br>`},`<br>`{`<br>`　"type": "home",`<br>`　"number": "0123-4567-8910"`<br>`}`||
|[start:end]|配列のスライス|`$.phoneNumbers[0:1]`|`{`<br>`　"type": "iPhone",`<br>`　"number": "0123-4567-8888"`<br>`}`||
|[?(\<expression>)]|フィルター機能。式はBooleanで評価されないといけない|`@` に例記載。||

…結果がきれいに書けてない。

#### 関数

`min()`, `max()`, `avg()`, `stddev()`, `length()`, `sum()` があります。
`length()` は `$.phoneNumbers.length` とあまり深く考えずに使えますが、他の関数が少し厄介。

こんな感じのJSONの場合…

```json
{
  "id": 1,
  "name" : "スクエ…",
  "products": [
    {
      "name"  : "FINAL FANTASY VII",
      "price": 1572
    },
    {
      "name"  : "FINAL FANTASY X",
      "price": 8800
    },
    {
      "name"  : "FINAL FANTASY XV",
      "price": 8800
    }
  ]
}
```

一度取り出してから、処理を行う必要があるらしい。
https://oboe2uran.hatenablog.com/entry/2018/05/06/131634

MockMvcを使う場合は以下で動きました。

```kotlin
        val jsonObject = mockMvc.perform(get("/gameCompanies/1"))
                .andExpect(status().isOk())
                .andReturn().response.contentAsString
        val priceList: List<Int> = JsonPath.read( jsonObject, "\$.products[*].price")
        val min: Int = JsonPath.read(priceList, "\$.min()")
        val max: Int = JsonPath.read(priceList, "\$.max()")
        val sum: Int = JsonPath.read(priceList, "\$.sum()")
        val stddev: Int = JsonPath.read(priceList, "\$.stddev()")
        assertThat(min).isEqualTo(1572)
        assertThat(max).isEqualTo(8800)
        assertThat(sum).isEqualTo(19172)
        assertThat(stddev).isEqualTo(3407)
```



[他にこちらにも例が載ってます](https://github.com/json-path/JsonPath#path-examples)

## 3. MockMvcでの使い方

```kotlin
@RunWith(SpringRunner::class)
@WebMvcTest(GameCompanyController::class)
class GameCompanyControllerTest {

    @Autowired
    lateinit var mockMvc: MockMvc

    @MockBean
    lateinit var gameCompanyRepository: GameCompanyRepositoryInterface

    @Test
    fun getGameCompanyTest() {
        val expectedGameCompany = GameCompany(
                id = 1L,
                name = "スクエ…",
                products = listOf(
                        Product("FINAL FANTASY VII", BigDecimal(1572)),
                        Product("FINAL FANTASY X", BigDecimal(8800)),
                        Product("FINAL FANTASY XV", BigDecimal(8800))
                )
        )

        `when`(gameCompanyRepository.fetchGameCompany(any())).thenReturn(expectedGameCompany)
        mockMvc.perform(get("/gameCompanies/1")
                .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("\$.id").value(1))
                .andExpect(jsonPath("\$.name").value("スクエ…"))
                .andExpect(jsonPath("\$.products[0].name").value("FINAL FANTASY VII"))
                .andExpect(jsonPath("\$.products[0].price").value(1572))
                .andExpect(jsonPath("\$.products[1].name").value("FINAL FANTASY X"))
                .andExpect(jsonPath("\$.products[1].price").value(8800))
                .andExpect(jsonPath("\$.products[2].name").value("FINAL FANTASY XV"))
                .andExpect(jsonPath("\$.products[2].price").value(8800))
    }
}
```

 Kotlinの [`indices`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/indices.html) 使うとこんな感じ。

```kotlin
        …
        mockMvc.perform(get("/gameCompanies/1")
                .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                ...
                .andExpect(expectedGameCompany.products)
    }

    private fun ResultActions.andExpect(products: List<Product>): ResultActions {
        for (index in products.indices) {
            andExpect(jsonPath("\$.products[$index].name").value(products[index].name))
                    .andExpect(jsonPath("\$.products[$index].price").value(products[index].price))
        }
        return this
    }
```

他にも、 `Matchers` のisを使用しても動きます。

```kotlin
        mockMvc.perform(get("/gameCompanies/1")
                .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("\$.id", `is`(1)))
```

ただ、[少し古い書き方](http://blog.64p.org/entry/2019/01/06/182538)のようなので、 `value` で書くのが良さそう。


#### 参考にしたサイト一覧
https://goessner.net/articles/JsonPath/
https://www.techscore.com/tech/XML/XPath/XPath1/xpath01.html/
https://github.com/json-path/JsonPath
http://oboe2uran.hatenablog.com/entry/2018/05/06/131634