Newtonsoft Json.NET を雰囲気で使う(パース)
概要
C#で(WebAPI経由の)JSON扱いたいなら、とりあえず Newtonsoft.Json 使っとけ、みたいな所 無いですかね...
自分の場合、他所のWebAPIから拾った不定形気味のJSONをパース(デシリアライズとも言う?)して、
あれやこれやする事が多いので主にそっち系です
パッケージ諸々
VisualStudio に Nuget 経由でパッケージを突っ込みます。
Newtonsoft.Json
で参照(検索)かければ一番上に出て来るかも?
パッケージインストールしたら、あとは必要に応じて
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
等で、よしなに。
種類抜粋
この前提知識があると多分扱いやすくなると思います。
以下の型や紐づいているメソッド等を使って、JSON内の狙った値をピンポイントで掘り起こしていき、
最終的に JValue型
の粒度まで崩せればゲームセットです。
※ 雰囲気重視の自己解釈です。 厳密には間違ってるかもしれないのでご容赦
-
JObject型
文字列から直接パースした直後の、全体的な JSON を体現 -
JToken型
なにがしかの key から得られた value 部分に相当
配列内の各要素もJToken型
に相当
JToken型
はJArray型
やJValue型
へ変換できる場合がある (変換できなければ例外を飛ばす) -
JArray型
配列部分に相当 -
JValue型
最終的にこのJValue
から、直接的な値を得る
自分自身より下層にオブジェクトが存在しない(=末端,最小粒度である) value に相当
上のJToken型
ないしJArray型
から、このJValue型
へ変換してから使う(値を取り出す)事が殆ど?
上記で挙げた4型のイメージ
※ 自己解釈
順を追った、値の取得方法
1. JSONをパースし、JTokenを引っ張り出す
// サンプルJSON文字列
string _s =
@"
{
'samples':{
'data_str':'hoge',
'data_num':123,
'data_null':null,
'data_undefined':undefined
},
'foo':'bar'
}
";
JObject _jObj = JObject.Parse(_s);
JToken _jtoken = _jObj.SelectToken("samples.data_str");
Console.WriteLine(_jtoken.ToString()); // "hoge"
-
解説
まずJObject.Parse()
でJSON文字列をパースしてJObject
型を得る。
次に、JObject
に生えてる.SelectToken()
を使って 、
目的の value "相当" であるJToken
型をとりあえず引っ張り出します
Json.NET Documentation - JObject Class
Json.NET Documentation - JToken.SelectToken -
余談1 :
JPath式
について
JObject.SelectToken()
を使うとき、引数として
「読み取りたい場所のキー名を階層毎にピリオドで繋いでセレクタっぽくしたもの」をセット。
( これを本ライブラリ的にはJPath式
と呼び、界隈的にはJSONPath
という規格概念なのだとか )
Json.NET Documentation - 複雑な JSON パスを使用した JSON のクエリ -
余談2 : 値取得回収メソッド例
JObject.TryGetValue()
Json.NET Documentation - Object.TryGetValue指定キー名が存在するか
bool
で判断しつつ、
そのキーと対になるvalueが得られれば、第二引数のJToken
に対しout
パラメータ修飾子でデータを渡すJToken _someData; if(_jObj.TryGetValue("hoge" , out _someData)){ Console.WriteLine("hogeキーが存在するので、_someData にvalue相当のJTokenを渡しました"); }
2. JValue
へ変換して単体の値を引っ張り出す
前項で一旦得た JToken
のままでは、目的となる int
/bool
/string
をまだ取得できないので、
更に JValue
型へと変換します。
JValue
にしておけば、そこから JValue.Value
を使って、
Object?
(実質最小粒度) な値を得られます
↓ 全体的な流れはだいたいこんな感じ
JObject
→ JToken
→ JValue
( object?
) → JValue.value
≒ int
, bool
, string
サンプルコード
string _s =
@"
{
'meta':{
'status':201,
}
}
";
JObject jObj = JObject.Parse(_s);
JToken jt = jObj.SelectToken("meta.status");
// ▼ JToken から 201 の値 (int) を得る方法 3種
// (A)
object val_A = ((JValue)jt).Value; // Integer の 201 が得られる
// (B)
JValue jVal_B = (JValue)jt; // JToken を JValue にキャスト
object val_B = jVal_B.Value; // Integer の 201 が得られる
// (C)
JValue jVal_C = jt.ToObject<JValue>(); // ToObject() で変換取り出し
object val_C = jVal_C.Value; // Integer の 201 が得られる
JToken.Value
はあくまで object?
なので、
初っ端から int や string , bool といった型の変数で受け取ろうとすると、IDEに注意されました
まぁ適宜キャスト、nullチェックなどで対応を...
先述のコード例 A~C は全て上手く行きますが、
「JToken
は必ずしも JValue
に変換できるとは限らない」 という点に注意してください
例えば 先述サンプルコードで "status" キーから掘った value(JToken
) は、
キャストなどを経て、数値の 201 を問題なく得られました。
では次の構成を "meta.status" で得た値はどうなるでしょうか?
string _s =
@"
{
'meta':{
'status':{
'foo':123
}
}
}
";
JObject jObj = JObject.Parse(_s);
JToken jt = jObj.SelectToken("meta.status");
// meta.status から得られる値は {'foo':123} のオブジェクトであり、最小粒度の値ではない
object val = ((JValue)jt).Value; // ?
"meta.status" キーから掘って得られた value である JToken
は、
{'foo':123}
のオブジェクトとなるので、最小粒度の値とは言えません。
これを対象に .Value
プロパティでアクセスしようとしても
「いや、もっとバラしてくれ。これは最小粒度のValueではないよ」と拒否されてしまいます。
雑ですが、そういう理屈で value を引っ張り出せません。
そもそも、このケースで {'foo':123}
のオブジェクトである JToken
を JValue
へ変換しようとした時点で、例外がスローされてコケます。
↓ 記事の前部分と画像を部分再掲しておきます。
改めて見ると、ピンとくるんじゃないかなーと思いますが、どうでしょうか?
JToken
はJArray型
やJValue型
へ変換できる場合があるJValue
は末端,最小粒度である value に相当
「JToken
が、更に下層へのオブジェクトを持つか?」(最小粒度の是非) の判断用として、
bool
を返す Jtoken.HasValues
が用意されています。
「このkeyに入るvalue部分、オブジェクトの場合もあるし単体値の場合もあるんだよな~」といった場合、
比較演算子と Jtoken.Type
の併用も判断材料になるでしょう。
配列の場合は JArray
で扱ってから
受け取った JToken
の実態が配列であれば、 JToken
から JArray
へと扱いを変える事で、
List
だの IEnumerable
的なヤツ として、各種要素を受け取れます。
string _s =
@"
{
'arr':[1,2,3,4]
}
";
JObject _jObj = JObject.Parse(_s);
JToken _jt = _jObj.SelectToken("arr");
// .Type プロパティで、実際は JTokenType.Array (JArray型)であることを確認
if (_jt != null && _jt.Type == JTokenType.Array)
{
// JArray にキャストできるので、そのまま配列的なノリで各種要素を取り出せる
JArray _jArr = (JArray)_jt;
foreach (JToken _t in _jArr)
{
// _t を JValue として扱いつつ、好きに調理
// ( ※ 最小粒度の値だから使える手法であることに留意 )
var _val = ((JValue)_t).Value; // Integer にて、各種 1~4が得られる
}
}
※ 配列内の要素に最小粒度の値以外が含まれていれば ((JValue)_t).Value;
の部分でコケるので注意
その他、雑なパース例
「例外とか気にせんよ~!」って感じの、雑で そこそこ短い経路の取得サンプルとか、機能紹介とかです
Example-A
{
"depth_1":
{
"depth_2":
{
"depth_3":"fooValue" // ←これほしい
}
}
}
string _jsonString = "(※上記JSON文字列)";
JObject _jObj = JObject.Parse(_jsonString);
JToken _depth3_JToken = _jObj.SelectToken("depth_1.depth_2.depth_3");
var _depth3_value = ((JValue)_depth3_JToken).Value;
Console.WriteLine(_depth3_value); // "fooValue"
// ※ _depth3_value を明示的に string として扱いたいなら、更に一度変換させたほうが吉
Example-B
{
"a":
[
{"arr":"foo"},
{"arr":"bar"} // ← 配列[1]の中のオブジェクトの、 "arr" キーの value が欲しい
]
}
string _jsonString = "{\"a\":[ {\"arr\":\"foo\"},{\"arr\":\"bar\"}]}";
JObject _jObj = JObject.Parse(_jsonString);
JToken _a_JToken = _jObj.SelectToken("a");
JArray _a_JArr = (JArray)_a_JToken; // 変換
// index 1 ( つまり { "arr":"bar" } )の要素取り出し
JToken _result_JToken = _a_JArr.Value<JToken>(1);
// { "arr":"bar" } の中から、 "arr" のキーに対応する value を探す
var _target_JToken = _result_JToken.SelectToken("arr");
// JValue にキャストして、 .Value で "bar" 取り出す
var _result_Value = ((JValue)_target_JToken).Value;
配列内のIndexを直接指定もできます 😋
string _jsonString = "{\"a\":[ {\"arr\":\"foo\"},{\"arr\":\"bar\"}]}";
JObject _jObj = JObject.Parse(_jsonString);
JToken _a_JToken = _jObj.SelectToken("a[1]"); // ← かゆい所に手が届く👏
var _target_JToken = _a_JToken.SelectToken("arr");
var _result_Value = ((JValue)_target_JToken).Value;
より一層コアなセレクタ(JPath式
)にも対応しています。
実際のところ JSONPath
という規格的な物に準拠している?様で、記述に関する情報だけで言えば、本ライブラリに限らず 探ると色々出てきます
- Google検索 - JSONPath
- Qiita - JSONPath 使い方まとめ
- Json.NET Documentation - Querying JSON with complex JSON Path
Example-C
その階層におけるキー群(と、そこに対となるvalue)を一覧で欲しい場合は
JObject.PropertyValues()
が用意されています
下記で構成された JObject
に対し、このメソッドを使う事で
"meta" と "data" のキーに対応する、それぞれの値(JToken
) を リスト的なもので受け取れます
{
"meta":
{
"status":201,
"errorCode":"CREATED"
}
,
"data":
{
"id":"foobar"
}
}
(下層を掘った後の) JToken
であっても、JObject
にキャストすれば .PropertyValues()
が使えます。
例えば、上記JSONで "meta" を掘った後の value (JToken
) に対して、
var _ProVals = ((JObject)_meta_JToken).PropertyValues();
のようにすれば、"meta" キー以下に属する一覧( ここでいう所の"status"と"errorCode"に関する情報群 )を得られます
そのほか
あんまり長くなるのもアレなんで 手短に紹介しますが、
.Next
/ .Previous
/ .First
/ .Last
とかで、位置基準のデータにアクセスできたり、
.Root
/ .Parent
で、親関係にアクセスできたり、
.Path
で、自分の位置を確認出来たりと、結構便利ですね。
JavaScript畑の自分(←強いて言えば)にとっては、Element
関係を連想しました。
MDN - Element
そもそも C# でJSON を扱うにあたって System.Text.Json という選択肢もあるとは思うのですが、まぁ今回はこちらで...