プロフィール

髭山髭人(ひげひと)

自分の書いた記事が、一人でも誰かの役に立てば...
活動信条の一つとして「貴方のメモは、誰かのヒント」というのがあります。

このサイトについて

本家HP packetroom.net から切り離した いわゆる技術メモ用のブログで、無料レンタルサーバーにて運用しています。広告表示はその義務なのでご容赦。
XREA さんには長年お世話になっています

Newtonsoft Json.NET を雰囲気で使う(パース)

概要

C#で(WebAPI経由の)JSON扱いたいなら、とりあえず Newtonsoft.Json 使っとけ、みたいな所 無いですかね...

自分の場合、他所のWebAPIから拾った不定形気味のJSONをパース(デシリアライズとも言う?)して、あれやこれやする事が多いので主にそっち系です

パッケージ諸々

VisualStudioNuget 経由でパッケージを突っ込みます。

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型のイメージ

※ 自己解釈

順を追った、値の取得方法

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() を使って文字列をパースし、JObject 型を出力させます
Json.NET Documentation - JObject Class

次に、得られた JObject に生えてる .SelectToken() を使って、
目的の value "相当" である JToken 型をとりあえず引っ張り出します
Json.NET Documentation - JToken.SelectToken

JObject.SelectToken() を使う際、
引数として「読み取りたい場所のキー名を階層毎にピリオドで繋いでセレクタっぽくしたもの」をセット。
( これを本ライブラリ的には JPath式 と呼び、界隈的には JSONPath という規格概念なのだとか )

指定セレクタ(JPath式)先が存在しない場合、平時なら null を得るだけですが、
第二引数を true にする事で例外を飛ばせます。

// 存在しないキーを指定した場合
JToken _jtoken = _jObj.SelectToken("aaa.bbb" , true);   // 🚫例外発生

ほか、取得/回収メソッド例

  • 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を渡しました");
    }
  • JObject.GetValue()
    Json.NET Documentation - JObject.GetValue

    上と似た性質だが、返るのは bool ではなく JToken
    得られない場合は例外が飛んでくるので、自分はこっちより JObject.TryGetValue() のほうが好き
    ( ※ コード例省略 )

2. JValue へ変換して単体の値を引っ張り出す

前項で JToken を得ましたが、この型では目的の int/bool/string をまだ取得できないので、
更に JToken から JValue 型へと変換します。

JValue にしておけば、そこから JValue.Value を使って、
Object? (実質最小粒度) な値を得られます

↓ 全体的な流れはだいたいこんな感じ
JObjectJTokenJValue ( object? ) → JValue.valueint , 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に注意されました
まぁ適宜キャストなどで対応を...

閑話休題。
先述のコード例 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 を引っ張り出せません。

そもそも、このケースで "meta.status" から得た JTokenJValue へ変換しようとした時点で例外がスローされてコケてしまいます。

↓ 記事の前部分と画像を部分再掲しておきます。
改めて見ると、ピンとくるんじゃないかなーと思いますが、どうでしょうか?

  • JTokenJArray型JValue型 へ変換できる場合がある
  • JValue は末端,最小粒度である value に相当

JToken が、更に下層へのオブジェクトを持つか?」(最小粒度の是非) というのは、
Jtoken.HasValues で返る bool にて判別できます。

「このキー名に入る値、最小粒度の場合と、オブジェクトが入る場合の どちらもあり得るんだよな~」という場合や、
JValue 変換時の例外保険を掛けたい場合は、 Jtoken.HasValues で事前に判断するのも良いかも。
比較演算子と Jtoken.Type の併用も判断材料になると思います。

配列の場合は JArray で扱ってから

newtonsoft.com - JArray Class

受け取った 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; の部分でコケるので注意

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 という規格的な物に準拠している?様で、記述に関する情報だけで言えば、本ライブラリに限らず 探ると色々出てきます

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 という選択肢もあるとは思うのですが、まぁ今回はこちらで...