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

谨以此文感谢关注此系列文章的园友!前段时间本以为此系列文章已没多少人关注,而不打算继续下去了。因为文章贴出来之后,看的人似乎不多,也很少有人对这些文章发表评论,而且几乎无人给予“推荐”。但前几天有人询问为何很久没有更新,这让我感觉把这文章翻译出来还是有价值的。为此,本人打算将此工作继续下去。这些关于Web API的技术文章均由微软专业人员撰写,虽然文章作为博客帖子而写得比较简单,但如果仔细揣摩其内容,应当还是能够有不少收获的,也希望我的这些译文能够使那些对Web API有兴趣的园友从中得到一些收获。

尽管最近正忙于《Pro ASP.NET MVC 4》一书的翻译,但我仍会将此系列文章继续下去,只不过可能不会仔细检查,译文一定会有比较多的错误,恳请读者谅解。

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-serialization

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格式化器。

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。

Contents
本小节内容

  • 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是一个第三方开源项目。

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即可:

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

看完此文如果觉得有所收获,请给个推荐
你的推荐是我继续下去的动力,也会让更多人关注并获益,这也是你的贡献。

posted @ 2013-06-26 07:53 r01cn 阅读(...) 评论(...) 编辑 收藏