Wikidataで遊んでみた その2 – C#におけるJSONデータのデシリアライズ
北本です。
前回、C#からWikidataにSPARQL文のクエリを送って、ピアノソナタを書いている作曲家を生年月日順に並べたデータをXML形式やJSON形式で取得しました。しかし、そのままではC#でうまく扱うことが出来ませんので、データをC#のオブジェクトに変換する必要があります。今回は、JSON形式のデータからの変換を取り上げてみたいと思います。
C#でJSONをシリアライズ/デシリアライズする方法はいくつかありますが、ここではSystem.Text.Jsonを使ってみることにします。
System.Text.Jsonは、.NET Core 3.0では共有フレームワークに組み込まれていますが、それ以外の.NETフレームワークで利用する場合は、NuGetパッケージをインストールする必要があります。
インストールの仕方は以下の通りです。
ソリューションエクスプローラーで「参照」を右クリックし「NuGetパッケージの管理」を選択。
NuGetパッケージマネージャーで、「Text.Json」などと検索し、System.Text.Jsonを選んでインストール。
すると、System.Text.Json名前空間が使えるようになります。
JSONデータをオブジェクトに変換するのには、JsonSerializer.Deserializeメソッドを使います。staticなのでインスタンス化の必要はありません。
今回は、引数が(ReadOnlySpan<byte>, Type, JsonSerializerOptions)のものを使います。第1引数にはWikidataから取得したバイト列を渡します。なお、第1引数がStringのものもあるので、文字列に変換後のデータを使うこともできます。第2引数には変換先のオブジェクトの型を渡しますが、これについては後述します。第3引数ではオプションを設定しますが、今回は特に設定の必要がないので何も書かずデフォルトのnullを使います。
第2引数についてですが、JSONのフォーマットに合ったクラスを用意してやる必要があります。入れ子になったクラスを手動で用意しようとすると大変ですが、Visual Studioの機能で一瞬で生成することが可能です。
変換したいJSONのフォーマットで書かれた文字列をコピーし、「編集」>「形式を選択して貼り付け」>「JSONをクラスとして貼り付ける」を実行します。今回コピーするのは、前回のコードでコンソールに出力されたものでよいです。
すると、そのJSONのフォーマットに合うクラスが自動で生成されます。
今回の場合、以下のようなクラスが出来上がります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | public class Rootobject { public Head head { get; set; } public Results results { get; set; } } public class Head { public string[] vars { get; set; } } public class Results { public Binding[] bindings { get; set; } } public class Binding { public Composerlabel composerLabel { get; set; } public Dateofbirth dateOfBirth { get; set; } } public class Composerlabel { public string xmllang { get; set; } public string type { get; set; } public string value { get; set; } } public class Dateofbirth { public string type { get; set; } public object value { get; set; } public string datatype { get; set; } } |
では、前回のコードのMainメソッドを書き換えてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | static void Main(string[] args) { string url = "https://query.wikidata.org/sparql"; byte[] data; using (System.Net.WebClient wc = new System.Net.WebClient()) { System.Collections.Specialized.NameValueCollection ps = new System.Collections.Specialized.NameValueCollection(); ps.Add( "query", @" SELECT DISTINCT ?composerLabel ?dateOfBirth WHERE { ?composer wdt:P106 wd:Q36834; wdt:P569 ?dateOfBirth. ?work wdt:P86 ?composer; wdt:P31 wd:Q1546995. SERVICE wikibase:label { bd:serviceParam wikibase:language ""ja, en"". } } ORDER BY ?dateOfBirth LIMIT 100 "); ps.Add("format", "json"); wc.Headers.Add("User-Agent: Other"); data = wc.UploadValues(url, ps); } Rootobject rootobject = (Rootobject)JsonSerializer.Deserialize(data, typeof(Rootobject)); foreach(Binding binding in rootobject.results.bindings) { Console.WriteLine(string.Format("{0} ({1})", binding.composerLabel.value, binding.dateOfBirth.value)); } } } |
実行すると以下のように出力されます(2020年7月28日に実行したものです)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 作者不明 (t2009759595) フランツ・ヨーゼフ・ハイドン (1732-03-31T00:00:00Z) ヨハン・クリストフ・フリードリヒ・バッハ (1732-06-21T00:00:00Z) ヨハン・クリストフ・フリードリヒ・バッハ (1732-06-23T00:00:00Z) ムツィオ・クレメンティ (1752-01-23T00:00:00Z) ヴォルフガング・アマデウス・モーツァルト (1756-01-27T00:00:00Z) ヤン・ラディスラフ・ドゥシーク (1760-02-12T00:00:00Z) ルートヴィヒ・ヴァン・ベートーヴェン (1770-12-16T00:00:00Z) フランツ・シューベルト (1797-01-31T00:00:00Z) ミハイル・グリンカ (1804-06-01T00:00:00Z) /// 中略 /// ベン・ジョンストン (1926-03-15T00:00:00Z) ジャン・バラケ (1928-01-17T00:00:00Z) Carl Vine (1954-10-08T00:00:00Z) |
前回は特に突っ込んでいませんでしたが、「作者不明」が取得されているのが気になります。どうやら、モーツァルトやベートーヴェンの贋作のピアノソナタの項目があり、それらの作曲者として「作者不明」が取得されたようです。
また、「ヨハン・クリストフ・フリードリヒ・バッハ」が2つ取得されていますが、生年月日が2通り登録されているためそうなってしまいました。
あと、「ミハイル・グリンカ」が取得されていて、グリンカがピアノソナタなんて書いていたっけと思いましたが、これはどうやら彼のヴィオラソナタに属性として「ピアノソナタ」が登録されていたのが原因のようです。
思い通りのデータを綺麗に取ろうとすると更なる工夫が必要ですが、不特定多数の人が編集しているWikidataであるので、なかなか難しいかもしれません。