65.9K
CodeProject 正在变化。 阅读更多。
Home

APJSON

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (5投票s)

2012年11月25日

CPOL

13分钟阅读

viewsIcon

43088

downloadIcon

1460

这是“fastJSON”的替代方案

引言   

这是 Gholam 出色的 JSON 库的一个替代版本,它带来了新的反序列化器、代码和工作流优化以及一些新功能,同时性能损失在可接受范围内(如果)。 

主要特性 (v1.0)  

  • 将 JSON 字符串更快速地反序列化为通用值 (IJsonValue)
  • 类型扩展支持 (自定义类型名称) 
  • 自定义类型支持 (自定义 (反) 序列化器) 
  • Silverlight 5 支持
  • 可选地检查 DataMember 属性。
  • 处理 DataMember.Name,代表 JSON 字段名  
  • 处理 IgnoreDataMember,忽略用它修饰的属性。 
  • 通过 JsonDateTimeOptions 属性处理日期时间种类。
  • 通过 JsonDateTimeOptions 属性处理自定义日期格式。
  • [新增] 支持 HashSet<T> (伴随一定的性能损失)
  • [新增] Json.Current.BuildUp 现在支持将 JsonArray 转换为集合类型。
     
  • (大多数)内置值类型都受支持。   
  • 非抽象引用类型(包括继承)都受支持 
  • 自定义类型支持。  
  • 增强的调试功能,请参阅 GetSerializationMembers 方法
  • 优先保证质量而不是性能。 

==> 请参阅本文档底部的更改日志。

==> 请参阅 fastJSON 2.0.9 的功能列表,以获取更全面的功能列表。 

使用代码 

由于 ApJson 是建立在 fastJSON 之上的,因此其 API 有一些重叠: 

序列化对象 

Apolyton.FastJson.Json.Current.ToJson(c);  

请注意,在所有未提供显式参数对象的情况下,都将使用默认参数。 

反序列化对象   

选项 1:直接操作

为了直接反序列化对象(支持自定义类型),您的调用应如下所示

string jsonText =  "{...}"; // your json string 
var myObject = (MyClass)Apolyton.FastJson.Json.Current.ReadObject(jsonText);

注意 1:必须启用类型扩展并在 jsin 字符串中存在,此功能才能正常工作(否则反序列化器无法确定要创建哪种对象)。
注意 2:此方法还有一个泛型版本
 

选项 2:嗅探即用

但是,A-FastJson 还提供了一个反序列化器 JsonValueDeserializer,它返回一个基于 IJsonValue 的值存储。此操作比上面显示的 ReadObject 方法快得多,但类型不是那么严格。读取到 JSON 值可以通过以下方式完成:   

string jsonText =  "{...}"; // your json string 
JsonObject myObject = Apolyton.FastJson.Json.Current.ReadJsonValue(jsonText);    

JsonObject 类本质上是一个字典,允许您在继续反序列化过程之前嗅探其值。例如,这对于协议验证非常有用,因为它可以让您在完全反序列化之前就丢弃 JSON 请求,从而节省时间并可能提高您的 I/O 性能(ReadJsonValue 的速度**几乎是 ReadObject 的两倍**,见下文)。 

JsonObject 的实例可用于填充现有 CLR 对象实例。为此,请使用 Json 单例上的 BuilUp 方法:   

string jsonText =  "{...}"; // your json string 
var deserializedStore = (JsonObject)Apolyton.FastJson.Json.Current.ReadJsonValue(jsonText);
var target = new MyClass();

Apolyton.FastJson.Json.Current.BuildUp(target, deserializedStore);

为了获得最佳性能,您应该池化您的目标对象;BuildUp 方法旨在确保您可以回收您的实例。 

配置  

默认情况下,Json 类实例使用默认参数进行序列化和反序列化。由于内部机制,这些配置对象**很昂贵**,与 fastJSON 中的对象相比。这是因为所有序列化元信息都附加到它上面。 

 
 

参数名称应自明,并在代码中简要说明。从 v0.93 开始,对引用序列化和反序列化的参数有了明确的区分。JsonParameters 类上的属性引用两个操作。 UseGlobalTypes 已弃用。 

