C#でJSONを読み込むメモ

Webアプリケーションでのデータ構造デファクトスタンダードがJSONなので、.NETというかC#でJSONを読みたいことがままあります。

みんな同じようなことを思うので、ライブラリがちゃんとあって、周辺環境も色々と整備されています。

基本的にはぐぐればおよその情報が出てくるので、メモ程度です。

あ、前の記事で艦これのやりとりするJSONデータが取れたんで、それを料理しましょうって話です。


ライブラリ

.NET Frameworkは3.5から標準ライブラリにJsonReaderWriterFactoryDataContractJsonSerializerの二つが入りました(どちらもSystem.Runtime.Serialization.Json)。これら二つが実装される前は、
Json.NETというライブラリを使うのが安牌だったようです*1

簡単に使えるのないかなーと探すと4.0以降で実装されたdynamicを使った、DynamicJsonというJsonReaderWriterFactoryのラッパライブラリを見つけました。

今回は、Json.NETとDynamicJson、この二つを使って読み込んだ例をメモっておきます。

DynamicJsonの作者さんが日本語で使い方を書いているんで、読んでみるのをおすすめします。


DataContractJsonSerializerは使ったことないんですが、MSDNに記事あるんでリンク貼ります

読んでみる

シリアライズで読み込む

JSONをオブジェクトとみなして.NETのクラスにデシリアライズする形で読み込んでみます。

DataContractJsonSerializerあるいはJson.NETで読み込む場合ですね。わたしはDataContractJsonSerializer使わなかったんですが、多分Json.NETと同じ形式でいけるはず(未確認)。

Json.NETだとJsonConvert.DeserializeObject<T>(string)を使います

using Newtonsoft.Json;

var result = JsonConvert.DeserializeObject<api_req_mission.Result>(json);

こんな感じでさっくり読めるわけですが、クラスapi_req_mission.Resultを定義するのがめんどいよね。

json2csharp - generate c# classes from json

JSONを貼っつけると、そのJSON形式に合ったJson.NETテンプレートクラスを生成するWebサイトがある。神。

例えばこんなかんじに。

f:id:W53SA:20131214015227j:plain

これだとクラスが二つになっちゃってびみょーんなので、ApiDataをRootObjectの内部クラスにして、RootObjectじゃなんだか分からんので名前を変えるとこんな感じ。

public class Result
{
    public class ApiData
    {
        public List<int> api_ship_id { get; set; }
        public int api_clear_result { get; set; }
        public int api_get_exp { get; set; }
        public int api_member_lv { get; set; }
        public int api_member_exp { get; set; }
        public List<int> api_get_ship_exp { get; set; }
        public List<List<int>> api_get_exp_lvup { get; set; }
        public string api_maparea_name { get; set; }
        public string api_detail { get; set; }
        public string api_quest_name { get; set; }
        public int api_quest_level { get; set; }
        public List<int> api_get_material { get; set; }
        public List<int> api_useitem_flag { get; set; }
    }
    public int api_result { get; set; }
    public string api_result_msg { get; set; }
    public ApiData api_data { get; set; }
}

あとはJsonConvert.DeserializeObjectに渡すだけ。やったね。

DataContractJsonSerializerの場合もテンプレート生成するWebサイトがあります。VB.NETも対応してて、これでもJson.NET用テンプレート生成できるんですがjson2csharp.comの方が見てくれ綺麗ですねん。

f:id:W53SA:20131214020146j:plain

ちなみに、このテンプレートクラスには罠があります。それは後ほど。

ツリーをたどる

シリアライズで一気にオブジェクト化するのとは対照的に、JSONオブジェクトをちまちまたぐって読み込む方法もあります。

DynamicJson使ったり、Json.NET使ったりして読めます。

DynamicJson

using Codeplex.Data;

dynamic json = DynamicJson.Parse(JSON);
if ((int)json.api_result != 1)
    return;

dynamic result = json.api_data;
if (result == null)
    return;

var material = (double[])result.api_get_material;

Json.NET

var res = (Newtonsoft.Json.Linq.JObject)JsonConvert.DeserializeObject(json);
var data = (Newtonsoft.Json.Linq.JObject)res2.GetValue("api_data");
var material = data.GetValue("api_get_material").ToArray();

Json.NETはLINQ使って書けるみたいだけど、わたしSQL嫌いなのでスルー。そもそも、このコードでちゃんと読めるのか未検証という適当さ*2

DynamicJsonの場合は前掲した作者さんの記事を読めば十分わかるんで端折る。

まぁ、ここまでの話は前座なんで適当に。

闇鍋対策

これを書くために今までちまちま書いてきたと言っても過言ではない、メインアーティクルです。

何を書きたいかというと

「JSONの値が複数の型を取りうる場合」

具体的には、

"api_get_material":[0,45,0,0]
"api_get_material":-1

何がアレかって、一番目は配列なのに二番目は値が返ってきてること。C#は型を持つ言語なので、どんな型のデータが返ってきてるか判断しないと実行時に例外飛んで死にます。これが先述したです。

シリアライズで読んでる

Json.NETでの場合ですが、objectで受けてRTTI*3で型を取ってきて適切にキャストすることになります。

前出のテンプレートクラスを書き換えます。

public class Result
{
    public class ApiData
    {
        ...
//        public List<int> api_get_material { get; set; }
        public object api_get_material { get; set; }
    }
...

これで、api_get_materialが配列でも値でも死ななくなります。あとは、型を判断してあげましょう。

if (api_data.api_get_material is Newtonsoft.Json.Linq.JArray)
    // 配列だった
else
    // 配列じゃなかった

数字だった場合はint64、配列だった場合はNewtonsoft.Json.Linq.JArrayになっています。

DataContractJsonSerializerでも同じ手法でいけるようです

オブジェクトを手繰って読んでる

DynamicJsonの時はこんな感じ

if(result.api_get_material is Codeplex.Data.DynamicJson)
   //配列だった
else
   //配列じゃなかった

注意点としては、配列型だったら型はCodeplex.Data.DynamicJsonになってることぐらいですか。数値の場合はdouble型になってたんだったかな。

Json.NETの時はこんな感じです

var mat = data.GetValue("api_get_material");
if (mat.Type == Newtonsoft.Json.Linq.JTokenType.Integer)
  //数値だった
else
  //数値でなかった

Json.NETの場合は数値型が複数あります。配列の時は多分Newtonsoft.Json.Linq.JTokenType.Arrayになってるんちゃうかな。


こんなところでしょうか。

*1:もちろんJson.NETは.NET4.0環境でも楽しく使えます。

*2:怪しいのは最後のGetValueあたりですけど。

*3:C#ではリフレクションと呼ぶっぽい。