この記事で解説すること
Keyが定まっていないメンバーを含むJsonをParse(Desrialize)する方法
やりたいこと
Json形式はAPIのResponseなどとしてよく使われています。
Jsonの最小要素はKeyとValueのペアですが、このKey側があらかじめ定まっていない場合(静的でない)は、情報の取り出しに少し工夫がいります。
(C#での話です。Pythonだと"request"モジュールで自動的にParseしてくれたりします。)
(ちなみに、あらかじめKeyが定まっている場合はC#標準のDataContractJsonSerializerとかを使えば簡単にParseできます。)
// Jsonの最小要素// { "key" : value }の形で表される// この"key"の部分(ここでいう"name")が常に固定でない場合のParseをしたい。{"name":"Tanaka"}
Keyが定まっていないJsonの実例
題材がないと説明しにくいので、Keyが定まってないJsonの例を一つ書きます。
これは、SlackのWorkspaceに登録されている絵文字一覧を取得するAPIのResponseとして渡されるJsonです。
最初にある"ok":trueは、Requestが上手くできたかの値なのでここでは関係ありません。
問題は次の"emoji"に対応する項目です。
"bowtie", "squirrel".. というように、登録されている絵文字の名前がKeyとして並んでいます。しかし、登録されている絵文字はあらかじめ決まっていない(Slackではユーザーが新しく絵文字を追加さることができる)です。
つまり、"emoji"の中身の情報を取り出そうとすると、Keyが定まっていないJsonをParseする必要があります。
// 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(Json.NET)を使ってParseを行います。Newtonsoft.JsonはNugetからインストールします。
(C#標準でもJsonを扱うためのクラス群が用意されているのですが、予め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だけです。)
// {}のまとまりがJObject (Taroや24はJValue){"name":"Taro","age":24}// []のまとまりがJArray[{"name":"Taro"},{"age":24},...]
本題
まずParseしたいJsonの構成を見て、使う型を選びます。
下記のJsonなら以下のようにParseできそうです。(今回はJObject型のみ使用)
- 全体をJObject型で受け取る。(全体 = 一番外側の"ok"や"emoji"を含む{}のこと)
- 1の中から"emoji"のValue("emoji" : 以降の{}の中身全体)をJObject型で受け取る
- 2の中から各項目をKeyValuePairとして取り出す
{"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までの流れをコードで書くとこうです。
stringjsonString=(↑に示したJsonが代入されているとする)// 1. 全体をJObject型で受け取る。// jsonStringはResponseなどで受け取った、json構造のstring型変数とする。// string --> JObjectの変換はJObject.Parse()で行う。JObjectjsonObject=JObject.Parse(jsonString);// 2. 1の中から"emoji"のValueをJObject型で受け取る。// ["(KeyName)"]で特定のKeyのValueを、JObjectから取り出せる。// ここでは取り出すValueもJObject型のため、JObject型の変数に代入してやる。明示的なCastが必要。JObjectemojis=(JObject)jsonObject["emoji"];// 3. 2の中から各項目をKeyValuePairとして取り出す// JObject型はIEnumerableを継承しており、// GetEnumerator()でKeyValuePair<string, JToken?>を返す。// 上の例では、KeyValuePairのKeyが絵文字の名前、Valueが絵文字のURIとなる。foreach(varemojiinemojis){varname=emoji.Key;// KeyValuePairのValueはJToken?型のため、stringに変換する。varuri=newUri(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()
JObjectjsonObject=JObject.Parse(jsonString);
引数に与えられたstring型をJObject型に変換するメソッドです。
コード中では、まずこのメソッドを使って、Responseなどで受け取ったstring型をJObjectに変換しています。
なお、引数に渡すstring型はJson形式になっている必要があります。(Json形式になっていない場合は、Newtonsoft.Json.JsonReaderExceptionがthrowされます。)
- Keyを指定したValueの取り出し
JObjectemojis=(JObject)jsonObject["emoji"];
JObject型から特定のKeyに対応するValueを取り出したい場合は、Dictionary型などと同様に、["(Key)"]の形で指定してやると取り出すことができます。
この際、取り出したValueを適切な型にCastしてやる必要があります。(正確にいうと、この時点ではCastしなくても大丈夫です。Castしない場合はJToken型となります。)
例ではJObject型にCastしていますが、JArray型にCast使用とすると例外(System.InvalidCastException)がthrowされます。
- JObjectからの個別要素の取り出し
// emojisがJObject// emojisの実態は、// "bowtie": "https://my.slack.com/emoji/bowtie/46ec6f2bb0.png"// "squirrel": "https://my.slack.com/emoji/squirrel/f35f40c0e0.png", ...// というKeyとValueの羅列foreach(varemojiinemojis){....}
ここで、変数emojisはJObject型であり、その実態はKeyとValueの羅列です。
JObject型はIEnumerableを継承しており、foreachで扱えます。
このとき、各要素(GetEnumerator()の戻り値)は、KeyValuePairとなります。(↑の例だと、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を受け取ることにより、取り出す。