【ASP.NET Web API教程】6.2 ASP.NET Web API中的JSON和XML序列化

謹以此文感謝關注此係列文章的園友!前段時間本覺得此係列文章已沒多少人關注,而不打算繼續下去了。由於文章貼出來以後,看的人彷佛很少,也不多有人對這些文章發表評論,並且幾乎無人給予「推薦」。但前幾天有人詢問爲什麼好久沒有更新,這讓我感受把這文章翻譯出來仍是有價值的。爲此,本人打算將此工做繼續下去。這些關於Web API的技術文章均由微軟專業人員撰寫,雖然文章做爲博客帖子而寫得比較簡單,但若是仔細揣摩其內容,應當仍是可以有很多收穫的,也但願個人這些譯文可以使那些對Web API有興趣的園友從中獲得一些收穫。web

儘管最近正忙於《Pro ASP.NET MVC 4》一書的翻譯,但我仍會將此係列文章繼續下去,只不過可能不會仔細檢查,譯文必定會有比較多的錯誤,懇請讀者諒解。json

6.2 JSON and XML Serialization in ASP.NET Web API
6.2 ASP.NET Web API中的JSON和XML序列化

本文引自:http://www.asp.net/web-api/overview/formats-and-model-binding/json-and-xml-serializationapi

By Mike Wasson|May 30, 2012
做者:Mike Wasson|2012-3-30服務器

This article describes the JSON and XML formatters in ASP.NET Web API.
本文描述ASP.NET Web API中的JSON和XML格式化器。app

In ASP.NET Web API, a media-type formatter is an object that can:
在ASP.NET Web API中,媒體類型格式化器(Media-type Formatter)是一種可以作如下工做的對象:框架

  • Read CLR objects from an HTTP message body
    從HTTP消息體讀取CLR(公共語言運行時)對象
  • Write CLR objects into an HTTP message body
    將CLR對象寫入HTTP消息體

Web API provides media-type formatters for both JSON and XML. The framework inserts these formatters into the pipeline by default. Clients can request either JSON or XML in the Accept header of the HTTP request.
Web API提供了用於JSON和XML的媒體類型格式化器。框架已默認將這些格式化器插入到消息處理管線之中。客戶端在HTTP請求的Accept報頭中能夠請求JSON或XML。asp.net

Contents
本小節內容
less

  • JSON Media-Type Formatter(JSON媒體類型格式化器)
    • Read-Only Properties(只讀屬性)
    • Dates(日期)
    • Indenting(縮進)
    • Camel Casing(駝峯式大小寫轉換)
    • Anonymous and Weakly-Typed Objects(匿名及弱類型對象)
  • XML Media-Type Formatter(XML媒體類型格式化器)
    • Read-Only Properties(只讀屬性)
    • Dates(日期)
    • Indenting(縮進)
    • Setting Per-Type XML Serializers(設置每一個類型的XML序列化器)
  • Removing the JSON or XML Formatter(去除JSON或XML格式化器)
  • Handling Circular Object References(處理循環對象引用)
  • Testing Object Serialization(測試對象序列化)

6.2.1 JSON Media-Type Formatter
6.2.1 JSON媒體類型格式化器

JSON formatting is provided by the JsonMediaTypeFormatter class. By default, JsonMediaTypeFormatter uses the Json.NET library to perform serialization. Json.NET is a third-party open source project.
JSON格式化是由JsonMediaTypeFormatter類提供的。默認狀況下,JsonMediaTypeFormatter使用Json.NET庫執行序列化工做。Json.NET是一個第三方開源項目。ide

If you prefer, you can configure the JsonMediaTypeFormatter class to use the DataContractJsonSerializer instead of Json.NET. To do so, set the UseDataContractJsonSerializer property to true:
若是喜歡,你能夠將JsonMediaTypeFormatter配置成使用DataContractJsonSerializer來代替Json.NET。要想這麼作,只需UseDataContractJsonSerializer將屬性設置爲true便可:oop

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

6.2.2 JSON Serialization
6.2.2 JSON序列化

This section describes some specific behaviors of the JSON formatter, using the default Json.NET serializer. This is not meant to be comprehensive documentation of the Json.NET library; for more information, see the Json.NET Documentation.
本小節描述,在使用默認的Json.NET序列化器時,JSON格式化器的一些特定行爲。這並不意味着要包含Json.NET庫的整個文檔。更多信息參閱Json.NET Documentation。

What Gets Serialized?
什麼會被序列化?

By default, all public properties and fields are included in the serialized JSON. To omit a property or field, decorate it with the JsonIgnore attribute.
默認狀況下,全部public屬性和字段都會被包含在序列化的JSON中。爲了忽略一個屬性或字段,須要用JsonIgnore註解屬性修飾它。

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

