次の方法で共有


ASP.NET Web API での JSON と XML のシリアル化

この記事では、ASP.NET Web API の JSON フォーマッタと XML フォーマッタについて説明します。

ASP.NET Web API では、 メディア型フォーマッタ は次のことが可能なオブジェクトです。

  • HTTP メッセージ本文から CLR オブジェクトを読み取る
  • HTTP メッセージ本文に CLR オブジェクトを書き込む

Web API は、JSON と XML の両方にメディア型フォーマッタを提供します。 フレームワークは、既定でこれらのフォーマッタをパイプラインに挿入します。 クライアントは、HTTP 要求の Accept ヘッダーで JSON または XML を要求できます。

内容

JSON メディアタイプ フォーマッタ

JSON 書式設定は、 JsonMediaTypeFormatter クラスによって提供されます。 既定では、 JsonMediaTypeFormatterJson.NET ライブラリを使用してシリアル化を実行します。 Json.NET は、サードパーティ製のオープン ソース プロジェクトです。

必要に応じて、Json.NET の代わりに DataContractJsonSerializer を使用するように JsonMediaTypeFormatter クラスを構成できます。 これを行うには、 UseDataContractJsonSerializer プロパティを true に設定します。

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.UseDataContractJsonSerializer = true;

JSON シリアル化

このセクションでは、既定の Json.NET シリアライザーを使用した JSON フォーマッタの特定の動作について説明します。 これは、Json.NET ライブラリの包括的なドキュメントではありません。詳細については、 Json.NET ドキュメントを参照してください

シリアル化される内容

既定では、すべてのパブリック プロパティとフィールドがシリアル化された JSON に含まれます。 プロパティまたはフィールドを省略するには、 JsonIgnore 属性で装飾します。

public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
    [JsonIgnore]
    public int ProductCode { get; set; } // omitted
}

"オプトイン" アプローチを使用する場合は、 DataContract 属性を使用してクラスを装飾します。 この属性が存在する場合、 DataMember がない限り、メンバーは無視されます。 DataMember を使用してプライベート メンバーをシリアル化することもできます。

[DataContract]
public class Product
{
    [DataMember]
    public string Name { get; set; }
    [DataMember]
    public decimal Price { get; set; }
    public int ProductCode { get; set; }  // omitted by default
}

読み取り専用プロパティ

既定では、読み取り専用プロパティがシリアル化されます。

日付

既定では、Json.NET は ISO 8601 形式で日付を書き込みます。 UTC (世界協定時刻) の日付は、"Z" サフィックスで書き込まれます。 現地時刻の日付には、タイム ゾーン オフセットが含まれます。 例えば次が挙げられます。

2012-07-27T18:51:45.53403Z         // UTC
2012-07-27T11:51:45.53403-07:00    // Local

既定では、Json.NET はタイム ゾーンを保持します。 これをオーバーライドするには、DateTimeZoneHandling プロパティを設定します。

// Convert all dates to UTC
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;

ISO 8601 ではなく Microsoft JSON 日付形式 ("\/Date(ticks)\/") を使用する場合は、シリアライザー設定で DateFormatHandling プロパティを設定します。

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.DateFormatHandling 
= Newtonsoft.Json.DateFormatHandling.MicrosoftDateFormat;

インデント

インデントされた JSON を書き込むには、 書式設定設定をFormatting.Indented に設定します。

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;

キャメル ケース

データ モデルを変更せずにキャメル ケースで JSON プロパティ名を書き込むには、シリアライザーで CamelCasePropertyNamesContractResolver を設定します。

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

匿名オブジェクトと弱い型付けオブジェクト

アクション メソッドは、匿名オブジェクトを返して JSON にシリアル化できます。 例えば次が挙げられます。

public object Get()
{
    return new { 
        Name = "Alice", 
        Age = 23, 
        Pets = new List<string> { "Fido", "Polly", "Spot" } 
    };
}

応答メッセージの本文には、次の JSON が含まれます。

{"Name":"Alice","Age":23,"Pets":["Fido","Polly","Spot"]}

Web API がクライアントから緩やかに構造化された JSON オブジェクトを受け取る場合は、要求本文を Newtonsoft.Json.Linq.JObject 型に逆シリアル化できます。

public void Post(JObject person)
{
    string name = person["Name"].ToString();
    int age = person["Age"].ToObject<int>();
}

ただし、通常は、厳密に型指定されたデータ オブジェクトを使用することをお勧めします。 その後、データを自分で解析する必要がなく、モデル検証の利点が得られます。

XML シリアライザーは、匿名型または JObject インスタンスをサポートしていません。 JSON データにこれらの機能を使用する場合は、この記事で後述するように、パイプラインから XML フォーマッタを削除する必要があります。

