やっさんの雑記

プログラミングでやってみたこととか

C#でJSONのパース

こんばんはおはようございますこんにちは

今回はC#でJSONのパースをしてみたいと思います。

neueccさんの作られたDynamicJsonという素晴らしいライブラリもありますが、ここでは標準ライブラリを使うことにします。

標準ライブラリでJSONのパースをするには、DataContractJsonSerializerクラスを使います。

なお、.NET Framework 4.0以上が必須です。

まずは使ってみる

使い方はとても簡単で、取得したいオブジェクトの型でDataContractJsonSerializerのインスタンスを初期化して、文字列をMemoryStream経由で読み込ませるだけです。

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;

[DataContract]
class Data
{
    [DataMember]
    public int a;

    [DataMember]
    public double d;

    [DataMember]
    public string s;
}

class Program
{
    static void Main()
    {
        var json = @"{""a"":123, ""s"":""test!"", ""pi"":3.14}";
        var serializer = new DataContractJsonSerializer(typeof(Data));
        using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(json))) {
            var data = (Data)serializer.ReadObject(ms);
            Console.WriteLine(data.a);
            Console.WriteLine(data.d);
            Console.WriteLine(data.s);
        }
    }
}

このように、JSONデータの中に存在しないフィールド・プロパティがあるなどJSONデータとオブジェクトが完全には一致していなくても、デフォルト値を入れるなど適当に処理してくれます。

また、読み込ませたいオブジェクトにはDataContractAttributeを、読み込むフィールド・プロパティにはDataMemberAttributeをつけないといけないことに注意しましょう。DataMemberAttributeをつけていないフィールドやプロパティはJSONから読み込まれません。

DataMemberAttributeさえつけていれば、publicだけではなく例えばprivateなフィールド・プロパティでも読み込んでくれます。

Dictionaryの読み込み

このDataContractJsonSerializerは、組み込み型でもユーザ定義型でもだいたい読み込んでくれます。

ただし、データをDictionaryとして読み込みたいときは少しコードを変えないといけません。

具体的に何をするかというと、DataContractJsonSerializerSettingsのインスタンスを作って、UseSimpleDictionaryFormatプロパティをtrueにして、DataContractJsonSerializerのインスタンスを作ります。

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;

[DataContract]
class Data
{
    [DataMember]
    public Dictionary<string, double> xs;
}

class Program
{
    static void Main()
    {
        var json = @"{""xs"":{""1"":1, ""e"":2.718, ""pi"":3.14}}";
        var settings = new DataContractJsonSerializerSettings();
        settings.UseSimpleDictionaryFormat = true;
        var serializer = new DataContractJsonSerializer(typeof(Data), settings);
        using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(json))) {
            var data = (Data)serializer.ReadObject(ms);
            foreach (var x in data.xs) {
                Console.WriteLine("{0}\t{1}", x.Key, x.Value);
            }
        }
    }
}

読み込み時に自動的にデータを変換する

TwitterAPIを叩いたときに返ってくるJSONデータの中には、created_atプロパティのように「中身は実質的にDateTimeだけど、JSONに載せる都合上stringで送られてくる」ものも存在します(JSONに載せられるデータ型は実質的に、数値と文字列とそれらの配列だけです)。

中身は実質的にDateTimeなので、JSONを読み込むと自動的にstringからDateTimeに変換してほしいものですが、実際にはそんなに簡単にはいきません。

というのも、Twitterから送られてくるcreated_atプロパティの中のフォーマットがDateTimeのデフォルトの文字列解釈のフォーマットと異なるため、そのままではちゃんと読み込んでくれないのですね。

そこで、DataMemberAttributeをつけたものしか読み込まないという仕様をうまく使って、データ読み込み時に自動的にデータ変換するようにします。

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;

[DataContract]
class Data
{
    [DataMember(Name = "created_at")]
    private string created_at_str_prop
    {
        get
        {
            return created_at_str_field;
        }

        set
        {
            created_at_field = DateTime.ParseExact(value, "ddd MMM dd HH:mm:ss zzz yyyy", DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None);
            created_at_str_field = value;
        }
    }
    private string created_at_str_field;
    public DateTime created_at
    {
        get
        {
            return created_at_field;
        }

        set
        {
            created_at_str_field = value.ToString("ddd MMM dd HH:mm:ss zzz yyyy", DateTimeFormatInfo.InvariantInfo);
            created_at_field = value;
        }
    }
    private DateTime created_at_field;
}

class Program
{
    static void Main()
    {
        var json = @"{""created_at"":""Wed Sep 11 20:01:35 +0000 2013""}";
        var serializer = new DataContractJsonSerializer(typeof(Data));
        using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(json))) {
            var data = (Data)serializer.ReadObject(ms);
            Console.WriteLine(data.created_at);
        }
    }
}

JSONデータを読み込むときに、DataMemberAttiributeがついているcreated_at_str_propプロパティのsetterが呼び出されるので、その中でデータを変換しているわけです。

Data.created_atプロパティ以外はクラスの外からは見えないので、あたかもJSONを読み込むと自動的にstringをDateTimeに変換しているように見えるわけですね。

また、今まで特に触れていませんでしたが、DataMemberAttributeをつけるときに、Name = "hogehoge"と指定することで、JSONデータ中のフィールド名を指定できます。

最後に

これでDataContractJsonSerializerを使ってJSON形式のデータを読み込む方法の紹介がざっと終わったわけですが、書き込むときもReadObjectメソッドではなくWriteObjectメソッドを使って同じようにすればできるみたいです。