If you prefer an "opt-in" approach, decorate the class with the DataContract attribute. If this attribute is present, members are ignored unless they have the DataMember. You can also use DataMember to serialize private members.
若是你更喜歡「opt-in(選入)」方法,能夠用DataContract註解屬性來修飾類。若是有註解屬性,則成員均被忽略,除非有DataMemberDataMember也能夠序列化private成員。

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

Read-Only Properties
只讀屬性

Read-only properties are serialized by default.
只讀屬性默認是序列化的。

Dates(日期)

By default, Json.NET writes dates in ISO 8601 format. Dates in UTC (Coordinated Universal Time) are written with a "Z" suffix. Dates in local time include a time-zone offset. For example:
默認狀況下,Json.NET會將日期寫成ISO 8601格式。UTC(Coordinated Universal Time — 世界標準時間)格式的日期書寫時帶有後綴「Z」。本地時間格式的日期包括了一個時區偏移量。例如:

2012-07-27T18:51:45.53403Z         // UTC(標準時間)
2012-07-27T11:51:45.53403-07:00    // Local(本地時間)

By default, Json.NET preserves the time zone. You can override this by setting the DateTimeZoneHandling property:
默認狀況下,Json.NET保留時區。經過設置DateTimeZoneHandling屬性,能夠重寫這一行爲:

// Convert all dates to UTC
// 將全部日期轉換成UTC格式
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.DateTimeZoneHandling =
     Newtonsoft.Json.DateTimeZoneHandling.Utc;

If you prefer to use Microsoft JSON date format ("\/Date(ticks)\/") instead of ISO 8601, set the DateFormatHandling property on the serializer settings:
若是你喜歡使用微軟的JSON日期格式("\/Date(ticks)\/ ")而不是ISO 8601,能夠在SerializerSettings上設置DateFormatHandling屬性:

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

Indenting(縮進)

To write indented JSON, set the Formatting setting to Formatting.Indented:
爲了書寫有縮進的JSON,能夠將Formatting設置爲Formatting.Indented

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

Camel Casing(駝峯式大小寫轉換)

To write JSON property names with camel casing, without changing your data model, set the CamelCasePropertyNamesContractResolver on the serializer:
爲了在不修改數據模型的狀況下,用駝峯式大小寫轉換JSON的屬性名,能夠設置序列化器上的CamelCasePropertyNamesContractResolver

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

Anonymous and Weakly-Typed Objects
匿名類型與弱類型對象

An action method can return an anonymous object and serialize it to JSON. For example:
動做方法或以返回一個匿名對象,並將其序列化成JSON。例如:

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

The response message body will contain the following JSON:
響應消息體將含有如下JSON:

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

If your web API receives loosely structured JSON objects from clients, you can deserialize the request body to a Newtonsoft.Json.Linq.JObject type.
若是Web API從客戶端接收了鬆散結構的JSON,你能夠將該請求體解序列化成Newtonsoft.Json.Linq.JObject類型。

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

However, it is usually better to use strongly typed data objects. Then you don't need to parse the data yourself, and you get the benefits of model validation.
然而,一般更好的是使用強類型數據對象。那麼,便不須要自行對數據進行解析,而且能獲得模型驗證的好處。

The XML serializer does not support anonymous types or JObject instances. If you use these features for your JSON data, you should remove the XML formatter from the pipeline, as described later in this article.
XML序列化器不支持匿名類型或JObject實例。若是將這些特性用於JSON數據,應該去掉管線中的XML格式化器,如本文稍後描述的那樣。

6.2.3 XML Media-Type Formatter
6.2.3 XML媒體類型格式化器

XML formatting is provided by the XmlMediaTypeFormatter class. By default, XmlMediaTypeFormatter uses the DataContractSerializer class to perform serialization.
XML格式化是由XmlMediaTypeFormatter類提供的。默認狀況下,XmlMediaTypeFormatter使用DataContractSerializer類來執行序列化。

If you prefer, you can configure the XmlMediaTypeFormatter to use the XmlSerializer instead of the DataContractSerializer. To do so, set the UseXmlSerializer property to true:
若是喜歡,你能夠將XmlMediaTypeFormatter配置成使用XmlSerializer而不是DataContractSerializer。要想這麼作,可將UseXmlSerializer屬性設置爲true

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

The XmlSerializer class supports a narrower set of types than DataContractSerializer, but gives more control over the resulting XML. Consider using XmlSerializer if you need to match an existing XML schema.
XmlSerializer類支持的類型集要比DataContractSerializer更窄一些,但對結果XML有更多的控制。若是須要與已有的XML方案匹配,可考慮使用XmlSerializer

6.2.4 XML Serialization
6.2.4 XML序列化

This section describes some specific behaviors of the XML formatter, using the default DataContractSerializer.
本小節描述使用默認DataContractSerializer的時,XML格式化器的一些特殊行爲。

