LoginSignup
2
1

More than 3 years have passed since last update.

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

Posted at

この記事で解説すること

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型のみ使用)

  1. 全体をJObject型で受け取る。(全体 = 一番外側の"ok"や"emoji"を含む{}のこと)
  2. 1の中から"emoji"のValue("emoji" : 以降の{}の中身全体)をJObject型で受け取る
  3. 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を受け取ることにより、取り出す。

各種リンク

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1