XMLメディアタイプフォーマッタ

XML 書式設定は、 XmlMediaTypeFormatter クラスによって提供されます。 既定では、 XmlMediaTypeFormatterDataContractSerializer クラスを使用してシリアル化を実行します。

必要に応じて、DataContractSerializer の代わりに XmlSerializer を使用するように XmlMediaTypeFormatter を構成できます。 これを行うには、 UseXmlSerializer プロパティを true に設定します。

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.UseXmlSerializer = true;

XmlSerializer クラスは、DataContractSerializer よりも狭い型セットをサポートしますが、結果の XML をより細かく制御できます。 既存の XML スキーマと一致させる必要がある場合は、 XmlSerializer の使用を検討してください。

XML シリアル化

このセクションでは、既定の DataContractSerializer を使用した XML フォーマッタの特定の動作について説明します。

既定では、DataContractSerializer は次のように動作します。

  • すべてのパブリック読み取り/書き込みプロパティとフィールドがシリアル化されます。 プロパティまたはフィールドを省略するには、 IgnoreDataMember 属性で装飾します。
  • プライベート メンバーと保護されたメンバーはシリアル化されません。
  • 読み取り専用プロパティはシリアル化されません。 (ただし、読み取り専用コレクション プロパティの内容はシリアル化されます)。
  • クラス名とメンバー名は、クラス宣言に表示されるとおりに XML で記述されます。
  • 既定の XML 名前空間が使用されます。

シリアル化をより詳細に制御する必要がある場合は、 DataContract 属性を使用してクラスを装飾できます。 この属性が存在する場合、クラスは次のようにシリアル化されます。

  • "オプトイン" アプローチ: プロパティとフィールドは、既定ではシリアル化されません。 プロパティまたはフィールドをシリアル化するには、 DataMember 属性で装飾します。
  • プライベート メンバーまたは保護メンバーをシリアル化するには、 DataMember 属性で装飾します。
  • 読み取り専用プロパティはシリアル化されません。
  • XML でのクラス名の表示方法を変更するには、DataContract 属性で Name パラメーターを設定します。
  • XML でのメンバー名の表示方法を変更するには、DataMember 属性で Name パラメーターを設定します。
  • XML 名前空間を変更するには、DataContract クラスで Namespace パラメーターを設定します。

読み取り専用プロパティ

読み取り専用プロパティはシリアル化されません。 読み取り専用プロパティにバッキング プライベート フィールドがある場合は、プライベート フィールドを DataMember 属性でマークできます。 この方法では、クラスの DataContract 属性が必要です。

[DataContract]
public class Product
{
    [DataMember]
    private int pcode;  // serialized

    // Not serialized (read-only)
    public int ProductCode { get { return pcode; } }
}

日付

日付は ISO 8601 形式で記述されます。 たとえば、"2012-05-23T20:21:37.9116538Z" などです。

インデント

インデントされた XML を書き込むには、 Indent プロパティを true に設定します。

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.Indent = true;

XML シリアライザーの型ごとの設定

CLR 型ごとに異なる XML シリアライザーを設定できます。 たとえば、下位互換性のために XmlSerializer を必要とする特定のデータ オブジェクトがあるとします。 このオブジェクトには XmlSerializer を使用し、他の型には DataContractSerializer を引き続き使用できます。

特定の型の XML シリアライザーを設定するには、 SetSerializer を呼び出します。

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
// Use XmlSerializer for instances of type "Product":
xml.SetSerializer<Product>(new XmlSerializer(typeof(Product)));

XmlSerializer または XmlObjectSerializer から派生する任意のオブジェクトを指定できます。

JSON または XML フォーマッタの削除

JSON フォーマッタまたは XML フォーマッタを使用しない場合は、フォーマッタの一覧から削除できます。 これを行う主な理由は次のとおりです。

  • Web API の応答を特定のメディアの種類に制限する。 たとえば、JSON 応答のみをサポートし、XML フォーマッタを削除することができます。
  • 既定のフォーマッタをカスタム フォーマッタに置き換えるには たとえば、JSON フォーマッタを JSON フォーマッタの独自のカスタム実装に置き換えることができます。

次のコードは、既定のフォーマッタを削除する方法を示しています。 Global.asax で定義 されているApplication_Start メソッドからこれを呼び出します。

void ConfigureApi(HttpConfiguration config)
{
    // Remove the JSON formatter
    config.Formatters.Remove(config.Formatters.JsonFormatter);

    // or

    // Remove the XML formatter
    config.Formatters.Remove(config.Formatters.XmlFormatter);
}

循環オブジェクト参照の処理

