C#でKey(変数)が定まっていないJsonをParseする

#この記事で解説すること

Keyが定まっていないメンバーを含むJsonをParse(Desrialize)する方法

#やりたいこと

Json形式はAPIのResponseなどとしてよく使われています。
Jsonの最小要素はKeyとValueのペアですが、このKey側があらかじめ定まっていない場合（静的でない）は、情報の取り出しに少し工夫がいります。
（C#での話です。Pythonだと"request"モジュールで自動的にParseしてくれたりします。）
（ちなみに、あらかじめKeyが定まっている場合はC#標準の[DataContractJsonSerializer](https://docs.microsoft.com/ja-jp/dotnet/framework/wcf/feature-details/how-to-serialize-and-deserialize-json-data#to-deserialize-an-instance-of-type-person-from-json)とかを使えば簡単にParseできます。）  

```json-doc  
// Jsonの最小要素
// { "key" : value }の形で表される
// この"key"の部分（ここでいう"name"）が常に固定でない場合のParseをしたい。
{ "name": "Tanaka" }
```


#Keyが定まっていないJsonの実例

題材がないと説明しにくいので、Keyが定まってないJsonの例を一つ書きます。
これは、SlackのWorkspaceに[登録されている絵文字一覧を取得するAPI](https://api.slack.com/methods/emoji.list) のResponseとして渡されるJsonです。

最初にある"ok":trueは、Requestが上手くできたかの値なのでここでは関係ありません。

問題は次の"emoji"に対応する項目です。
"bowtie", "squirrel".. というように、登録されている絵文字の名前がKeyとして並んでいます。しかし、登録されている絵文字はあらかじめ決まっていない（Slackではユーザーが新しく絵文字を追加さることができる）です。

つまり、"emoji"の中身の情報を取り出そうとすると、Keyが定まっていないJsonをParseする必要があります。  

```json-doc

// ok, emojiがkeyとなるのは確定している。
{
	"ok": true,
	"emoji": {
		// emojiの各項目は、Key=絵文字の名前: Value=絵文字画像のurl(またはalias)、という形式。
        // Key=絵文字の名前であり、それはあらかじめ定まっていないためParseに工夫が必要。
　　    // また、emojiの項目数は固定ではない。
		"bowtie": "https://my.slack.com/emoji/bowtie/46ec6f2bb0.png",
		"squirrel": "https://my.slack.com/emoji/squirrel/f35f40c0e0.png",	
		// … (以下登録されている絵文字が続く）
	}
}

```

この記事では、このJsonを例にとって、Parse方法を見ていきます。

#環境

- Windows10
- VisualStudio2019 Community
- C#8.0 (.NET Core3.1)
- Newtonsoft.Json(Json.NET) v12.0.3

今回は、[Newtonsoft.Json](https://www.newtonsoft.com/json)(Json.NET)を使ってParseを行います。Newtonsoft.JsonはNugetからインストールします。
（C#標準でもJ[sonを扱うためのクラス群](https://docs.microsoft.com/ja-jp/dotnet/api/system.text.json?view=net-5.0)が用意されているのですが、予めKey（文字列）が定まっていないJsonのParse方法はなさそうでした。やり方知っている方いれば教えてください。）

#事前知識

本題に入る前に、Newtonsoft.Jsonの基本事項を確認しておきます。

Newtonsoft.Jsonを使ってJsonをParseする場合は、対象のJsonを、Jvalue, JObject, JArrayの3つの型に変換していく必要があります。Jvalue, JObject, JArrayの3つは、Parse対象のJsonの構成によって、適切に使い分ける必要があります。  
（※Jsonの構成によっては、明示的にJValue, JObject, JArrayなどに変換しなくても、自動的に変換してくれるメソッドが用意されています。）

JValue, JObject, JArrayについて、以下に説明と簡単な例を示します。

- JValue型 : プリミティブ型を表す。（文字列、数値など）
- JObject型 : 単純なKeyとValueの羅列を表す。
- JArray型 : 配列。いくつかのJObjectをひとまとめにしたものを表す。
- JToken型：JValue, JObject, JArrayのベースクラス。  
（ちなみに、今回のParseで使うのはJObjectだけです。）

```json-doc

// {}のまとまりがJObject (Taroや24はJValue)
{ "name" : "Taro", "age" : 24 } 

// []のまとまりがJArray
[ { "name" : "Taro" }, { "age" : 24 }, ... ]
```

#本題  

まずParseしたいJsonの構成を見て、使う型を選びます。

下記のJsonなら以下のようにParseできそうです。（今回はJObject型のみ使用）

1. 全体をJObject型で受け取る。（全体 = 一番外側の"ok"や"emoji"を含む{}のこと）
2. 1の中から"emoji"のValue（"emoji" : 以降の{}の中身全体）をJObject型で受け取る
3. 2の中から各項目をKeyValuePairとして取り出す

```json-doc

{
	"ok": true,
	"emoji": {
		// emojiの各項目は、Key=絵文字の名前: Value=絵文字画像のurl(またはalias)、という形式。
        // Key=絵文字の名前が不定のため特別にParseする必要がある。
　　    // また、emojiの項目数は固定ではない。
		"bowtie": "https://my.slack.com/emoji/bowtie/46ec6f2bb0.png",
		"squirrel": "https://my.slack.com/emoji/squirrel/f35f40c0e0.png",
		…
	}
}
```

実際に、1~3までの流れをコードで書くとこうです。

```csharp

string jsonString = (↑に示したJsonが代入されているとする)

// 1. 全体をJObject型で受け取る。
// jsonStringはResponseなどで受け取った、json構造のstring型変数とする。
// string --> JObjectの変換はJObject.Parse()で行う。
JObject jsonObject = JObject.Parse(jsonString);

// 2. 1の中から"emoji"のValueをJObject型で受け取る。
// ["(KeyName)"]で特定のKeyのValueを、JObjectから取り出せる。
// ここでは取り出すValueもJObject型のため、JObject型の変数に代入してやる。明示的なCastが必要。
JObject emojis = (JObject)jsonObject["emoji"];

// 3. 2の中から各項目をKeyValuePairとして取り出す
// JObject型はIEnumerableを継承しており、
// GetEnumerator()でKeyValuePair<string, JToken?>を返す。
// 上の例では、KeyValuePairのKeyが絵文字の名前、Valueが絵文字のURIとなる。
foreach (var emoji in emojis)
{
    var name = emoji.Key;
		// KeyValuePairのValueはJToken?型のため、stringに変換する。
    var uri = new Uri(emoji.Value.ToString());		
    Console.WriteLine($"EmojiName : {name}, EmojiUri : {uri}");
}

// ============================
// Output : 
// EmojiName : bowtie, EmojiUri : https://my.slack.com/emoji/bowtie/46ec6f2bb0.png
// EmojiName : squirrel, EmojiUri : https://my.slack.com/emoji/squirrel/f35f40c0e0.png
// ...
```

コード中にここまでで解説していないものがいくつかあるので、補足していきます。

- JObject.Parse()

```csharp
JObject jsonObject = JObject.Parse(jsonString);
```

引数に与えられたstring型をJObject型に変換するメソッドです。
コード中では、まずこのメソッドを使って、Responseなどで受け取ったstring型をJObjectに変換しています。

なお、引数に渡すstring型はJson形式になっている必要があります。（Json形式になっていない場合は、Newtonsoft.Json.JsonReaderExceptionがthrowされます。）

- Keyを指定したValueの取り出し

```csharp
JObject emojis = (JObject)jsonObject["emoji"];
```

JObject型から特定のKeyに対応するValueを取り出したい場合は、Dictionary型などと同様に、["(Key)"]の形で指定してやると取り出すことができます。
この際、取り出したValueを適切な型にCastしてやる必要があります。（正確にいうと、この時点ではCastしなくても大丈夫です。Castしない場合はJToken型となります。）  

例ではJObject型にCastしていますが、JArray型にCast使用とすると例外(System.InvalidCastException)がthrowされます。

- JObjectからの個別要素の取り出し

```csharp
// emojisがJObject
// emojisの実態は、
//		"bowtie": "https://my.slack.com/emoji/bowtie/46ec6f2bb0.png"
//		"squirrel": "https://my.slack.com/emoji/squirrel/f35f40c0e0.png", ...
// というKeyとValueの羅列
foreach (var emoji in emojis)
{
    ....
}
```

ここで、変数emojisはJObject型であり、その実態はKeyとValueの羅列です。  

JObject型はIEnumerableを継承しており、foreachで扱えます。
このとき、各要素（GetEnumerator()の戻り値）は、KeyValuePair<string, JToken?>となります。（↑の例だと、Keyのstringが絵文字の名前、Valueが絵文字のuriとなります）

なお、KeyValuePairのValueは、JToken型となるので利用するにはCastが必要となります。

#まとめ

この記事では、Keyの名前が静的でないJsonのParse(Deserialize)方法を書きました。  

簡潔にまとめると以下です。

- ParseしたいJsonの構成を見て、使う型(JObject, JArray, JValueなど)を選ぶ。
- string型をJObject.Parseで変換し、Newtonsoft.jsonで扱えるようにする。
- Json全体から、必要な部分を、選んだ型に変換しつつ取り出していく。
- Keyの名前が静的でない部分は、JObjectからKeyValuePairを受け取ることにより、取り出す。


## 各種リンク

* 引用  
    * [SlackAPI-Emoji.List](https://api.slack.com/methods/emoji.list)
* リンク先
    * [DataContractJsonSerizlierの使用方法](https://docs.microsoft.com/ja-jp/dotnet/framework/wcf/feature-details/how-to-serialize-and-deserialize-json-data#to-deserialize-an-instance-of-type-person-from-json)
    * [System.Text.Json名前空間](https://docs.microsoft.com/ja-jp/dotnet/api/system.text.json?view=net-5.0)
* 参考
    * [NEWTONSOFT.JSON(JSON.NET)の基本的な使い方-@SUBARU](https://netweblog.wordpress.com/2016/10/24/json-net-newtonsoft-json-usage/)
    * [JSON入門-とほほのWWW入門](http://www.tohoho-web.com/ex/json.html)