检查配置   

为了**调试**或自动协议检查,您可能希望查看 JSON (反) 序列化器可见的属性列表。为此,您可以使用 GetSerializationMembers 方法。

// For the default parameter
Apolyton.FastJson.Json.Current.GetSerializationMembers(typeof(MyClass)); 
or  
// For any parameter object
new JsomParameter().GetSerializationMembers(typeof(MyClass);
  

自定义类型支持  

FastJSON 的自定义类型支持已得到扩展和审查,以确保其平台独立性。本质上,参数对象可以注册一个序列化和反序列化处理程序,它们是纯委托。这可以例如通过以下方式完成: 

// Preamble for illustration purposes only
JsonParameters parameters = CreateTestParameters();
JsonSerializer serializer = new JsonSerializer(parameters);
CustomTypeClass customTypeClass = new CustomTypeClass() { Custom = new CustomTypeClass.CustomType() };

// Register two delegates, we serialize to 'yes', we deserialize to 'null' 
parameters.RegisterCustomType(typeof(CustomTypeClass.CustomType), 
       (obj) => { return "yes"; }, 
       (obj) => { return null;  });

String jsonString = serializer.Serialize(customTypeClass);

这将呈现为如下 JSON 字符串

{"Custom":"yes"}

提供的内联委托会为给定类型的每个实例调用。请注意,您**不能**为内部类型注册自定义类型处理程序。如果您尝试这样做,注册方法将抛出 ArgumentException。 

为了代码更具可维护性,您也可以实现 ICustomTypeSerializer 接口并注册该实例作为自定义类型处理程序:   

已经有一个基类 CustomTypeSerializer,它预先实现了该接口,让我们专注于代码的重要部分(而不是形式上的部分)。

internal class ObjectIdSerializer : CustomTypeSerializer
{
    /// <summary>
    /// Gets the type for which this serializer is responsible for.
    /// </summary>
    public override Type Type
    {
        get { return typeof(ObjectId); }
    }

    /// <summary>
    /// Returns true: yes, this class can deserialize ObjectId.
    /// </summary>
    public override bool CanDeserialize
    {
        get{return true; }
    }

    /// <summary>
    /// Returns true: yes, this class can serialize ObjectId.
    /// </summary>
    public override bool CanSerialize
    {
        get { return true; }
    }

    /// <summary>
    /// Returns the string of the object id.
    /// </summary>
    /// <param name="data"></param>
    /// <returns></returns>
    public override string Serialize(object data)
    {
        return data.ToString();
    }

    /// <summary>
    /// Returns the object id representing the string.
    /// </summary>
    public override object Deserialize(string jsonString)
    {
        if (!String.IsNullOrEmpty(jsonString))
        {
            return new ObjectId(jsonString);
        }
        else
        {
            return ObjectId.Empty;
        }
    }
}

该示例将 Mongo ObjectId 结构序列化为字符串,并以相反的方式进行。请注意,我们没有实现 TypeName,它可以定义类型的名称(参见类型描述符和多态性);返回 null 会导致默认行为(推荐)。 

类型描述符和多态性

本章主要与高级反序列化场景相关。对象的序列化在无需交互或配置的情况下正常工作。当使用多态对象时,就达到了无配置反序列化的极限。  

理解问题

涉及多态对象的**简单场景**是动物列表,其中每个项目可以是某种具体的动物,如狗或猫。没有类型描述符,反序列化器会将每个项目反序列化为一个动物。因此,给定一个包含 3 只动物(2 只狗和 1 只猫)的列表,序列化器会正确地完成其工作,生成一个包含 3 个项目及其属性的 JSON 数组: 

[
  { "name"="Brutus", barkLevel="high", "power"="medium" }, 
  { "name"="Bronto", barkLevel="high", "power"="high"},
  { "name"="Silvester", "intelligence"="low", "creativity"="high", "luck"="not-existent" }
] 

然而,反序列化器仅仅*看到*了一个包含 3 只动物的列表。根据上述文本信息,行的类型丢失了。因此,反序列化的列表将只包含一个包含 3 只动物的列表,而忽略流中的所有额外信息。解决此问题的方法是同时序列化类型信息,通常放入名为 $type 的特殊属性中: 

[
  { "$type"="dog", "name"="Brutus", barkLevel="high", "power"="medium" }, 
  { "$type"="dog", "name"="Bronto", barkLevel="high", "power"="high"},
  { "$type"="cat", "name"="Silvester", "intelligence"="low", "creativity"="high", "luck"="not-existent" }
]   

类型字段的值可以通过类型描述符来控制

类型描述符  

类型描述符的主要职责是用字符串描述一个类型。默认情况下,FastJson 和 ApJson 使用程序集限定名来描述给定类型,这是一个非常冗长的字符串: 

For System.Object:
"System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"

这个字符串的好处在于,在 .NET 世界中它应该能很好地工作,但这些类型名称的缺点是其可移植性和长度。**显而易见**,'用户类型的**良好类型名称**是什么?'这个问题无法由这个工具包来回答。虽然**推荐**使用 DataContractTypeDescriptor,但可以通过实现自己的 JsonTypeDescriptor 子类并将其注册到参数来自定义类型描述: 

 

如上所述,JsonTypeDescriptor 生成程序集限定的类型名称。很容易想象(并实现)使用类型的 FullName 或仅仅是 Name 的类型描述符。但是,**推荐的方式**是使用 DataContractTypeDescriptor

顾名思义,该描述符使用 DataContractAttribute 来确定类型的名称。作为一种回退机制,将使用类型的 FullName(如果给定类型没有 DataContract 属性或没有分配 Name)。为我们的一个类分配名称非常直接: 

[System.Runtime.Serialization.DataContract(Name="dog")]
public class Dog
{
  ...
}

我们可以选择性地提供一个命名空间,但上面的示例已经产生了可读的输出,如上所示。 

关注点     

  • 始终重用配置(JsonParameter)对象。如果您不这样做,性能**会急剧下降**。
  • IJsonValue 概念受到 Microsoft 在 Silverlight 中实现的JSON API 的启发。我发现它在嗅探传入请求并在请求不符合基本预期(缺少必需字段)时将其丢弃方面非常有用。为了达到相同的目的而运行完整的反序列化对我来说似乎是资源浪费。
  • 代码包含单元测试(约 160+ 个),应确保每个版本的质量都很高。
  • 不支持字节枚举。请使用 byte[] 代替。
  • DataMember.OrderNumber 被忽略
  • JsonDateTimeOptions.Format 遵循 DateTime.ParseExact 的规范。 
  • 避免在 (反) 序列化中使用internal 属性或成员。框架在通过反射访问这些属性时反应有时会很奇怪——并且在大多数情况下,性能会受到负面影响(这是 .NET 的问题)。例如,Silverlight 会为内部类型抛出访问冲突或方法未找到异常。

基准测试  

在计算数字之前,应阐明一些容易被遗忘的要点

  • 框架开发者发布的所有基准测试,如本测试或 fastJSON,都是经过优化的结果。实际结果可能**非常**不同。这不是故意的,而是完全正常的。因此
  • 此处发布的所有结果**仅是性能指标**。您应该在考虑以下因素的情况下,比较框架在您的端到端场景中的性能: 
  • **性能变化**不仅因运行而异,还因类而异,也因其中的数据而异。 
  • 所有基准测试均针对发布时的参考版本 fastJSON 2.0.13 运行。
  • 在阅读基准测试结果时,请确保输入和输出相同 (*1)  

(*1) 例如,fastJSON 的基准测试与 BinaryFormatter 进行比较,后者以流作为输入。fastJSON 无法处理流。这一点被忽略了,但它可能导致一个错误的假设,即 fastJSON 比 BinaryFormatter 快。 

场景 1:A-FastJSON vs fastJSON x86(自定义类型) 

 

  • (A)-FastJSON 序列化通常比 fastJSON 快 20%。 
  • (A1)-FastJSON 反序列化到 IJsonValue 比序列化快,并且比 fastJSON 快 **~300%**。
  • (A2)-FastJSON 反序列化到 IJsonValue,然后构建到给定类几乎比 fastJSON 快 **~20%**(比 v0.91 快 10%) 
  • (A3)-FastJSON 反序列化到 IJsonValue,然后构建到带有类型扩展、自定义类型名称(Data Contract 支持)的给定类比 FastJson 快约 9%。 
  • (A4)-FastJSON 反序列化到对象比 fastJSON 快约 10% 或相等
  • (A5)-FastJSON 反序列化到**已知**对象比 fastJSON 快 20%(ReadObject<T>

场景 2:A-FastJSON vs fastJSON x86(自定义类型和奇异类型)  

 

  • (A)-FastJSON 序列化通常比 fastJSON 快 15%。 
  • (A1)-FastJSON 反序列化到 IJsonValue 比序列化快,并且比 fastJSON 快 **~300%**。
  • (A2)-FastJSON 反序列化到 IJsonValue,然后构建到给定类几乎比 fastJSON 快 **~20%**(比 v0.91 快 10%)  
  • (A3)-FastJSON 反序列化到 IJsonValue,然后构建到带有类型扩展、自定义类型名称(Data Contract 支持)的给定类**比** FastJson **慢**。 
  • (A4)-FastJSON 反序列化到对象比 fastJSON 快约 19% 或相等
  • (A5)-FastJSON 反序列化到**已知**对象比 fastJSON 快 15%(ReadObject<T>)** 

请注意,64 位场景不再列出,因为它们没有显示令人惊讶的度量(因此没有意义)。  

结论 

  • ApJson 和 fastJSON 都相当快。  
  • 在 64 位场景中,ApJson 的优势较低。
  • 如果需要内置数据集/数据表支持,fastJSON 可能是更好的选择。 
  • 如果需要奇异类型支持(字典、哈希集等),ApJson 可能是更好的选择。 
  • IJsonValue 转换速度惊人地快,并且似乎是一个非常好的选择——**特别是**,如果考虑到第二步,将通用字典转换为给定类型是可选的。 
  • 由于代码优化,ApJson 在其定义的多数场景下已比 FastJson 更快。 

已知问题和限制 

  • DataSetDataTable**不**支持 BuildUp 方法(*2)。不过,从一种通用类型转换为另一种类型的数值应该相当低。如果兴趣足够,可以在 IJsonValue 上添加扩展方法。 
  • 反序列化中的HashSet 不受支持(缺乏填充哈希集集合的接口)。
  • DataMember 带有 Name = "$type" 并且可比较的仍然被允许,并可能导致错误行为。
  • 当类上定义了属性索引器(也称为 this[])以进行序列化时,会抛出 InvalidProgramException
  • 反序列化到非公共类型会因 TypeAccessException 而失败。
  • UseGlobalTypes 不适用于 JsonValueDeserializer。 

 (*2) 可以通过 ReadDataTable/ ReadDataSet 方法反序列化 DataTable 和 DataSet,但此代码部分目前未进行测试。 

发布说明:  

v1.0: 

  • 使用 JsonValue 反序列化进行的小幅性能改进。
  • [新增] 现在可以反序列化到 HashSet<>
  • [新增] 允许构建 JSON 数组。
  • [新增] 反序列化为枚举类型现在尝试设置项目数组(而不是抛出异常)
  • [修复] 注册有时未遵守属性读/写说明符 
  • [修复] 反序列化到非泛型 Array 时崩溃。
  • [修复] JsonPropertyInfo.Copy 复制了 Silverlight 的过多不相关值。
  • [修复] Silverlight 属性/字段 getter 对某些类型导致 NullReferenceExceptions
  • [更改] JsonParameter.UseGlobalTypes 已被移除。 
  • 其他修复……

v.93 Release Candidate   

  • 小幅性能提升 5%-10%。 
  • [已取消] (无法为接口声明显式转换运算符) IJsonValue
  • [新增] JsonPrimitive 类上的显式转换运算符 
  • [新增] JsonParameter 已审查。旧版本缺乏对序列化选项、反序列化选项和通用选项的明确区分。 
  • [新增] 通过 'JsonDateTimeOptions.Format' 格式字符串支持自定义日期格式(请注意,支持自动转换)。 
  • [修复] JsonPrimitive decimal 使用了整数解析。 
  • [修复] JsonPrimitive ToChar 在本地值包含多个字符时未抛出异常  
  • [修复] IJsonValue 实现现在抛出承诺的 NotSupportedException 而不是 InvalidOperationException。 
  • [修复] BuildUp 错误地构建了 Dictionary<,>
  • [更改] 默认 UseExtensions 设置为 false,因为它在大多数情况下不是必需的。
  • [更改] JsonValue 构建器将成为默认反序列化器,数据表和数据集对象除外。 
  • [更改] SerializationPolicy 已弃用,将被 MemberStrategy 取代。 
  • [更改] JsonObjectJsonValueJsonArray 的可见范围已更正为 internal(它们是只读对象)。 
  • [更改] DateTimeKind 规范(本地、UTC 等)仅在字符串值是 Zulu 时间时对反序列化值产生隐式影响。否则,Kind 根据选项设置,但值不被修改。
  • [更改] 类型扩展现在默认禁用,因为这是一个高级用例,并且对性能有显著影响。  

v0.92  

在 API 稳定(v1.0)之前的第一版中间版本  

  • 移除对 xmlignore 属性的支持,它已被 DataMemberIgnoreDataMember 取代
  • 新增JsonPrimitive 自定义类型支持。 
  • 新增:Silverlight 5 支持。
  • 新增:如果尝试序列化一个没有(可见)属性的类,则会抛出 SerializationException。*以前,这将渲染为 '{}',导致接收方出现问题*。
  • 新增Json.ToJsonBytes 返回一个字节数组,代表已配置编码的 JSON 字符串字节。
  • 新增:日期时间属性可以用 DateTimeOptions 属性修饰,该属性定义了预期的日期时间种类(UTC 或非 UTC)。反序列化器将在合适时自动转换。
  • 更改:已从 JSON 类中移除 Thread static 属性。它引起了许多麻烦,因为每个线程都有自己的默认参数。对参数的任何更改都需要为每个工作线程重新设置。
  • 修复:可以为非默认 JSON 参数注册自定义类型处理程序。
  • 修复DateTime 反序列化总是转换为本地时间。现在字段可以声明 JsonDateTimeOptions 属性,允许指定所需的值。
  • 修复:如果最后一个属性为 null 或为空,并且 SerializeNullValues 为 true,则仍然会渲染一个逗号(即 { i:1,},其中 'j' 是可空的且为 null)。
  • 增加 JsonRegistry 和其他相关类的单元测试深度。
  • 检测到重复数据成员时改进了错误报告。

v0.9 从 fastJSON 2.0.9 分叉 

序列化 

  • DateTime 转 UTC 现在会尊重日期时间的种类(JsonSerializer_DateTimeUtc
  • 当列表字节被序列化时,字节数组不正确(参见 JsonSerializer_ByteEnumeration
  • 实现了 IList 的自定义类型的序列化未被考虑 
  • TimeSpan 被错误地序列化。  

反序列化

  • 字节数的反序列化失败 
  • 当给定字符串为 UTC 且参数设置为避免 UTC 日期时,返回了错误的日期时间 
JsonParameter
  • 在运行时更改序列化/反序列化值可能导致意外输出。
  • 代码自行更改了一些属性。 

MyPropInfo   

  • 字段的CanWrite 始终为 false  
  • Fill 始终为 true 且未使用。已移除。  
  • 除了名称为 'Dictionary' 的字典之外,GenericTypes 始终为 null。

基准测试

  • 修复基准测试工具中的时间测量缺陷。      

历史

2013 年 8 月 28 日:发布 v1.0(经过长时间测试)。

2013 年 1 月 30 日:发布 v0.93
2013 年 1 月 4 日:发布 v.92
2012 年 11 月 26 日:发布 v.90
2012 年 10 月 25 日:Fork 自 FastJSON。开始开发。

© . All rights reserved.