この記事で解説すること
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までの流れをコードで書くとこうです。
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()
JObject jsonObject = JObject.Parse(jsonString);
引数に与えられたstring型をJObject型に変換するメソッドです。
コード中では、まずこのメソッドを使って、Responseなどで受け取ったstring型をJObjectに変換しています。
なお、引数に渡すstring型はJson形式になっている必要があります。(Json形式になっていない場合は、Newtonsoft.Json.JsonReaderExceptionがthrowされます。)
- Keyを指定したValueの取り出し
JObject emojis = (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 (var emoji in emojis)
{
....
}
ここで、変数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を受け取ることにより、取り出す。