C#におけるJSONファイルの読み込み
C#で、System.Text.Jsonを使った動的読込がやりたい。(言葉が合っているか不明だけど。)
Google Books APIを叩いて、戻ってきたJSON形式の情報で必要なところだけ切り出したいのです。
1.参照元の記述形式
APIで取得できるJSONは、たとえば以下のような形。
{
"kind": "books#volumes",
"totalItems": 1,
"items": [
{
"kind": "books#volume",
"id": "YKLhMQEACAAJ",
"etag": "9QD7EaF1Yb8",
"selfLink": "https://www.googleapis.com/books/v1/volumes/YKLhMQEACAAJ",
"volumeInfo": {
"title": "まほろ駅前番外地",
"authors": [
"三浦しをん"
],
"publishedDate": "2012-10",(後略)
取得するためのアドレスは以下の通り。isbnの後ろ側に固有のコードを入れれば良しという感じ。
https://www.googleapis.com/books/v1/volumes?q=isbn:9784167761028
2.デシリアライズの方法模索
色々と検索するとJSON形式のデシリアライズのためにクラスを用意して……とかあるんだけど、正直言って面倒。
必要だと思っているのは、titleの部分だとか、authorsの部分。JSON読み込んで名前「title」とか指定したら「まほろ駅前番外地」とか値が欲しい。対応するクラスを用意するのはちょっとやだ。
探し方が悪いのか、なかなか目当ての情報がなかった。検索と公式情報、ついでにトライアンドエラーで(正解かどうかは知らないけども)テキストから読み込んだ値が取れるようになったので、備忘録を兼ねて、ここに残します。
API叩いて取得するところは実装していないので、今回はローカルに配置したファイルを読み込んで処理するところを記述します。
3.実装コード(参照設定/Const定義)
参照設定は以下の通り。
using System;
using System.IO;
using System.Collections.Generic;
using System.Reflection;
using System.Text.Json;
ファイル読み込みは、以下の通り。但し、これは「Xamarin.Forms でのファイル処理」の「埋め込みリソースファイルの読み込み」の章を転記して利用しています。(コンソールアプリケーションで実装を試したけど、最終的にはXamarin.Formsで動くようにする予定)
var assembly = IntrospectionExtensions.GetTypeInfo(typeof(MainClass)).Assembly;
Stream stream = assembly.GetManifestResourceStream("textJson.gapi_sample.json");
string text = "";
using (var reader = new System.IO.StreamReader(stream))
{
text = reader.ReadToEnd();
}
注意点としては、最初のtypeofには、自分が作成したクラス名を使うらしい。
ファイル名はプロジェクトに組み込んだ後のリソースIDを利用します。
そして取得したい値の箇所については、Constで定義。JSON内で使われていて、自分が必要と思うところを書き出した。
// BooksInfo
const string TotalItems = "totalItems";
const string Items = "items";
// BookInfo
const string I_ID = "id";
const string I_VolumeInfo = "volumeInfo";
// BookInfo-Details
const string IV_Title = "title";
const string IV_Authors = "authors";
const string IV_PublishedDate = "publishedDate";
const string IV_PageCount = "pageCount";
// BookInfo-ISBN
const string IV_IndustryIdentifiers = "industryIdentifiers";
const string IVI_Type = "type";
const string IVI_Identifier = "identifier";
4.実装コード(JSON値取得部分)
そして、値の取得の実装は、以下の通り。
var jsonElems = JsonDocument.Parse(text);
var elmLength = jsonElems.RootElement.GetProperty(TotalItems).GetInt32();
if (elmLength == 0)
return;
// 書籍情報ツリーを取得
var bookInfo = jsonElems.RootElement.GetProperty(Items)[0];
var bookInfoDetails = bookInfo.GetProperty(I_VolumeInfo);
// 情報展開
Console.WriteLine(I_ID + " : " + bookInfo.GetProperty(I_ID));
Console.WriteLine(IV_Title + " : " + bookInfoDetails.GetProperty(IV_Title));
Console.WriteLine(IV_Authors + " : " + ElementArrayConcat(bookInfoDetails.GetProperty(IV_Authors)));
Console.WriteLine(IV_PublishedDate + " : " + bookInfoDetails.GetProperty(IV_PublishedDate));
Console.WriteLine(IV_PageCount + " : " + bookInfoDetails.GetProperty(IV_PageCount));
string[] prmNames = { IVI_Type, IVI_Identifier };
Console.WriteLine(IV_IndustryIdentifiers + " : "
+ ElementArrayConcat(bookInfoDetails.GetProperty(IV_IndustryIdentifiers), prmNames));
要素途中の配列部分とか、子要素の並びの部分は適当にプライベートメソッドで抽出。もうちょっと上手く纏められないかな、って感じだけど、今は、とりあえずこれで良い。
// 要素が配列になっているもの
private string ElementArrayConcat(JsonElement elm)
{
var arrayLength = elm.GetArrayLength();
List<string> wkstrs = new List<string>();
for (int i = 0; i < arrayLength; i++)
{
wkstrs.Add(elm[i].ToString());
}
return string.Join<string>(",", wkstrs);
}
// 子要素があるもの
private string ElementArrayConcat(JsonElement elm, string[] args)
{
var arrayLength = elm.GetArrayLength();
List<string> wkstrs = new List<string>();
for (int i = 0; i < arrayLength; i++)
{
List<string> buf = new List<string>();
foreach (string arg in args)
{
buf.Add(elm[i].GetProperty(arg).ToString());
}
// 引数分の値を連結格納
wkstrs.Add(string.Join<string>("/", buf));
}
return string.Join<string>(",", wkstrs);
}
5.実行結果
実行すると、以下出力。
単純なJSONからの値取得だけど、なんだか感慨深い。ちなみに手元の本を捲ってみたら解説の終わりが299ページだったので、情報としても合っていますね。
やっている途中から、DOMのそれに近いなぁって思っていた。
元々がWeb系のそれだから、近くても驚きはないけれども。
6.補足的な覚え書き
単純な構造ならば「jsonElems.RootElement.EnumerateObject()」をforeachで廻しても値は取れそうだった。取った要素が配列でとか、その下にまだ子要素が……となると、さらにforeachを重ねないといけなさそうだけど。