注释
本文介绍 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 持续时间格式,GUID 采用“12345678-ABCD-ABCD-ABCD-1234567890AB”格式,URI 则采用其本来的字符串形式,例如 "http://www.example.com" )。 有关精确信息,请参阅 数据协定架构参考。 |
XmlQualifiedName | 字符串 | 格式为“name:namespace”(第一个冒号前的任何内容都是名称)。 名称或命名空间可能缺失。 如果没有命名空间,也可以省略冒号。 |
System.Windows.Input.ICommand 类型的 Command |
数字数组 | 每个数字表示一个字节的值。 |
DateTime | DateTime 或 String | 请参阅本文后面的“日期/时间”和“JSON”。 |
DateTimeOffset | 复杂类型 | 请参阅本文后面的“日期/时间”和“JSON”。 |
XML 和 ADO.NET 类型 (XmlElement, XElement。 XmlNode、 ISerializable, DataSet)。 |
字符串 | 请参阅本文的 XML 类型和 JSON 部分。 |
DBNull | 空复杂类型 | -- |
集合、字典和数组 | 数组 | 请参阅本主题的“集合”、“字典”和“数组”部分。 |
复杂类型(应用 DataContractAttribute 或 SerializableAttribute) | 复杂类型 | 数据成员将成为 JavaScript 复杂类型的成员。 |
实现了ISerializable接口的复杂类型 | 复杂类型 | 与其他复杂类型相同,但某些 ISerializable 类型不受支持 - 请参阅 ISerializable 支持。 |
任何类型的 Null 值 |
零 | 也支持可以为 null 的值类型,这些值类型映射到 JSON 的方式与不可以为 null 的值类型相同。 |
枚举和 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 代理时, 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
是 300(从 GMT 中减去 300 分钟或 5 个小时)。注释
DateTime 和 DateTimeOffset 对象在序列化为 JSON 时,仅将信息保留为毫秒精度。 序列化期间,子毫秒值(微/纳秒)会丢失。
XML 类型和 JSON
XML 类型将成为 JSON 字符串。
例如,如果 XElement 类型的数据成员“q”包含 <abc/>,则 JSON 为 {“q”:“<abc/>”}。
有一些特殊规则指定 XML 的包装方式 - 有关详细信息,请参阅本文后面的“高级信息”部分。
如果使用 ASP.NET AJAX,并且不想在 JavaScript 中使用字符串,而是希望改用 XML DOM,请将ResponseFormat属性在WebGetAttribute上设置为 XML,或将ResponseFormat属性在WebInvokeAttribute上设置为 XML。
集合、字典和数组
所有集合、字典和数组都以 JSON 形式表示为数组。
在 JSON 表示形式中,任何使用 CollectionDataContractAttribute 的自定义都会被忽略。
字典不是直接使用 JSON 的方法。 在 WCF 中,Dictionary<string,object> 可能并不像用于其他 JSON 技术时所期望的那样获得相同的支持。 例如,如果将“abc”映射到“xyz”,并且“def”在字典中映射到 42,则 JSON 表示形式不是 {“abc”:“xyz”,“def”:42},而是 [{“Key”:“abc”,“Value”:“xyz”},{“Key”:“def”,“Value”:42}]。
如果要直接使用 JSON(在不预先定义刚性协定的情况下动态访问键和值),可以使用以下几个选项:
请考虑使用 弱类型 JSON 序列化 (AJAX) 示例。
请考虑使用 ISerializable 接口和反序列化构造函数 - 这两种机制允许分别在序列化和反序列化上访问 JSON 键/值对,但在部分信任方案中不起作用。
请考虑使用 JSON 和 XML 之间的映射 ,而不是使用序列化程序。
在序列化的上下文中,多形性是指在需要基类型时序列化派生类型的能力。 在以多态形式使用集合时(例如,在将集合分配给 Object 时),有一些 JSON 特定的特殊规则。 本文后面的“高级信息”部分更全面地讨论了此问题。
其他详细信息
数据成员的顺序
使用 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 反序列化就像声明的类型是对象一样。
使用自己的基类型和派生类型时,通常需要使用KnownTypeAttributeServiceKnownTypeAttribute或等效的机制。 例如,如果你有一个具有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,因此,如果某个操作使用一个名为“number”的Int
和一个名为“p”的Person
复杂类型,则 URL 可能类似于以下 URL。
http://example.com/myservice.svc/MyOperation?number=7&p={"name":"John","age":42}
如果使用 ASP.NET AJAX 脚本管理器控件和代理来调用服务,则此 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 连网格式
DateTime 值显示为 JSON 字符串,格式为“/Date(700000+0500)/”,其中第一个数字(提供的示例中为 700000)是 GMT 时区中的毫秒数,自 1970 年 1 月 1 日午夜以来的常规(非夏令时制)时间。 表示早期时间的数字可能是负数。 示例中由“+0500”组成的部分是可选的,指示时间属于 Local 此类,即应在反序列化时转换为本地时区。 如果不存在,则时间将反序列化为 Utc。 将忽略本示例中的实际数字(“0500”)及其符号(+ 或 -)。
序列化 DateTime 时,写入的 Local 和 Unspecified 时间将带有偏移量,而写入的 Utc 则不带偏移量。
ASP.NET AJAX 客户端 JavaScript 代码会自动将此类字符串转换为 JavaScript DateTime
实例。 如果其他字符串具有与 .NET 中没有类型的 DateTime 类似形式,则它们也会转换。
只有“/”字符为转义字符(即 JSON 的形式类似于“\/Date(700000+0500)\/”)时,才会进行转换,因此 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 数组开头的空命名空间中的属性(在其他元素之前)不受支持。
包括 XElement 和 DataSet 的 IXmlSerializable 类型
ISerializable 将类型细分为“内容类型”、“数据集类型”和“元素类型”。 有关这些类型的定义,请参阅 数据协定中的 XML 和 ADO.NET 类型。
“Content”和“DataSet”类型被序列化,类似于上一部分中讨论的Array对象XmlNode。 它们包装在一个元素中,该元素的名称和命名空间对应于有问题的类型的数据协定名称和命名空间。
“Element” 类型(如 XElement)将按原样进行序列化,类似于本文前面讨论的 XmlElement。
多态性
保留类型信息
如前所述,JSON 支持多态性,但存在一些限制。 JavaScript 是一种弱类型语言,类型标识通常不是问题。 但是,使用 JSON 在强类型系统(.NET)和弱类型系统(JavaScript)之间通信时,保留类型标识非常有用。 例如,具有数据协定名称“Square”和“Circle”的类型派生自数据协定名称为“Shape”的类型。 如果将“Circle”从 .NET 发送至 JavaScript,随后又将其返回给某个需要“Shape”的 .NET 方法,则 .NET 端就需要它以知道该对象最初为“Circle”,否则任何特定于派生类型的信息(例如,“Circle”上的“radius”数据成员)都可能丢失。
若要保留类型标识,可将复杂类型序列化为 JSON 时添加“类型提示”,反序列化程序可识别该提示并相应地执行作。 “类型提示”是一个键名为“__type”(两个下划线,后跟单词“type”)的 JSON 键/值对。 该值是格式为“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 返回类型但返回一个 Circle 对象,那么 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>,以发现通过配置文件添加的已知类型。
分配给对象的集合
序列化分配给对象的集合时,会将它们视为实现 IEnumerable<T> 的集合:一个 JSON 数组,其中属于复杂类型的每一项都具有类型提示。 例如,分配给 List<T> 的 Shape
类型的 Object 将类似于以下形式。
[{"__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 之间的映射。