Newtonsoft Json.NET を雰囲気で使う
概要
C#で(WebAPI経由の)JSON扱いたいなら、とりあえず Newtonsoft.Json 使っとけ、みたいな所 無いですかね...
自分の場合、他所のWebAPIから拾った不定形気味のJSONをパース(デシリアライズとも言う?)して、あれやこれやする事が多いので主にそっち系です
パッケージ諸々
VisualStudio に Nuget 経由でパッケージを突っ込みます。
Newtonsoft.Json
で参照(検索)かければ一番上に出て来るかも?
パッケージインストールしたら、あとは必要に応じて
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
等で、よしなに。
種類抜粋
ソースコード付きじゃないと雰囲気分かりにくいかもしれませんが、この前提知識があると多分扱いやすくなると思います。
以下の型と、その型に紐づいているメソッド等を使って、JSON内の狙った値をピンポイントで掘り起こしていく感じになります。
※ 雰囲気重視の自己解釈です。 厳密には間違ってるかもしれないのでご容赦
-
JObject型
文字列から直接パースした直後の、全体的な JSON を体現 -
JToken型
なにがしかの key を指定して得た、対となる value に相当
配列内の要素もJToken型
に相当
JToken型
はJArray型
やJValue型
へ変換できる場合がある (変換できなければ例外を飛ばす) -
JArray型
配列部分に相当 -
JValue型
最終的にこのJValue
から、直接的な値を得る
自分自身より下層にオブジェクトが存在しない(=末端である) value に相当
上のJToken型
ないしJArray型
から、このJValue型
へ変換してから使う(値を取り出す)事が殆ど?
上記で挙げた4型のイメージ
※ 自己解釈
順を追った、値の取得方法
JSON構成例
この構成をもつ JSON 文字列を例にとり、次項から解説していきます
{
"meta":
{
"status":201,
"errorCode":"CREATED"
}
,
"data":
{
"id":"foobar"
}
}
初手は文字列からパース
Json.NET Documentation - JObject Class
JObject.Parse()
を用いて JSON文字列を JObject
へと変換します。
パース失敗時は、例外 JsonReaderException
が飛んできます
string _hoge = "{\"meta\":{ \"status\":201,\"errorCode\":\"CREATED\"},\"data\":{\"id\":\"foobar\"}";
JObject _jObj = JObject.Parse(_hoge);
JToken
として引っ張り出す
JObject.SelectToken()
Json.NET Documentation - JToken.SelectToken
引数には、読み取りたい場所へのキー名を階層毎にピリオドで繋ぎ、セレクタっぽくしたもの(本ライブラリ的には JPath式
と呼ぶ?)を用います。
界隈的には JSONPath
という規格概念なのだとか。( 別の箇所で少し触れます )
指定したセレクタ先が正しく存在すれば JToken
形式が返るので、それを改めてこねくり回します(後述)
// meta キー → status キー と階層を辿った先の value (的なもの)を得る
// 存在しなければ null
JToken _jtoken = _jObj.SelectToken("meta.status");
// 得た JToken は後でゆっくり調理します😋
指定したセレクタ(JPath式
)先が存在しない場合、平時であれば null
を得るだけですが、
敢えて第二引数を true
にする事で 例外が飛んできます。
// 上記JSON構成例に存在しないキーを指定した場合
JToken _jtoken = _jObj.SelectToken("hoge.huga" , true); // 🚫例外発生
JObject.TryGetValue()
Json.NET Documentation - Object.TryGetValue
指定したキー名が存在するか bool
で判断しつつ、
キーと対になるvalueが得られれば out
パラメータ修飾子にて、第二引数へ配置した JToken
にデータを渡す
// 渡し先を用意
JToken _someData;
// その階層に meta キー名が存在すれば、対応するvalue(JToken) が _someData へセットされる
if(_jObj.TryGetValue("meta" , out _someData)){
Console.WriteLine("metaキーがあったので、_someData にvalue相当のJTokenを渡しました");
}
JObject.GetValue()
Json.NET Documentation - JObject.GetValue
上と似た性質だが、返るのは bool
ではなく JToken
得られない場合は例外が飛んでくるので、自分はこっちより JObject.TryGetValue()
のほうが好き
※ コード例省略
JValue
にしてから値を引っ張り出す
大抵は、JToken
や JArray
(後述)を、一度 JValue
に変換(キャスト)する感じになります。
JValue
にある .Value
プロパティ(フィールドっていうの?) から、Object?
な値を得ます。
JToken.Value
プロパティが返すのはあくまで object?
なので、
初っ端から int や string , bool といった型の変数で受け取ろうとすると、注意されたりします。 ご注意
まぁ適宜キャストなどで対応を...
↓ JValue
にして値を引っ張り出すのはこんな感じ。
// セレクタ(JPath式)から引っ張ってきた JTokenを用意しておく
JToken _jtoken = _jObj.SelectToken("meta.status");
// 取り出し方法A
object _val_A = ((JValue)_jtoken).Value; // Integer の 201 が得られる
// 取り出し方法B
var _jVal_B = (JValue)_jtoken; // JToken を JValue にキャスト
object _val_B = _jVal_B.Value; // Integer の 201 が得られる
// 取り出し方法C
var _jVal_C = _jtoken.ToObject<JValue>(); // ToObject() で変換
object _val_C = _jVal_C.Value;
↑ この例だとA~Cは上手く行きますが、
留意すべきなのは「JToken
は必ずしも JValue
に変換できるとは限らない」 という事です。
// JSON構成例の再掲
{
"meta":
{
"status":201,
"errorCode":"CREATED"
}
,
"data":
{
"id":"foobar"
}
}
例えば 上記 JSON の "status" キーから掘った value(JToken
) は、(キャストなどを経て)数値の 201 として得られますし、
上記例はそれで問題なく取得できました。
しかしこれを "meta" キーから掘ると、得た value(JToken
) は、単体で成り立つ値にはならず、
{ "status":201, "errorCode":"CREATED" }
のオブジェクトを含んだ物となります。
{ "status":201, "errorCode":"CREATED" }
を対象に .Value
プロパティでアクセスしようとしても
「どれが Valueじゃい!?」 ってなりますよね。
雑ですが、そういう理屈で value を引っ張り出せません。
というかそもそも、このケースで "meta" キーから得た JToken
を JValue
へ変換しようとした時点で例外がスローされてコケます。
一応 「得たJToken
が、自身から更に下層へのオブジェクトを持つか?」 というのは Jtoken.HasValues
から得られる bool
にて判別できます。
「このキー名の値、単品の値っていう場合もあるし、オブジェクトが入る場合もあるんだよな~」という場合や、
JValue
変換時の例外に対し保険を掛けたい場合などは、 Jtoken.HasValues
で事前に判断するのも良いかも。
比較演算子と Jtoken.Type
の併用も判断材料になると思います。
配列の場合は JArray
で扱ってから
受け取った value (つまり当該箇所、JToken
) が「配列」であるならば、 JToken
から JArray
へと扱いを変える事で、
List
だの IEnumerable
的なヤツ として、各種要素を受け取れます。
/*
string _jsonStr; に以下が格納されている場合
{
"arr":[1,2,3,4]
}
*/
string _jsonStr = "{\"arr\":[1,2,3,4]}";
JObject _jObj = JObject.Parse(_jsonStr); // パースして
JToken _jt = _jObj.SelectToken("arr"); // arr キーのvalue相当を、一度 JToken 形式で受け取って
// .Type プロパティで、実際は JTokenType.Array (JArray型)であることを確認して
if (_jt != null && _jt.Type == JTokenType.Array)
{
// あとはキャスト後、配列的なノリで各種要素を取り出す
JArray _jArr = (JArray)_jt;
foreach (JToken _t in _jArr)
{
// _t を JValue として扱いつつ、好きに調理
var _val = ((JValue)_t).Value; // Integer にて、各種 1~4が得られる
}
}
但し、先程 JValue
の説明で記した注意同様に、配列の中が単体の値ではない場合は注意が必要です。
上のケースだと JArray
の中身から得た個々の JToken
を JValue
へとキャストできましたが、
下のケースなら JArray
から得たJToken
はまだオブジェクトの状態ゆえ JValue
として扱えなくなるので、そこを考慮しないと例外が飛んできます。
/*
配列の要素がオブジェクトの場合
{
"arr":[{"a":1},{"b":2},{"c":3},{"d":4}]
}
*/
string _jsonStr = "{\"arr\":[{\"a\":1},{\"b\":2},{\"c\":3},{\"d\":4}]}";
...中略...
JArray _jArr = (JArray)_jt;
foreach (JToken _t in _jArr)
{
// ↓ これだと JValue にキャストできないので例外が発生する
// ※ 自身の要素が末端の値ではなく、更に下層へ続くオブジェクトの状態なので、JValue にはなれない。
var _val = ((JValue)_t).Value;
}
etc Example
「例外とか気にせんよ~!」って感じの、雑で そこそこ短い経路の取得サンプルとか、機能紹介とかです
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()
が用意されています
このメソッドを下記のJSON(というか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 という選択肢もあるとは思うのですが、まぁ今回はこちらで...