備註
本文是討論DataContractJsonSerializer的。 對於涉及串行化和還原串行化 JSON 的大部分案例,我們建議 System.Text.Json 命名空間中的 API。
JSON (JavaScript 物件表示法) 是一種數據格式,專門設計成供瀏覽器內網頁上執行的 JavaScript 程式代碼使用。 這是在 Windows Communication Foundation (WCF) 中建立 ASP.NET AJAX 服務所使用的預設數據格式。
此格式也可以在建立 AJAX 服務而不與 ASP.NET 整合時使用 - 在此情況下,XML 是預設值,但可以選擇 JSON。
最後,如果您需要 JSON 支援,但不建立 AJAX 服務, DataContractJsonSerializer 則可以將 .NET 物件直接串行化為 JSON 數據,並將這類數據還原串行化回 .NET 類型的實例。 如需了解如何執行此操作的描述,請參閱 如何:序列化和反序列化 JSON 資料。
使用 JSON 時,支援相同的 .NET 類型,與 DataContractSerializer 中所支援的情況相同,但有一些例外。 如需支援的型別清單,請參閱 數據合約串行化程式所支援的類型。 這包括大部分的基本類型、大部分的數位和集合類型,以及使用 DataContractAttribute 和 DataMemberAttribute的複雜型別。
將 .NET 類型對應至 JSON 類型
下表顯示序列化和反序列化程序所對應的 .NET 型別與 JSON/JavaScript 型別之間的對應關係。
| .NET 類型 | JSON/JavaScript | 註釋 |
|---|---|---|
| 所有數值類型,例如 Int32、 Decimal 或 Double | 數目 | 不支援Double.NaN、Double.PositiveInfinity 和 Double.NegativeInfinity 等特殊值,並會導致 JSON 無效。 |
| Enum | 數目 | 請參閱本文稍後的<列舉和 JSON>。 |
| Boolean | 布爾邏輯 | |
| String、Char | 繩子 | |
| TimeSpan、Guid、Uri | 繩子 | JSON 中的這些類型格式與 XML 的格式相同(基本上,ISO 8601 持續時間格式的時間範圍、“12345678-ABCD-ABCD-ABCD-ABCD-1234567890AB” 格式的 GUID,以及其自然字串格式的 URI,例如 "http://www.example.com")。 如需精確資訊,請參閱 數據合約架構參考。 |
| XmlQualifiedName | 繩子 | 格式為 “name:namespace”(第一個冒號之前的任何部分都是名稱)。 名稱或命名空間都可以遺失。 如果沒有命名空間,也可以省略冒號。 |
型別 Command 的 System.Windows.Input.ICommand |
數字陣列 | 每個數位都代表一個字節的值。 |
| DateTime | 日期時間或字串 | 請參閱本文稍後的日期/時間與 JSON。 |
| DateTimeOffset | 複雜類型 | 請參閱本文稍後的日期/時間與 JSON。 |
| XML 和 ADO.NET 類型 (XmlElement, XElement。 XmlNode 的陣列, ISerializable, DataSet)。 |
繩子 | 請參閱本文的 XML 類型和 JSON 一節。 |
| DBNull | 空的複雜類型 | -- |
| 集合、字典和陣列 | 陣列 | 請參閱本主題的集合、字典和陣列一節。 |
| 複雜類型(已套用 DataContractAttribute 或 SerializableAttribute) | 複雜類型 | 數據成員會成為 JavaScript 複雜類型的成員。 |
| 實作 ISerializable 介面的複雜型別) | 複雜類型 | 與其他複雜類型相同,但不支援某些 ISerializable 類型 – 請參閱 ISerializable 支援。 |
Null 值適用於任何類型 |
零 | 空值類型也受到支援,並以與非空值類型相同的方式映射至 JSON。 |
列舉和 JSON
列舉成員值在 JSON 中會被視為數字,這與在數據合約中的處理方式不同,數據合約會將它們包含為成員名稱。 如需數據合約處理的詳細資訊,請參閱 數據合約中的列舉型別。
例如,如果您有
public enum Color {red, green, blue, yellow, pink},序列化yellow會產生數字 3,而不是字串 “yellow”。所有
enum成員都可以串行化。 如果使用EnumMemberAttribute和NonSerializedAttribute屬性,則會忽略它們。可以將不存在的
enum值進行反序列化,例如,值 87 可以反序列化為之前的 Color 列舉,即使沒有定義相應的色彩名稱。一個旗標
enum並不特殊,會被視為與其他任何enum相同。
日期/時間和 JSON
JSON 格式不支援日期和時間。 不過,它們非常常用,ASP.NET AJAX 提供這些類型的特殊支援。 使用 ASP.NET AJAX Proxy 時, DateTime .NET 中的類型會完全對應至 DateTime JavaScript 中的類型。
不使用 ASP.NET 時, DateTime 類型會以 JSON 表示為字串,其中包含本主題的進階資訊一節中所述的特殊格式。
DateTimeOffset 以 JSON 表示為複雜類型:{“DateTime”:d ateTime,“OffsetMinutes”:offsetMinutes}。 該
offsetMinutes成員是格林威治標準時間(GMT)的當地時間位移,現在也稱為國際標準時間(UTC),與感興趣的事件位置相關聯。 成員dateTime代表發生該事件時的時間實例。當使用 ASP.NET AJAX 時,它會在 JavaScript 中變成dateTime;若未使用則會變成 字串。 在串行化時,dateTime成員一律會在 GMT 中串行化。 因此,如果描述紐約標準時間凌晨 3:00,則dateTime的時間元件為上午 8:00,且offsetMinutes與 GMT 時差為負 300 分鐘或減去 5 小時。備註
DateTime 和 DateTimeOffset 對象,當串行化為 JSON 時,只會將資訊保留為毫秒精確度。 串行化期間,子毫秒值 (micro/nanoseconds) 會遺失。
XML 類型和 JSON
XML 類型會變成 JSON 字串。
例如,如果 XElement 類型的數據成員 「q」 包含 <abc/>,則 JSON 為 {“q”:“<abc/>”}。
有一些特殊規則可指定 XML 包裝方式 - 如需詳細資訊,請參閱本文稍後的一節。
如果您使用 ASP.NET AJAX,而不想在 JavaScript 中使用字串,但希望改用 XML DOM,請在 ResponseFormat 上將 WebGetAttribute 屬性設為 XML,或在 ResponseFormat 上將 WebInvokeAttribute 屬性設為 XML。
集合、字典和陣列
所有集合、字典和陣列都會以 JSON 表示為陣列。
使用 CollectionDataContractAttribute 的任何自定義都會在 JSON 表示法中忽略。
字典不是直接使用 JSON 的方法。 <使用其他 JSON 技術時,WCF 可能無法以與預期相同的方式支援字典字串。> 例如,如果 “abc” 對應到字典中的 “xyz” 和 “def” 對應到 42,則 JSON 表示法不是 {“abc”:“xyz”,“def”:42},而是 [{“Key”:“abc”,“Value”:“xyz”},{“Key”:“def”,“Value”:42}] 。
如果您想要直接使用 JSON(動態存取索引鍵和值,而不預先定義固定的合約),您有數個選項:
建議使用 弱類型 JSON 序列化(AJAX) 範例。
請考慮使用 ISerializable 介面和還原串行化建構函式 - 這兩種機制可讓您分別存取串行化和還原串行化上的 JSON 索引鍵/值組,但在部分信任案例中無法運作。
請考慮使用 JSON 與 XML 之間的對應 ,而不是使用序列化器。
在序列化的語境中,多型指的是在預期基底型別的情況下,能夠序列化其衍生型別。 以多型方式使用集合時,有特殊的 JSON 特定規則,例如,將集合指派給 Object。 本文稍後的一節會更完整地討論此問題。
其他詳細資料
數據成員的順序
使用 JSON 時,數據成員的順序並不重要。 具體來說,即使 Order 已設定,JSON 數據仍可依任何順序還原串行化。
JSON 類型
JSON 類型不一定符合還原串行化上的上表。 例如,Int 通常對應到 JSON 數字,但只要該 JSON 字串包含有效的數字,也可以成功解構化。 也就是說,如果有 Int 名為 「q」 的數據成員,則 {“q”:42} 和 {“q”:“42”} 都是有效的。
多型性
多型序列化的能力在於可以將預期為基底型別的衍生型別進行序列化。 WCF 支援 JSON 串行化,這與支援 XML 串行化的方式相當。 例如,您可以在預期應為 MyDerivedType 的地方串行化 MyBaseType ,或在預期應為 Int 的地方串行化 Object 。
序列化還原時,如果預期的是基礎類型,但其實是還原序列化的衍生類型,那麼類型資訊可能會遺失,除非您還原序列化的是複雜類型。 例如,如果 Uri 在預期的地方進行序列化為 Object ,則會產生 JSON 字串。 如果此字串接著還原串行化回 Object,則會傳回 .NET String 。 反序列化器無法知道該字串最初的型別為Uri。 一般而言,當預期Object時,所有 JSON 字串會反序列化為 .NET 字串,而所有用於序列化 .NET 集合、字典和陣列的 JSON 陣列則會反序列化為型別為Array的 .NETObject,不論實際的原始類型為何。 JSON 布林值會對應至 .NET Boolean。 不過,當期望有Object時,JSON 數字會被反序列化為 .NET Int32、Decimal 或 Double,系統會自動選擇最合適的類型。
當將資料反序列化為介面類型時,DataContractJsonSerializer 的反序列化行為就像宣告的類型是物件一樣。
當使用您自己的基底和衍生型別時,通常需要使用 KnownTypeAttribute、ServiceKnownTypeAttribute 或其他對等機制。 例如,如果您有一個具有 Animal 傳回值的作業,而它實際上會傳回一個衍生自 Cat 的 Animal 實例,則應該將 KnownTypeAttribute 套用到 Animal 類型,或將 ServiceKnownTypeAttribute 套用到該作業,並在這些屬性中指定 Cat 型別。 如需詳細資訊,請參閱 數據合約已知類型。
如需多型串行化運作方式的詳細數據,以及使用時必須遵守的一些限制的討論,請參閱本文稍後的一節。
版本控制
JSON 完全支援數據合約版本控制功能,包括 IExtensibleDataObject 介面。 此外,在大多數情況下,可以從一種格式(例如 XML)解析類型,然後將其序列化為另一種格式(例如 JSON),並仍然保留 IExtensibleDataObject 中的數據。 如需詳細資訊,請參閱 Forward-Compatible 數據合約。 請記住,JSON 未排序,因此任何訂單資訊都遺失。 此外,JSON 不支援具有相同索引鍵名稱的多個索引鍵/值組。 最後,上 IExtensibleDataObject 的所有作業本質上都是多型的,也就是其衍生型別會指派給 Object,這是所有型別的基底類型。
URL 中的 JSON
使用 ASP.NET AJAX 端點搭配 HTTP GET 動詞命令時(使用 WebGetAttribute 屬性),傳入參數會出現在要求 URL 中,而不是訊息本文。 即使在要求 URL 中也支援 JSON,因此如果您有一項作業,其採用 Int 稱為 「number」 且 Person 複雜類型稱為 「p」,則 URL 可能會類似下列 URL。
http://example.com/myservice.svc/MyOperation?number=7&p={"name":"John","age":42}
如果您使用 ASP.NET AJAX 腳本管理員控件和 Proxy 來呼叫服務,則 Proxy 會自動產生此 URL,而且不會看見。 JSON 不能用於 non-ASP.NET AJAX 端點的 URL。
進階資訊
ISerializable 支援
被支援和不被支援的 ISerializable 類型
一般而言,序列化/反序列化 JSON 時,完全支持實作 ISerializable 介面的類型。 不過,這些類型中有一些(包括一些 .NET Framework 類型)是以某種方式實現的,使得 JSON 特定的序列化層面導致它們無法正確地反序列化。
使用 ISerializable時,永遠不會事先知道個別數據成員的類型。 這會導致一種多型的情況,類似於將型別反序列化為物件。 如先前所述,這可能會導致 JSON 中的類型資訊遺失。 例如,在其
enum實作中序列化 ISerializable 的型別,並在沒有做適當轉換的情況下嘗試直接反序列化回enum時會失敗,因為enum在 JSON 中是以數字形式序列化的,而 JSON 數字會反序列化為內建的 .NET 數值類型(如 Int32、Decimal 或 Double)。 因此,原本作為enum值的數字已被遺忘。ISerializable取決於還原串行化建構函式中特定還原串行化順序的類型也可能無法還原串行化某些 JSON 數據,因為大部分的 JSON 串行化程式並不保證任何特定順序。
工廠類型
IObjectReference介面一般是在 JSON 中支援的,但不支援任何需要「工廠類型」功能的類型(這類功能會傳回不同於實作介面的GetRealObject(StreamingContext)類型的實例)。
DateTime Wire 格式
DateTime 值會以 「/Date(700000+05000+05000)/“ 的形式顯示為 JSON 字串串,其中第一個數位(如提供的範例中為 700000)是 GMT 時區中的毫秒數,自 1970 年 1 月 1 日午夜以來的一般 (非日光節約時間) 時間。 數位可能是負數,表示先前的時間。 範例中由 「+0500」 所組成的元件是選擇性的,並指出時間是 Local 這類的 ,也就是說,應該在還原串行化時轉換成當地時區。 如果不存在,時間會反序列化為 Utc。 本範例中的實際數位 (“0500”) 和其正負號 (+ 或 -) 會被忽略。
序列化 DateTime 時,Local 和 Unspecified 的時間會以位移寫入,而 Utc 則沒有位移地寫入。
ASP.NET AJAX 用戶端 JavaScript 程式代碼會自動將這類字串轉換成 JavaScript DateTime 實例。 如果有其他字串形式類似但不是 DateTime 類型的 .NET,它們也會被轉換。
只有在逸出 「/」 字元時才會進行轉換(也就是 JSON 看起來像 」\/Date(700000+05000)\/“),因此 WCF 的 JSON 編碼器(由 WebHttpBinding啟用)一律會逸出 ”/“ 字元。
JSON 字串中的 XML
XmlElement
XmlElement 保持原樣進行序列化,不包裝。 例如,包含 XmlElementabc/< 類型的>資料成員 「x」 表示如下:
{"x":"<abc/>"}
XmlNode 的陣列
Array 類型的 XmlNode 物件會包裝在名為 "ArrayOfXmlNode" 的元素中,此元素屬於該類型的標準資料契約命名空間。 如果 「x」 是包含命名空間 「ns」 中屬性節點 「N」 且包含 「value」 和空元素節點 「M」 的陣列,表示法如下所示。
{"x":"<ArrayOfXmlNode xmlns=\"http://schemas.datacontract.org/2004/07/System.Xml\" a:N=\"value\" xmlns:a=\"ns\"><M/></ArrayOfXmlNode>"}
XmlNode 陣列開頭空命名空間中的屬性不受支援(在其他元素之前)。
IXmlSerializable 類型,包括 XElement 和 DataSet
ISerializable 類型細分為「內容類型」、「DataSet 類型」和「元素類型」。 如需這些類型的定義,請參閱 數據合約中的 XML 和 ADO.NET 型別。
“Content” 和 “DataSet” 類型會串行化,類似於 Array 上一節所討論的物件 XmlNode 。 它們包裝在一個元素中,其名稱和命名空間對應至所指型別的資料契約名稱和命名空間。
如 的 XElement 「元素」類型會依原樣串行化,類似於 XmlElement 本文先前討論的。
多型性
保留類型資訊
如先前所述,JSON 支援多型,但有一些限制。 JavaScript 是弱型別語言,且類型識別通常不是問題。 不過,使用 JSON 在強型別系統 (.NET) 與弱型別系統 (JavaScript) 之間進行通訊時,保留類型識別很有用。 例如,具有數據合約名稱 「Square」 和 「Circle」 的類型衍生自具有 「Shape」 資料合約名稱的類型。 如果「Circle」從 .NET 傳送到 JavaScript,稍後返回至預期「Shape」的 .NET 方法時,讓 .NET 知道該物件原本是「Circle」是很有幫助的。否則有關衍生類型的特定資訊(例如「Circle」中的「radius」數據成員)可能會遺失。
若要保留類型識別,可以將複雜型別串行化為 JSON 時,可以新增「類型提示」,而還原串行化程式會辨識提示並適當地運作。 “type hint” 是 JSON 索引鍵/值組,其索引鍵名稱為 “__type” (兩個底線後面接著 “type” 一詞)。 此值是格式為 「DataContractName:DataContractNamespace」 的 JSON 字串(最多第一個冒號為名稱)。 使用先前的範例,可以將 “Circle” 串行化,如下所示。
{"__type":"Circle:http://example.com/myNamespace","x":50,"y":70,"radius":10}
型別提示與 XML 架構實例標準所定義的屬性非常類似 xsi:type ,並在串行化/還原串行化 XML 時使用。
可能會與類型提示發生衝突,所以禁止稱為「__type」的資料成員。
減少類型提示的大小
為了減少 JSON 訊息的大小,預設數據合約命名空間前置詞 (http://schemas.datacontract.org/2004/07/) 會取代為 “#” 字元。 (若要使此取代可復原,則會使用逸出規則:如果命名空間以 “#” 或 “\” 字元開頭,則會附加額外的 “\” 字元。 因此,如果 「Circle」 是 .NET 命名空間 「MyApp.Shapes」 中的類型,則其預設數據合約命名空間為 http://schemas.datacontract.org/2004/07/MyApp。 圖形和 JSON 表示法如下所示。
{"__type":"Circle:#MyApp.Shapes","x":50,"y":70,"radius":10}
在還原串行化過程中,截斷的(#MyApp.Shapes)名稱和完整的(http://schemas.datacontract.org/2004/07/MyApp.Shapes)名稱都能被理解。
在 JSON 對象中輸入提示位置
請注意,類型提示必須先出現在 JSON 表示法中。 在 JSON 處理中,索引鍵/值組的順序只有在這種情況下才重要。 例如,下列不是指定類型提示的有效方式。
{"x":50,"y":70,"radius":10,"__type":"Circle:#MyApp.Shapes"}
WCF 和 ASP.NET AJAX 用戶端頁面所使用的DataContractJsonSerializer 都一律先發出類型提示。
類型提示僅適用於複雜類型
無法針對非複雜類型發出類型提示。 例如,如果某項作業的返回類型是 Object,但實際返回一個圓形,那麼 JSON 的表示形式可以如前所示,並且保留類型資訊。 不過,如果傳回的是 Uri,則 JSON 會以字串的形式表示,而用來代表 Uri 的字串這個事實將會遺失。 這不僅適用於基本類型,也適用於集合和陣列。
何時顯示類型提示
類型提示可能會大幅增加訊息大小(其中一種方法可以減輕此問題,就是盡可能使用較短的數據合約命名空間)。 因此,下列規則會控管是否發出類型提示:
使用 ASP.NET AJAX 時,類型提示一律會盡可能發出,即使沒有基底/衍生指派,例如,即使將 Circle 指派給 Circle 也一樣。 (這是為了完整啟用從弱型別 JSON 環境呼叫至強型別 .NET 環境的過程,且不會有意外的資訊流失。)
當使用沒有 ASP.NET 整合的 AJAX 服務時,類型提示只有在有基礎類型/衍生類型指派時才會發出,也就是當 Circle 指派給 Shape 或 Object 時發出,而不是指派給 Circle 時發出。 為了正確地實作 JavaScript 用戶端,這裡提供所需的最小資訊,因此可以提升效能,但無法防止在設計不當的用戶端中發生類型資訊遺失。 如果您想要避免在用戶端上處理此問題,請完全避免伺服器上的基底/衍生指派。
使用 DataContractSerializer 類型時,建
alwaysEmitTypeInformation構函式參數可讓您在上述兩種模式之間選擇,預設值為 “false” (僅在必要時發出類型提示)。
重複的數據成員名稱
衍生的類型資訊會與基底類型資訊一起存在於相同的 JSON 物件中,而且可以依任何順序發生。 例如, Shape 可能會以下列方式表示。
{"__type":"Shape:#MyApp.Shapes","x":50,"y":70}
而 Circle 可能會以下列方式表示。
{"__type":"Circle:#MyApp.Shapes","x":50, "radius":10,"y":70}
如果基底 Shape 類型也包含名為 「radius」 的數據成員,這會導致串行化發生衝突(因為 JSON 物件不能有重複的索引鍵名稱)和還原串行化(因為目前還不清楚 “radius” 是否是指 Shape.radius 或 Circle.radius)。 因此,雖然「屬性隱藏」的概念(基礎類別和衍生類別中具有相同名稱的資料成員)在資料合約類別中通常不建議使用,但在 JSON 的情況下,實際上禁止此概念。
多型和「IXmlSerializable 類型」
IXmlSerializable 根據一般數據合約規則,只要符合已知類型需求,類型就可以依一般方式指派給彼此。 不過,串行化 IXmlSerializable 類型以取代 Object 會導致類型資訊遺失,因為結果是 JSON 字串。
多型和特定介面類型
禁止將集合型別或一個非集合型別被預期卻實作了 IXmlSerializable 的型別進行串行化(除了 IXmlSerializable 和 Object 非集合型別外)。 例如,稱為 IMyInterface 的自定義介面,以及實作類型 MyType 的 IEnumerable<T> 和 int 的類型 IMyInterface。 不允許從傳回型別為MyType的作業傳回IMyInterface。 這是因為 MyType 必須序列化為 JSON 陣列,且需要型別提示,如前所述,您無法在陣列中包含型別提示,只能在複雜類型中使用。
已知類型和組態
DataContractSerializer 使用的所有已知型別機制也同樣得到 DataContractJsonSerializer 的支援。 這兩個串行化程式都會讀取 system.runtime.serialization< 中的 > 同一個組態元素 <dataContractSerializer>,以探索透過組態檔新增的已知類型。
指派給物件的集合
指派給 Object 的集合會被序列化為實作 IEnumerable<T>的集合:如果為複雜類型,每個項目都有類型提示的 JSON 陣列。 例如,指派給 List<T> 的 Shape 型別如下所示。
[{"__type":"Shape:#MyApp.Shapes","x":50,"y":70},
{"__type":"Shape:#MyApp.Shapes","x":58,"y":73},
{"__type":"Shape:#MyApp.Shapes","x":41,"y":32}]
還原反序列化到 Object 時:
Shape必須位於 [已知類型] 清單中。 將類型 List<T> 包含在已知類型Shape中沒有任何影響。 請注意,在此案例中,您不需要新增Shape至已知型別,系統會自動進行序列化。
指派給基底集合的衍生集合
將衍生集合指派給基底集合時,集合通常會串行化為基底類型的集合。 不過,如果衍生集合的項目類型無法指派給基底集合的項目類型,則會拋出異常。
類型提示和字典
將字典指派給 Object 時,字典中的每個索引鍵和值條目都會被視為指派給 Object,並獲取類型提示。
串行化字典類型時,包含「Key」和「Value」成員的 JSON 物件不受alwaysEmitTypeInformation設定影響,只有在前述集合規則需要時才會包含類型提示。
有效的 JSON 金鑰名稱
串行器會對不是有效 XML 名稱的索引鍵名稱進行 XML 編碼。 例如,名稱為 「123」 的數據成員會有編碼的名稱,例如 「_x0031__x0032__x0033_」,因為 「123」 是無效的 XML 元素名稱(開頭為數位)。 某些國際字元集在 XML 名稱中無效時,可能會發生類似的情況。 如需 XML 對 JSON 處理之影響的說明,請參閱 JSON 與 XML 之間的對應。