By default, the DataContractSerializer behaves as follows:
默認狀況下,DataContractSerializer行爲以下:

  • All public read/write properties and fields are serialized. To omit a property or field, decorate it with the IgnoreDataMember attribute.
    序列化全部public讀/寫屬性和字段。爲了忽略一個屬性或字段,請用IgnoreDataMember註解屬性修飾它。
  • Private and protected members are not serialized.
    private和protected成員不做序列。
  • Read-only properties are not serialized.
    只讀屬性不做序列化
  • Class and member names are written in the XML exactly as they appear in the class declaration.
    類名和成員名按類聲明中的確切呈現寫入XML
  • A default XML namespace is used.
    使用XML的默認命名空間

If you need more control over the serialization, you can decorate the class with the DataContract attribute. When this attribute is present, the class is serialized as follows:
若是須要在序列化上的更多控制,能夠用DataContract註解屬性修飾類。當這個註解屬性出現時,該類按以策略序列化:

  • "Opt in" approach: Properties and fields are not serialized by default. To serialize a property or field, decorate it with the DataMember attribute.
    「Opt in(選入)」方法:屬性與字段默認不被序列化。爲了序列化一個屬性或字段,請用DataMember註解屬性修飾它。
  • To serialize a private or protected member, decorate it with the DataMember attribute.
    要序列化private或protected成員,請用DataMember註解屬性修飾它。
  • Read-only properties are not serialized.
    只讀屬性不被序列化。
  • To change how the class name appears in the XML, set the Name parameter in the DataContract attribute.
    要改變類名在XML中的呈現,請在DataContract註解屬性中設置Name參數。
  • To change how a member name appears in the XML, set the Name parameter in the DataMember attribute.
    要改變成員名在XML中的呈現,請設置DataMember註解屬性中的Nmae參數
  • To change the XML namespace, set the Namespace parameter in the DataContract class.
    要改變XML命名空間,請設置DataContract類中的Namespace參數。

Read-Only Properties
只讀屬性

Read-only properties are not serialized. If a read-only property has a backing private field, you can mark the private field with the DataMember attribute. This approach requires the DataContract attribute on the class.
只讀屬性是不被序列化的。若是隻讀屬性有一個支撐private字段,能夠用DataMember註解屬性對這個private字段進行標記。這種辦法須要在類上使用DataContract註解屬性。

[DataContract]
public class Product
{
    [DataMember]
    private int pcode;  // serialized(序列化的)
// Not serialized (read-only) // 不做序列化(只讀) public int ProductCode { get { return pcode; } } }

Dates(日期)

Dates are written in ISO 8601 format. For example, "2012-05-23T20:21:37.9116538Z".
日期被寫成ISO 8601格式。例如,「2012-05-23T20:21:37.9116538Z」。

Indenting(縮進)

To write indented XML, set the Indent property to true:
要書寫縮進的XML,請將Indent屬性設置爲true

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

Setting Per-Type XML Serializers
設置每一類型(Per-Type)的XML序列化器

You can set different XML serializers for different CLR types. For example, you might have a particular data object that requires XmlSerializer for backward compatibility. You can use XmlSerializer for this object and continue to use DataContractSerializer for other types.
你能夠爲不一樣的CLR類型設置不一樣的XML序列化器。例如,你可能有一個特殊的數據對象,它出於向後兼容而須要XmlSerializer。你能夠爲此對象使用XmlSerializer,而對其它類型繼續使用DataContractSerializer

To set an XML serializer for a particular type, call SetSerializer.
爲了設置用於特殊類型的XML序列化器,要調用SetSerializer

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
// Use XmlSerializer for instances of type "Product":
// 對「Product」類型的實例使用XmlSerializer:
xml.SetSerializer<Product>(new XmlSerializer(typeof(Product)));

You can specify an XmlSerializer or any object that derives from XmlObjectSerializer.
你能夠指定一個XmlSerializer,或任何派生於XmlObjectSerializer的對象。

6.2.5 Removing the JSON or XML Formatter
6.2.5 去除JSON或XML格式化器

You can remove the JSON formatter or the XML formatter from the list of formatters, if you do not want to use them. The main reasons to do this are:
你能夠從格式化器列表中刪除JSON格式化器,或XML格式化器,只要你不想使用它們。這麼作的主要緣由是:

  • To restrict your web API responses to a particular media type. For example, you might decide to support only JSON responses, and remove the XML formatter.
    將你的Web API響應限制到特定的媒體類型。例如,你可能決定只支持JSON響應,而刪除XML格式化器。
  • To replace the default formatter with a custom formatter. For example, you could replace the JSON formatter with your own custom implementation of a JSON formatter.
    用一個自定義格式化器代替默認的格式化器。例如,你可能要用本身的自定義JSON格式化器實現來代替(默認的)JSON格式化器。

The following code shows how to remove the default formatters. Call this from your Application_Start method, defined in Global.asax.
如下代碼演示瞭如何刪除默認的格式化器。在Global.asax中定義的Application_Start方法中調用它。

void ConfigureApi(HttpConfiguration config)
{
    // Remove the JSON formatter
    // 刪除JSON格式化器
    config.Formatters.Remove(config.Formatters.JsonFormatter);
// or(或者)
// Remove the XML formatter // 刪除XML格式化器 config.Formatters.Remove(config.Formatters.XmlFormatter); }

6.2.6 Handling Circular Object References
6.2.6 處理循環對象引用

By default, the JSON and XML formatters write all objects as values. If two properties refer to the same object, or if the same object appears twice in a collection, the formatter will serialize the object twice. This is a particular problem if your object graph contains cycles, because the serializer will throw an exception when it detects a loop in the graph.
在默認狀況下,JSON和XML格式化器將全部對象都寫成值。若是兩個屬性引用了同一個對象,或者,若是在一個集合同一個對象出現了兩次,格式化器將對此對象作兩次序列化。這是在對象圖含有循環的狀況下會出現的特有問題,由於,序列化器在檢測到對象圖中的循環時,會拋出異常(故格式化器會預先經過兩個序列化,來消除這種循環對象引用 — 譯者注)。

Consider the following object models and controller.
考慮如下對象模型和控制器。

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; } }

Invoking this action will cause the formatter to thrown an exception, which translates to a status code 500 (Internal Server Error) response to the client.
調用此動做會觸發格式化器招聘異常,該異常將轉換成發送給客戶端的狀態代碼500(內部服務器錯誤)響應。

To preserve object references in JSON, add the following code to Application_Start method in the Global.asax file:
爲了保留JSON中的對象引用,對Global.asax文件的Application_Start方法添加如下代碼:

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

Now the controller action will return JSON that looks like this:
如今,此控制器動做將返回相似於以下形式的JSON:

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

Notice that the serializer adds an "$id" property to both objects. Also, it detects that the Employee.Department property creates a loop, so it replaces the value with an object reference: {"$ref":"1"}.
注意,序列化器對兩個對象都添加了一個「$id」。並且,它檢測到Employee.Department屬性產生了一個循環,所以,它用一個對象引用{"$ref":"1"}代替這個值。

Object references are not standard in JSON. Before using this feature, consider whether your clients will be able to parse the results. It might be better simply to remove cycles from the graph. For example, the link from Employee back to Department is not really needed in this example.
對象引用是不標準的JSON。在使用此特性以前,要考慮你的客戶端是否可以解析這種結果。簡單地去除對象圖中的循環,多是更好的辦法。例如,此例中Employee連接回Department並非真正的須要。

To preserve object references in XML, you have two options. The simpler option is to add [DataContract(IsReference=true)] to your model class. The IsReference parameter enables object references. Remember that DataContract makes serialization opt-in, so you will also need to add DataMember attributes to the properties:
爲了保留XML中的對象引用,可使用兩個選項。較簡單的選項是對模型類添加[DataContract(IsReference=true)]。IsReference參數啓用了對象引用。記住,DataContract構成了序列化的「選入(Opt-in)」,所以,你還須要對屬性添加DataMember註解屬性(使用了這一註解屬性的模型屬性,才被選入(Opt-in)爲序列化對象 — 譯者注):

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

Now the formatter will produce XML similar to following:
如今,該格式化器將產生相似於以下形式的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>

If you want to avoid attributes on your model class, there is another option: Create a new type-specific DataContractSerializer instance and set preserveObjectReferences to true in the constructor. Then set this instance as a per-type serializer on the XML media-type formatter. The following code show how to do this:
若是想避免在模型類上使用註解屬性,還有另外一個選項:建立親的類型專用的DataContractSerializer實例,並在構造器中將preserveObjectReferences設置爲true

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

6.2.7 Testing Object Serialization
6.2.7 測試對象序列化

As you design your web API, it is useful to test how your data objects will be serialized. You can do this without creating a controller or invoking a controller action.
在設計Web API時,對如何序列化對象進行測試是有用的。沒必要建立控制器或調用控制器動做,即可作這種事。

string Serialize<T>(MediaTypeFormatter formatter, T value)
{
    // Create a dummy HTTP Content.
    // 建立一個HTTP內容的啞元
    Stream stream = new MemoryStream();
    var content = new StreamContent(stream);
// Serialize the object. // 序列化對象 formatter.WriteToStreamAsync(typeof(T), value, stream, content.Headers, 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 // 解序列化成類型爲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); }

看完此文若是以爲有所收穫,請給個推薦。 你的推薦是我繼續下去的動力,也會讓更多人關注並獲益,這也是你的貢獻。

相關文章
相關標籤/搜索