C#(VS2008)の標準機能のみでJSONを読み込む

Microsoft Visual C# 2008 の標準機能のみでJSONを読み込む方法について調べた。

最終目標は C# で Web API を叩いて得られた JSON を読み込むこと。

以下のような JSON を読み込むものとする。

お天気Webサービス仕様 - Weather Hacks - livedoor 天気情報

Visual Studio、というか .NET Framework はバージョンによって大きく機能が異なるため、 まず利用する .NET Framework のバージョンを押さえておく必要がある。

Visual Studio 2008に含まれる.NET Frameworkのバージョンは「3.5」。 この場合、DataContractJsonSerializerを使うのが一般的なようだ。

ちなみに、.NET Framework 4.0 以降で実装された dynamic を使った、DynamicJson を用いるともっと楽にできるが、今回は題記の条件により見送る。

DataContractJsonSerializerを使う準備

参照の追加

Visual Studio で、プロジェクトの参照設定を右クリック→参照の追加で、 以下を追加する。

System.Runtime.Serialization
System.ServiceModel.Web
名前空間を定義
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
ここまでのはまりポイント

最初、System.Runtime.Serialization.Json を用いるため、 System.Runtime.Serialization.Json が参照の追加にあると思った。

しかし、実際には System.Runtime.Serialization.Json は参照追加の項目には存在せず、 System.ServiceModel.Web を参照追加するのが正しい。

ちなみに System.ServiceModel.Web を参照追加せず、 using System.Runtime.Serialization.Json した場合、以下のエラーが出る。

f:id:posnum:20140919224930j:plain

このあたりは非常にややこしいので注意。

参考: JSONとクラスのマッピング - Paradigm Shift Design

読み込む JSON に応じたクラスを用意する

まず、読み込む JSON に応じたクラスを用意する必要がある。 今回は以下にちゃんとしたドキュメントがあるので、それに従ってクラスを作る。

お天気Webサービス仕様 - Weather Hacks - livedoor 天気情報

最初にデータ全体のクラスを用意する。 クラスに[DataContract]をつけて定義するのがポイント。

[DataContract]
public class Weather
{
}

続いて、メンバであるが、今回読み込むのは入れ子になっていて結構ややこしい。 まずは入れ子になっていないものから考える。

例えば、title, link, publicTime というプロパティは以下のように定義する。 メンバに[DataMember]をつけて定義するのがポイント。

[DataContract]
public class Weather
{
    [DataMember]
    public String title { get; set; }
    [DataMember]
    public String link { get; set; }
    [DataMember]
    public String publicTime { get; set; }
}

入れ子になっているメンバはどうするかと言うと、 さらに[DataContract]をつけたクラスを定義する。

例えば、location だと以下のようになる。

[DataContract]
public class Weather
{
    [DataMember]
    public Location location { get; set; }

    //...

    [DataContract]
    public class Location
    {
        [DataMember]
        public string area { get; set; }
        [DataMember]
        public string pref { get; set; }
        [DataMember]
        public string city { get; set; }
    }
    
    //...

}

プロパティが配列になっている場合は、そのまま配列で定義すれば良い。

[DataContract]
public class Weather
{
    //...
    [DataMember]
    public Forecasts[] forecasts { get; set; }
    //...
}

ちなみに、ここでは単純化のためにすべて string 型で定義している。

プリミティブ型(int、floatなど)を用いた場合、null があると面倒なので 必要に応じて後でキャストする方針。

このような感じで全部のプロパティを定義していく。

ここでのはまりポイント
  • pinpointLocation ではなく、pinpointLocations

  • copyright の provider が実は配列

  • temperature は max, min それぞれが celsius, fahrenheit というプロパティを持っている

ここでは、C# というより Livedoor Weather Web Service のドキュメントに惑わされた。

実際の読み出し

using System.IO;
using System.Net;

// (省略)

// Livedoor Weather Web Service の基本URL + 久留米のID(400040)
string url = "http://weather.livedoor.com/forecast/webservice/json/v1?city=400040";

// リクエストを作成
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
// レスポンスを取得
HttpWebResponse webres = (HttpWebResponse)req.GetResponse();
// ストリームを取得
Stream st = webres.GetResponseStream();
// 取得したデータを格納するための変数を用意
Weather weather;

using (st)
{
    // DataContractJsonSerializer のインスタンスを作成
    var serializer = new DataContractJsonSerializer(typeof(Weather));
    // JSON を読み込む
    weather = (Weather)serializer.ReadObject(st);
}

これだけで取得したデータがちゃんと C# のクラスに格納される。 最初の定義が面倒だけど、かなり便利。

Visual Studioブレークポイント貼って、 ウォッチ式で weather にちゃんと格納されているのを確認した。

ソースは整えて後日アップしようと考え中。