既定では、JSON フォーマッタと XML フォーマッタはすべてのオブジェクトを値として書き込みます。 2 つのプロパティが同じオブジェクトを参照している場合、または同じオブジェクトがコレクションに 2 回表示される場合、フォーマッタはオブジェクトを 2 回シリアル化します。 これは、オブジェクト グラフにサイクルが含まれている場合に特に問題になります。これは、シリアライザーがグラフ内のループを検出したときに例外をスローするためです。

次のオブジェクト モデルとコントローラーについて考えてみましょう。

public class Employee
{
    public string Name { get; set; }
    public Department Department { get; set; }
}

public class Department
{
    public string Name { get; set; }
    public Employee Manager { get; set; }
}

public class DepartmentsController : ApiController
{
    public Department Get(int id)
    {
        Department sales = new Department() { Name = "Sales" };
        Employee alice = new Employee() { Name = "Alice", Department = sales };
        sales.Manager = alice;
        return sales;
    }
}

このアクションを呼び出すと、フォーマッタによって例外がスローされ、その結果としてクライアントには状態コード 500(内部サーバーエラー)の応答が返されます。

JSON でオブジェクト参照を保持するには、Global.asax ファイル 内のメソッドApplication_Start 次のコードを追加します。

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = 
    Newtonsoft.Json.PreserveReferencesHandling.All;

これで、コントローラー アクションは次のような JSON を返します。

{"$id":"1","Name":"Sales","Manager":{"$id":"2","Name":"Alice","Department":{"$ref":"1"}}}

シリアライザーが両方のオブジェクトに "$id" プロパティを追加していることに注意してください。 また、Employee.Department プロパティによってループが作成されることを検出し、値をオブジェクト参照 {"$ref":"1"} に置き換えます。

オブジェクト参照は JSON では標準ではありません。 この機能を使用する前に、クライアントが結果を解析できるかどうかを検討してください。 グラフからサイクルを削除するだけの方が良い場合があります。 たとえば、この例では、従業員から部署へのリンクは実際には必要ありません。

XML でオブジェクト参照を保持するには、2 つのオプションがあります。 より簡単なオプションは、モデル クラスに [DataContract(IsReference=true)] を追加することです。 IsReference パラメーターは、オブジェクト参照を有効にします。 DataContract ではシリアル化オプトインが行われるので、プロパティに DataMember 属性を追加する必要もあります。

[DataContract(IsReference=true)]
public class Department
{
    [DataMember]
    public string Name { get; set; }
    [DataMember]
    public Employee Manager { get; set; }
}

フォーマッタは、次のような XML を生成します。

<Department xmlns:i="http://www.w3.org/2001/XMLSchema-instance" z:Id="i1" 
            xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" 
            xmlns="http://schemas.datacontract.org/2004/07/Models">
  <Manager>
    <Department z:Ref="i1" />
    <Name>Alice</Name>
  </Manager>
  <Name>Sales</Name>
</Department>

モデル クラスの属性を回避する場合は、別のオプションがあります。新しい型固有 の DataContractSerializer インスタンスを作成し、コンストラクターで preserveObjectReferencestrue に設定します。 次に、このインスタンスを XML メディア型フォーマッタの型ごとのシリアライザーとして設定します。 次のコードは、これを行う方法を示しています。

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
var dcs = new DataContractSerializer(typeof(Department), null, int.MaxValue, 
    false, /* preserveObjectReferences: */ true, null);
xml.SetSerializer<Department>(dcs);

オブジェクトのシリアル化のテスト

Web API を設計するときは、データ オブジェクトのシリアル化方法をテストすると便利です。 これは、コントローラーを作成したり、コントローラー アクションを呼び出したりせずに行うことができます。

string Serialize<T>(MediaTypeFormatter formatter, T value)
{
    // Create a dummy HTTP Content.
    Stream stream = new MemoryStream();
    var content = new StreamContent(stream);
    /// Serialize the object.
    formatter.WriteToStreamAsync(typeof(T), value, stream, content, null).Wait();
    // Read the serialized string.
    stream.Position = 0;
    return content.ReadAsStringAsync().Result;
}

T Deserialize<T>(MediaTypeFormatter formatter, string str) where T : class
{
    // Write the serialized string to a memory stream.
    Stream stream = new MemoryStream();
    StreamWriter writer = new StreamWriter(stream);
    writer.Write(str);
    writer.Flush();
    stream.Position = 0;
    // Deserialize to an object of type T
    return formatter.ReadFromStreamAsync(typeof(T), stream, null, null).Result as T;
}

// Example of use
void TestSerialization()
{
    var value = new Person() { Name = "Alice", Age = 23 };

    var xml = new XmlMediaTypeFormatter();
    string str = Serialize(xml, value);

    var json = new JsonMediaTypeFormatter();
    str = Serialize(json, value);

    // Round trip
    Person person2 = Deserialize<Person>(json, str);
}