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

PowerJSON - 功能强大且快速的 JSON 序列化器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (30投票s)

2015年4月1日

CPOL

18分钟阅读

viewsIcon

81430

downloadIcon

926

这是“fastJSON”的一个分支,具有控制 JSON 序列化和反序列化多个方面的新功能,例如:序列化接口实例(多态序列化)和私有类型、包含或排除成员、执行数据转换、条件序列化等。

注意

几个月前,我针对几种 JSON 序列化器进行了一项性能测试,发现

  1. fastJSON 实际上并不那么快。
  2. Newtonsoft 的 JSON 序列化器有所发展,其性能得到了增强,与 fastJSON 相当接近
  3. 最快的 JSON 序列化器是 NetJSON,它使用动态程序集将性能提升到极致。fastJSON 的架构永远无法使其成为 NetJSON 的竞争对手。

因此,我已**弃用与 fastJSON 的同步和 PowerJSON 的开发**。

此文章因其历史原因而保留在此。我推荐 NetJSON,因为它是目前最快的 JSON 序列化器。

2018 年 2 月 3 日

目录

  1. 引言
  2. 背景
  3. 基本用法
  4. 在序列化和反序列化之前转换数据
  5. 使用 JsonConverter 序列化不支持的类型
  6. 有条件地拦截序列化和反序列化
  7. 性能如何
  8. 历史

引言

这是 的出色项目 fastJSON 的一个分支和增强版本。它添加了一些新的类、接口和自定义属性,以方便对象的 JSON 序列化和反序列化。它还修复了原始版本中的一些问题。

尽管原始版本添加了许多功能,但经过彻底的优化和重构后,性能并未牺牲,甚至有所提高。

该分支可在 CodePlex 上访问:https://github.com/wmjordan/PowerJSON

背景

如果您不熟悉 fastJSON,建议您在开始之前阅读此文章

PowerJSON 是从 fastJSON 分支出来的,并且几乎与其完全兼容。因此,PowerJSON 的程序集文件名是 fastJSON.dll,并且类型库使用 <span class="literal">fastJSON</span> 作为命名空间。

除了原始 fastJSON 的长功能列表外,PowerJSON 还提供了以下高级功能

  1. 以驼峰式或大写形式序列化成员名称,无需强制您的数据模型为了适应 JSON 协议而打破 .NET 编码约定。
  2. 在序列化中为成员分配另一个名称。
  3. 无需打开 UseExtensions 设置即可进行多态序列化:序列化和反序列化 interface 实例而不是具体类类型;序列化和反序列化 abstract 类。
  4. Enum 值分配另一个文字名称。
  5. 在序列化期间包含特定的只读成员,或将特定的 public 成员排除在序列化之外。
  6. 序列化和反序列化 private/internal 类、结构、字段或属性。
  7. 在序列化之前转换数据(例如,加密序列化数据,将数组序列化为逗号分隔的 string),反之亦然,在反序列化之前。
  8. 有条件地序列化或反序列化成员。
  9. 在序列化中写入额外数据。
  10. 在序列化或反序列化之前(或之后)转换或验证数据。
  11. 对序列化和反序列化的非侵入式控制——无需更改数据模型即可更改序列化结果,这在您没有它们的源代码时尤其有用。
  12. 序列化 HashSet<T> 和其他具有 Add(?) 方法的可枚举类型。
  13. 最简单的自定义序列化和反序列化。您可以以最少的努力实现对不支持类型的序列化。
  14. 库的文档文件。

此分支还修复了原始 fastJSON 中的一些问题,例如 TimeSpan 实例可能导致堆栈溢出,NameValueCollection 中的多值项无法像序列化之前那样反序列化等。有关更多详细信息,请参阅源代码中的 readme.md 文件。

基本用法

以下部分将描述如何使用 PowerJSON 控制序列化结果。

示例类

JSON 序列化最常见的问题之一是 JSON 结构和 .NET 类型的命名。假设我们有以下类

internal class DemoClass
{
    public string MyProperty { get; set; }

    public MyEnum MyEnumProperty { get; set; }

    public int Number { get; set; }

    public object Identifier { get; set; }

    public int InternalValue { get; set; }

    private int privateField;
}
public enum MyEnum
{
    None,
    Vip
}
public class ClassA
{
    public string Name { get; set; }
}
public class ClassB
{
    public int Code { get; set; }
}

并且我们希望满足以下要求

  • DemoClass 是一个 internal class,默认情况下,由于其构造函数不是公共可见的,因此不可序列化,但我们希望使其可反序列化。
  • 所有属性都应具有驼峰式序列化名称。
  • MyProperty 序列化为名称 "prop"。
  • MyEnumProperty 序列化为名称 "enum"。
  • 如果 Number 的值为 0,则不应序列化。
  • Identifier 可以是 ClassAClassB 类型,并且分别具有序列化名称 "a" 或 "b"。如果 Identifier 不是以上两种类型,则其序列化名称应为 "variant"。
  • InternalValue 不应序列化或反序列化。
  • privateField 应该被序列化。
  • MyEnum 类型中 Vip 字段的名称应序列化为 "VIP"。

上述要求无法通过 fastJSON 实现,但使用 PowerJSON 是可能的,并且有两种方法。

  1. 侵入式模式:向数据模型(此处为 DemoClass)添加自定义属性以控制序列化。
  2. 非侵入式模式:保持数据模型不变。以编程方式控制序列化结果。

侵入式模式:使用自定义属性控制序列化

侵入式序列化控制模式向我们想要控制的类或结构添加自定义属性。

由于此方法涉及源代码,因此称为**侵入式模式**。

以下代码显示了如何使用自定义属性控制序列化结果。

// marks the internal DemoClass class deserializable
[JsonSerializable]
internal class DemoClass
{
    // marks MyProperty property to be serialized to a field named "prop"
    [JsonField ("prop")]
    public string MyProperty { get; set; }

    // marks MyEnumProperty property to be serialized to a field named "enum"
    [JsonField ("enum")]
    public MyEnum MyEnumProperty { get; set; }

    // marks not to serialize the Number property, if its value is 0
    [JsonNonSerializedValue (0)]
    public int Number { get; set; }

    // marks the serialized name of Identifier will be "a", if its type is ClassA,
    //     and "b" for ClassB, and "variant" for other types
    [JsonField ("a", typeof (ClassA))]
    [JsonField ("b", typeof (ClassB))]
    [JsonField ("variant")]
    public object Identifier { get; set; }

    // marks the InternalValue property will not be serialized
    [JsonInclude (false)]
    // marks the InternalValue property will not be deserialized
    [System.ComponentModel.ReadOnly (true)]
    public int InternalValue { get; set; }

    // marks the privateField serializable
    [JsonSerializable]
    private int privateField;
}

public enum MyEnum
{
    None,
    // marks the serialized name of Vip to "VIP"
    [JsonEnumValue ("VIP")]
    Vip
}

使用自定义属性注释数据模型后,我们可以序列化数据模型。

首先,设置默认序列化设置以满足此要求——所有属性都应具有驼峰式序列化名称。

JSON.Parameters.NamingConvention = NamingConvention.CamelCase;
JSON.Parameters.UseExtensions = false;

其次,用一些值初始化数据模型。

var d = new Demo1.DemoClass () {
    MyProperty = "p",
    Number = 1,
    MyEnumProperty = Demo1.MyEnum.Vip,
    InternalValue = 2,
    Identifier = new ClassA () { Name = "c" }
};

最后,调用 ToJSON 方法以获取序列化结果。

var s = JSON.ToJSON (d);
Console.WriteLine (s);

以上示例给出以下结果。

{"prop":"p","enum":"VIP","number":1,"a":{"name":"c"},"privateField":0}

要将 JSON string 反序列化回 DemoClass 的实例,请使用 ToObject<span id="LST5A47910_0"><T<span id="LST5A47910_1">></span></span> 方法。

var o = JSON.ToObject<Demo1.DemoClass> (s);

非侵入式模式:使用 SerializationManager 控制序列化

侵入式模式非常简单——只需使用自定义属性标记类和成员即可完成。但是,自定义属性有一些缺点。其中一些列举如下

  1. **问题 1**:自定义属性需要修改源代码,但有时不可能,例如,我们无法修改 .NET Framework 中的 CLR 类型。
  2. **问题 2**:它们侵入数据模型并使其依赖于 PowerJSON 库。
  3. **问题 3**:它们可能会冲突,通常当同一个数据模型需要序列化为各种形式时。

为了克服上述问题,PowerJSON 引入了**非侵入式模式**的序列化控制。非侵入式模式不对数据模型进行任何修改,但提供的功能不亚于侵入式模式。

非侵入式模式主要通过以下类实现:SerializationManagerTypeOverrideMemberOverride

SerializationManager 的默认实例可以从 JSON 类中的静态 Manager 属性访问。调用其 Override<span id="LST5A47910_4"><T<span id="LST5A47910_5">></span></span> 方法,该方法接受 TypeOverride 的实例,将告诉序列化引擎如何更改序列化或反序列化的结果。

以下代码给出了使用 SerializationManager 的示例用法。

// overrides the serialization behavior of DemoClass
JSON.Manager.Override<DemoClass> (new TypeOverride () {
    // makes DemoClass always deserializable
    Deserializable = true,
    // override members of the class
    MemberOverrides = {
        // assigns the serialized name "prop" to MyProperty property
        new MemberOverride ("MyProperty", "prop"),
        new MemberOverride ("MyEnumProperty", "enum"),
        // assigns a default value to the Number property
        new MemberOverride ("Number") { NonSerializedValues = { 0 } },
        // assigns default serialized name and typed serialized name
        new MemberOverride ("Identifier", "variant") {
            TypedNames = {
                { typeof(ClassA), "a" },
                { typeof(ClassB), "b" }
            }
        },
        // denotes the InternalValue property is neither serialized nor deserialized
        new MemberOverride ("InternalValue") {
            Deserializable = false,
            Serializable = false
        },
        new MemberOverride ("privateField") {
            Serializable = true,
            Deserializable = true
        }
    }
});

// changes the serialized name of the "Vip" field of the MyEnum enum type
JSON.Manager.OverrideEnumValueNames<MyEnum> (new Dictionary<string, string> {
    { "Vip", "VIP" }
});

要序列化或反序列化数据模型,请使用与侵入式模式相同的函数调用到 JSON 类。输出将与侵入式模式示例相同。

使用 SerializationManager,可以从外部代码控制序列化。问题 1 和问题 2 已解决。要解决问题 3,即冲突序列化,我们将使用交替的 SerializationManager,这将在下面讨论。

交替 SerializationManager

为了演示自定义属性为什么会冲突以及如何通过交替 SerializationManager 解决冲突,让我们首先看看以下类。

public class Group
{
    public int ID { get; set; }
    public string Name { get; set; }
    public List<Member> Members { get; private set; } = new List<Member>();
}
public class Member
{
    public int GroupID { get; set; }
    public string Name { get; set; }
}

以上代码片段有两个类。Group 类包含一个列表,其中包含 Member 类的多个实例。

Group 类的正常序列化可能如下所示(为了清晰起见添加了空格)

{"ID": 1,
"Name": "group name",
"Members": [
    { "GroupID": 1, "Name": "a" },
    { "GroupID": 1, "Name": "b" },
    { "GroupID": 1, "Name": "c" }
]}

我们可以看到 "GroupID":1 在每个序列化的 Member 实例中重复多次。由于所有成员都属于组 1,这已经在 "ID": 1 中表示并通过 JSON 结果的级联结构暗示,因此可以省略那些 "GroupID": 1 字段以求简洁。

所以我们可能希望

  1. GroupID 字段在 Group 实例中序列化时,应将其隐藏以生成紧凑、无冗余的结果。
  2. GroupID 字段在 Member 实例中单独序列化时,应使其可见。
// the desired serialization result of a Group instance
{"ID": 1,
"Name": "group name",
"Members": [
    { "Name": "a" },
    { "Name": "b" },
    { "Name": "c" }
]}
// the desired serialization result of a Member instance
{"GroupID":1, "Name":"a"}

Member 类的 GroupID 成员上添加序列化控制可以隐藏 GroupID 字段,但这也会将其从 Member 类的单独序列化结果中隐藏,从而生成如下所示的结果,这是不可取的。

{"Name":"a"}

为了满足 GroupMember 类的两个序列化要求,我们应使用交替的 SerializationManager 并将其传递给 ToJSON(Object, JSONParameters, SerializationManager) 方法重载,该方法重载接受 SerializationManager 作为参数。

传递给 ToJSON 方法的交替 SerializationManager 可以覆盖 Group 类以隐藏 GroupID 字段,并且可以使用默认的 SerializationManager 序列化 Member 类,该类默认显示 GroupID 字段,如下面的代码所示。

var g = new Group () {
    ID = 1,
    Name = "test",
    Members = {
        new Member () { GroupID = 1, Name = "a" },
        new Member () { GroupID = 1, Name = "b" },
        new Member () { GroupID = 1, Name = "c" }
    }
};

var gsm = new SerializationManager ();
gsm.Override<Member> (new TypeOverride () {
    MemberOverrides = { new MemberOverride ("GroupID", false) }
});

// use the alternated SerializationManager
var s1 = JSON.ToJSON (g, JSON.Parameters, gsm);
Console.WriteLine ("Group: " + s1);
Assert.IsFalse (s1.Contains ("GroupID")); // "GroupID" is invisible

 // use the default SerializationManager
s1 = JSON.ToJSON (g.Members[0]);
Console.WriteLine ("Member: " + s1);
StringAssert.Contains (s1, "GroupID"); // "GroupID" is visible

以上代码输出以下结果,这是我们需要的

Group: {"ID":1,"Name":"test","Members":[{"Name":"a"},{"Name":"b"},{"Name":"c"}]}
Member: {"GroupID":1,"Name":"a"}

序列化和反序列化之前转换数据

在跨平台通信期间,我们有时可能希望将数据转换为其他形式以适应其他平台的需求。例如,

  1. 加密 JSON 中的关键信息。
  2. Array 转换为逗号分隔值。
  3. DateTime 值的不同表示。
  4. Enum 值序列化为数字。

普通序列化器可能要求您创建数据模型或添加额外字段并将其序列化。使用 PowerJSON,无需修改数据模型。这保持了原始模型的干净,同时满足了要求。这是通过 JSON 转换器功能完成的。

我们首先编写一个类来实现 IJsonConverter 接口,该接口将在序列化和反序列化之前使用。

在序列化之前,将调用该接口的 SerializationConvert 方法,并传入要序列化的对象的名称和值。相应的参数存储在 JsonItem 的实例中。在 JSON 反序列化之前,将调用 DeserializationConvert,并且 JsonItem 也将作为参数传递给该方法。

转换器可以在上述方法中更改 JsonItem 实例的 Value,因此序列化值可以与原始数据模型是不同的类型。

数据转换、加密

以下代码演示了 IJsonConverter 如何工作。在序列化期间,要加密的 string (item.Value) 将传递给 SerializationConvert。此类通过在 string 前面加上“Encrypted: ”来伪造“加密”。在反序列化中,加密的 string 将传递给 DeserializationConvert,并且该方法通过删除前缀来伪造“解密”。

class FakeEncryptionConverter : IJsonConverter
{
    public Type GetReversiveType (JsonItem item) {
        // no type conversion is required
        return null;
    }
    public void SerializationConvert (JsonItem item) {
        var s = item.Value as string;
        if (s != null) {
            item.Value = "Encrypted: " + s; // returns an encrypted string
        }
    }
    public void DeserializationConvert (JsonItem item) {
        var s = item.Value as string;
        if (s != null && s.StartsWith ("Encrypted: ")) {
            item.Value = s.Substring ("Encrypted: ".Length);
        }
    }
}

当转换器准备就绪后,我们可以将转换器应用于我们想要“加密”的任何 string 字段。在序列化之前,FakeEncryptionConverter 将“加密” MyProperty 以生成序列化结果,并在反序列化之前“解密”。DataConverterTestSample 类对所有这一切都完全不知情,并且只是像 MyProperty 从未被“加密”或“解密”过一样执行其工作。

public class DataConverterTestSample
{
    [JsonConverter (typeof(FakeEncryptionConverter))]
    public string MyProperty { get; set; }
}

数据类型转换和高级多态序列化

我们可以实现 IJsonConverter 以在序列化和反序列化之间转换不同的数据类型。例如,以下定义使用 Int32ArrayConverter 在序列化之前将 int 数组转换为 string,反之亦然,在反序列化之前。

public class CustomConverterType
{
    [JsonConverter (typeof (Int32ArrayConverter))]
    [JsonField ("arr")]
    public int[] Array { get; set; }

    public int[] NormalArray { get; set; }

    [JsonConverter (typeof (Int32ArrayConverter))]
    [JsonField ("intArray1", typeof (int[]))]
    [JsonField ("listInt1", typeof (List<int>))]
    public IList<int> Variable1 { get; set; }

    [JsonConverter (typeof (Int32ArrayConverter))]
    [JsonField ("intArray2", typeof (int[]))]
    [JsonField ("listInt2", typeof (List<int>))]
    public IList<int> Variable2 { get; set; }
}


class Int32ArrayConverter : IJsonConverter
{
    public Type GetReversiveType (JsonItem item) {
        return null;
    }
    public void DeserializationConvert (JsonItem item) {
        var s = item.Value as string;
        if (s != null) {
            item.Value = Array.ConvertAll (s.Split (','), Int32.Parse);
        }
    }
    public void SerializationConvert (JsonItem item) {
        var l = item.Value as int[];
        if (l != null) {
            item.Value = String.Join (",", Array.ConvertAll (l, Convert.ToString));
        }
    }
}

上述代码的示例输入是

var c = new CustomConverterType () {
    Array = new int[] { 1, 2, 3 },
    NormalArray = new int[] { 2, 3, 4 },
    Variable1 = new int[] { 3, 4 },
    Variable2 = new List<int> { 5, 6 }
};

输出是:

{"arr":"1,2,3","NormalArray":[2,3,4],
"intArray1":"3,4","listInt2":[5,6]}

请注意:在反序列化期间,JsonItem 的值尚未完全反序列化并转换为属性的类型。完全反序列化之前的值具有以下**原始反序列化类型**之一。有六种可能的原始反序列化类型:布尔值的 Boolean,整数的 Int64,小数(decimal 类型也转换为 Double)的 Double,文字值、enumDateTimeTimeSpan 类型的 String,数组的 IList<Object>,以及通用类或结构的 IDictionary<string, object>,当然,也可能有 null 值。传递给 DeserializationConvert 方法的 JsonItemValue 可以是以上六种类型之一。

尽管如此,您不必关心序列化期间的原始反序列化类型。序列化器可以处理转换。

为了避免处理**原始反序列化类型**,IJsonConverter 的实现应编写一个 GetReversiveType 方法,该方法告诉反序列化引擎 DeserializationConvert 方法的预期类型,反序列化器将尝试将原始值转换为 GetReversiveType 方法返回的类型。

例如,您正在尝试序列化一个 DateTime 属性,为夏令时增加一小时,并在反序列化时减去一小时。如果没有从 GetReversiveType 方法提供类型,代码将如下所示

class DayLightSavingTimeConverter : IJsonConverter {
    public Type GetReversiveType (JsonItem item) {
        return null;
    }
    public void SerializationConvert (JsonItem item) {
       if (item.Value is DateTime) {
           // converts the DateTime value to an hour later
           item.Value = ((DateTime)item.Value).AddHours(1);
       }
    }
    public void DeserializationConvert (JsonItem item) {
        // the primitive deserialization type of DateTime is string
        DateTime d = DateTime.Parse((String)item.Value, System.Globalization.CultureInfo.InvariantCulture);
        item.Value = d.AddHours(-1);
    }
}

为了简化代码,我们让 GetReversiveType 方法返回 typeof(DateTime),以告诉反序列化器预期的日期类型是 DateTime,如下所示

class DayLightSavingTimeConverter : IJsonConverter {
    public Type GetReversiveType (JsonItem item) {
       // tells the deserialization engine to convert the Value
       // in JsonItem to DateTime type
        return typeof(DateTime);
    }
    public void SerializationConvert (JsonItem item) {
       if (item.Value is DateTime) {
           // converts the DateTime value to an hour later
           item.Value = ((DateTime)item.Value).AddHours(1);
       }
    }
    public void DeserializationConvert (JsonItem item) {
       // the primitive deserialization type of DateTime is string
       // but it is converted to DateTime by the effect of
       // GetReversiveType method
        DateTime d = (DateTime)item.Value;
        item.Value = d.AddHours(-1);
    }
}

使用 JsonConverter<> 进行类型转换

**原始反序列化类型**可能会让您觉得编写 DeserializationConvert 部分有点麻烦。幸运的是,如果您的转换器类继承自 JsonConverter<> 类,反序列化器将在反序列化期间内部检测可能的类型(转换后的类型,而不是原始类型),并尽力将原始反序列化类型转换为适合 DeserializationConvert 方法的需求。

让我们通过与上面的代码进行比较来看看它是如何工作的。

class DateConverter : JsonConverter<DateTime, DateTime>
{
    protected override DateTime Convert (string fieldName, DateTime fieldValue) {
        return fieldValue.AddHours (1);
    }
 
    protected override DateTime Revert (string fieldName, DateTime fieldValue) {
        return fieldValue.AddHours (-1);
    }
}

DateConverter 基于泛型辅助类 JsonConverter<>,它实现了 IJsonConverter 接口,在转换过程中在两种特定的成员类型之间进行转换。

JsonConverter<> 类有两个 protected abstract 方法:ConvertRevertConvert 在序列化之前调用。它接受正在序列化的成员的名称和值。返回值将被序列化到 JSON 输出。Revert 在反序列化之前调用,返回值将用作反序列化的结果,或设置为反序列化对象的字段或属性。

在实现这两个方法时,您可以直接在 .NET 数据类型之间进行转换,而无需处理原始 JSON 类型。

数字枚举值序列化

使用 JsonConverter<>,还可以将特定 enum 类型专门序列化为数字,而无需在 JSONParameters 中打开 UseValuesOfEnums 设置,该设置会影响所有 enum 类型。

以下代码演示了它是如何工作的。下面的类有两个 enum 类型的属性。假设我们希望第一个属性像往常一样序列化——值以字面形式序列化,而第二个属性序列化为数字形式。我们可以使用 JsonConverterAttribute 标记第二个属性。然后我们实现 NumericEnumConverter,继承自 JsonConverter<> 类。

public class EnumTestSample
{
    public Fruits MyFruit { get; set; }
    [JsonConverter (typeof (NumericEnumConverter))]
    public Fruits NumericFruit { get; set; }
}

public class NumericEnumConverter : JsonConverter<Fruits, int>
{
    protected override int Convert (string fieldName, Fruits fieldValue) {
        return (int)fieldValue;
    }

    protected override Fruits Revert (string fieldName, int fieldValue) {
        return (Fruits)fieldValue;
    }
}

准备好上述代码后,第二个属性 NumericFruit 将被序列化为数字形式。

复杂类型转换

我们可以进行更复杂的类型转换,如下面的代码所示。该代码将把一个 string 类型的属性序列化为一个在 class PersonInfo 中定义的 JSON 结构。

public class PersonInfo
{
    public string Name { get; set; }
    public bool Vip { get; set; }
}
public class CustomConverterType
{
    [JsonConverter (typeof (PersonInfoConverter))]
    public string Master { get; set; }
    [JsonConverter (typeof (PersonInfoConverter))]
    public string Worker { get; set; }
}
class PersonInfoConverter : JsonConverter<string, PersonInfo>
{
    protected override PersonInfo Convert (string fieldName, string fieldValue) {
        return new PersonInfo () {
            Name = fieldValue.EndsWith ("*") ? 
                   fieldValue.Substring (0, fieldValue.Length - 1) : fieldValue,
            Vip = fieldValue.EndsWith ("*")
        };
    }

    protected override string Revert (string fieldName, PersonInfo fieldValue) {
        // no need to deal with the primitive deserialization type (Dictionary<string,object>) any more
        return fieldValue.Name + (fieldValue.Vip ? "*" : null);
    }
}

根据上述定义,我们创建一个实例,如下面的代码所示

var d = new CustomConverterType() { Master = "WMJ*", Worker = "Gates" };

序列化结果可能如下所示

{"Master":{"Name":"WMJ","Vip":true},
"Worker":{"Name":"Gates","Vip":false}}

如果没有 JsonConverterAttribute 的帮助,序列化结果将是这样的

{"Master":"WMJ*","Worker":"Gates"}

使用 JsonConverter 序列化不支持的类型

尽管 PowerJSON 增加了对更多类型序列化的支持。但它仍然无法涵盖实际世界中的所有数据类型。各种 JSON 序列化器都提供了支持自定义序列化的机制。fastJSON 提供了 SerializeDeserialize 委托,JSON.net 也为此目的提供了 abstract JsonConverter 类(参见此 API 文档)。然而,在所有这些机制中,您在实现自定义序列化时必须维护 JSON 的格式,并在反序列化时处理原始类型。使用 PowerJSON 中的 JsonConverter,您无需关心这些问题。只需关注数据本身就足够了,其余的将由序列化引擎处理。

自定义序列化的简单性

首先让我们看一下 JSON.net 的源代码,看看它是如何实现自定义 JsonConverter 来序列化和反序列化 Version 类的。

public class VersionConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (value == null) {
            writer.WriteNull();
        }
        else if (value is Version) {
            writer.WriteValue(value.ToString());
        }
        else {
            throw new JsonSerializationException("Expected Version object value");
        }
    }

    public override object ReadJson(JsonReader reader, 
        Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null) {
            return null;
        }
        else {
            if (reader.TokenType == JsonToken.String) {
                try {
                    Version v = new Version((string)reader.Value);
                    return v;
                }
                catch (Exception ex) {
                    throw JsonSerializationException.Create(reader, 
                    "Error parsing version string: {0}".FormatWith
                    (CultureInfo.InvariantCulture, reader.Value), ex);
                }
            }
            else {
                throw JsonSerializationException.Create(reader, 
                       "Unexpected token or value when parsing version. 
                       Token: {0}, Value: {1}".FormatWith(CultureInfo.InvariantCulture, 
                       reader.TokenType, reader.Value));
            }
        }
    }

    public override bool CanConvert(Type objectType) {
        return objectType == typeof(Version);
    }
}

上面的代码很长,您会看到它要求您了解 JsonReaderJsonWriterJsonSerializerJsonToken 类型。使用 PowerJSON,对 Version 类的转换支持,虽然内部不支持,但可以轻松实现,而无需了解 JSON 序列化引擎的任何实现细节。

public class VersionConverter : JsonConverter<Version, string>
{
    protected override string Convert (string fieldName, Version fieldValue) {
        return fieldValue != null ? fieldValue.ToString () : null;
    }

    protected override Version Revert (string fieldName, string fieldValue) {
        try {
            return fieldValue != null ? new Version (fieldValue) : null;
        }
        catch (Exception) {
            throw new JsonSerializationException ("Error parsing version string: " + fieldValue);
        }
    }
}

您可以看到您不需要考虑 WriteNullWriteValueTokenType(string)reader.Value 等。唯一需要的是在 Versionstring 类型之间进行转换的代码。一旦转换代码编写完成,就完成了。JsonConverter 将自动在对象和 JSON 字符串之间来回转换。

要应用转换器,在第一次序列化和反序列化开始之前添加一行代码,VersionConverter 将应用于 Version 类的所有实例。

JSON.Manager.OverrideConverter<Version> (new VersionConverter ());

运行以下代码,输出将是 "1.2.3.1234"(带引号)。

var v = new Version (1, 2, 3, 1234);
var s = JSON.ToJSON (v);
Console.WriteLine (s);

更复杂的自定义序列化情况

上面的例子很简单,因为类型被序列化为 string。下一个例子稍微复杂一些,它序列化 Regex

Regex 类通常无法序列化或反序列化,因为它没有公开模式属性(这是创建实例所必需的),也没有提供反序列化器所需的无参数构造函数。

JSON.netRegex 的序列化实现太长,无法在此处列出。您可以点击此链接阅读这 100 多行代码。对于 PowerJSON,这种情况仍然很简单。

要序列化 Regex 类,我们可以创建一个模型类 RegexInfo,其中包含 patternoptions 字段,并将 Regex 类转换为该模型。由于该模型具有默认构造函数和所有必需字段,因此可以由 JSON 序列化器和反序列化器处理。这是 Regex 转换器的实现。

class RegexConverter : JsonConverter<Regex, RegexConverter.RegexInfo>
{
    protected override RegexInfo Convert (string fieldName, Regex fieldValue) {
        return new RegexInfo () { Pattern = fieldValue.ToString (), Options = fieldValue.Options };
    }
    protected override Regex Revert (string fieldName, RegexInfo fieldValue) {
        return new Regex (fieldValue.Pattern, fieldValue.Options);
    }

    [JsonSerializable]
    internal struct RegexInfo
    {
        public string Pattern;
        public RegexOptions Options;
    }
}

以上 16 行(包括空行)使得 Regex JSON 序列化器几乎可以使用了。最后要做的是将转换器分配给 Regex,如下行所示。之后,所有 Regex 实例都可以序列化和反序列化。其实现比 JSON.net 或原始 fastJSON 更自然、更简单,后者要求您编写原始 JSON 字符串并将其读回。

JSON.Manager.OverrideConverter<Regex> (new RegexConverter ());
技巧

PowerJSON 中的 fastJSON.BonusPack.Converters 包含各种可用的转换器。

有条件地拦截序列化和反序列化

PowerJSON 允许您根据运行时的外部情况有条件地序列化或反序列化特定成员。这可以通过 IJsonInterceptor 接口和 JsonInterceptor<T> 类以及 JsonInterceptorAttribute 来实现。

对象的序列化有三个可以作为拦截点的阶段

  1. 序列化引擎获取要序列化的对象。
  2. 序列化引擎查看对象的结构,并遍历每个字段或属性以进行序列化。
  3. 序列化引擎完成序列化。

拦截器将允许您在上述三个阶段进行控制,以确定以下五个问题

  1. 是否应序列化特定对象。
  2. 是否应序列化对象中的成员。
  3. 在序列化之前是否应更改序列化成员的名称或值。
  4. 序列化对象是否应附加任何信息。
  5. 序列化完成后是否应采取任何进一步的行动。

使用拦截器,我们可以...

  1. 确定对象或其特定成员是否应序列化或反序列化。
  2. 在序列化和反序列化期间更改值。
  3. 跟踪和调试序列化和反序列化。
  4. 将额外字段和值写入序列化结果。

以下部分将展示如何使用拦截器。

使用 JsonInterceptorAttribute 将拦截器应用于类

给定一个类,如下面的代码所示。

[JsonInterceptor (typeof (TestInterceptor))]
public class InterceptorTestSample
{
    public int Value;
    public string Text;
    public bool Toggle;
    public string HideWhenToggleTrue = "Show when toggle false";
    public DateTime Timestamp;
}

当我们进行序列化时,我们希望 Timestamp 字段显示序列化发生的时间。当 Toggle 字段为 true 时,HideWhenToggleTrue 字段被隐藏。

为了实现上述要求,我们可以使用 JsonInterceptor<T> 类。我们首先使用 [JsonInterceptor (typeof (TestInterceptor))] 属性注释 InterceptorTestSample 类。

之后,我们通过覆盖 JsonInterceptor<InterceptorTestSample> 类来实现 IJsonInterceptor 接口。代码中的粗体文本将满足上述要求。

class TestInterceptor : JsonInterceptor<InterceptorTestSample> {
    public override bool OnSerializing (InterceptorTestSample obj) {
        // we can change the value to be serialized here
        obj.Value = 1;
        Console.WriteLine ("serializing.");
        return true;
    }
    public override void OnSerialized (InterceptorTestSample obj) {
        obj.Value = 2;
        Console.WriteLine ("serialized.");
    }
    public override bool OnSerializing (InterceptorTestSample obj, JsonItem item) {
        Console.WriteLine ("serializing " + item.Name);
        if (item.Name == "Text") {
            obj.Timestamp = DateTime.Now;
            item.Value = "Changed at " + obj.Timestamp.ToString ();
        }
        else if (item.Name == "HideWhenToggleTrue" && obj.Toggle) {
            return false;
        }
        return true;
    }
    public override void OnDeserializing (InterceptorTestSample obj) {
        obj.Value = 3;
        Console.WriteLine ("deserializing.");
    }
    public override void OnDeserialized (InterceptorTestSample obj) {
        obj.Value = 4;
        Console.WriteLine ("deserialized.");
    }
    public override bool OnDeserializing (InterceptorTestSample obj, JsonItem item) {
        Console.WriteLine ("deserializing " + item.Name);
        // we can change the serialized value here
        if (item.Name == "Text") {
            item.Value = "1";
        }
        return true;
    }
}

我们使用以下代码序列化和反序列化该值。

public void IntercepterTest () {
    var d = new InterceptorTestSample ();
    var s = JSON.ToJSON (d, _JP);
    Console.WriteLine (s);

    var o = JSON.ToObject<InterceptorTestSample> (s);

    d.Toggle = true;
    s = JSON.ToJSON (d, _JP);
    Console.WriteLine (s);
}

代码的输出如下

serializing.
serializing Value
serializing Text
serializing Toggle
serializing HideWhenToggleTrue
serializing Timestamp
serialized.
{"Value":1,"Text":"Changed at 2015-5-11 14:37:09","Toggle":false,
"HideWhenToggleTrue":"Show when toggle false","Timestamp":"2015-05-11T14:37:09"}
deserializing.
deserializing Value
deserializing Text
deserializing Toggle
deserializing HideWhenToggleTrue
deserializing Timestamp
deserialized.

serializing.
serializing Value
serializing Text
serializing Toggle
serializing HideWhenToggleTrue
serializing Timestamp
serialized.
{"Value":1,"Text":"Changed at 2015-5-11 14:37:09","Toggle":true,"Timestamp":"2015-05-11T14:37:09"}

使用 SerializationManager 将拦截器应用于类

上面的代码演示了如何使用*侵入式模式*将 JsonInterceptor 应用于类。

有时,我们没有源代码。以下示例将尝试有条件地序列化 System.Net.WebException 类。很明显,我们没有 WebException 类的源代码。我们必须使用 SerializationManager 来控制该类的序列化结果。此示例还演示了如何过滤掉不希望序列化的字段。

首先,我们创建拦截器类。它将命令序列化引擎在 SerializeExtraValues 方法中向序列化结果附加两个额外的名称-值对。它还过滤要序列化的属性。在此示例中,仅序列化 StatusMessage 属性。

public class WebExceptionJsonInterceptor : JsonInterceptor<System.Net.WebException>
{
    // Adds extra values to the serialization result
    public override IEnumerable<JsonItem> SerializeExtraValues (System.Net.WebException obj) {
        return new JsonItem[] {
            new JsonItem ("exceptionTime", DateTime.Now),
            new JsonItem ("machine", Environment.MachineName)
        };
    }
    public override bool OnSerializing (System.Net.WebException obj, JsonItem item) {
        // filter properties
        switch (item.Name) {
            case "Status":
            case "Message":
                // show the above properties
                return true;
            default:
                // hide other properties
                return false;
        }
    }
}

其次,我们调用 SerializationManager 中的 OverrideInterceptor 方法将拦截器应用于该类。

JSON.Manager.OverrideInterceptor<System.Net.WebException> (new WebExceptionJsonInterceptor ());

以下代码将给出一些输出

try {
    var c = System.Net.WebRequest.Create ("http://inexistent-domain.com");
    using (var r = c.GetResponse ()) {
    }
}
catch (System.Net.WebException ex) {
    string s = JSON.ToJSON (ex, p);
    Console.WriteLine (s);
}

代码的输出如下所示(为清晰起见换行)

{"httpstatus":"NameResolutionFailure",
"message":"Unable to resolve the host name: 'inexistent-domain.com'",
"exceptionTime":"2015-05-11T06:55:08Z",
"machine":"WMJ"}

性能如何?

尽管原始 fastJSON 添加了许多功能,但经过多次大规模重构后,性能从未下降,反而有所提高。根据两个项目提供的 consoletest 基准测试工具,PowerJSON 比原始 fastJSON 快约 5% 到 20%,而原始 fastJSON 已经足够快了。

历史

  1. 首次公开发布于 CodeProject:2015-4-1
  2. 更新源代码文件,引入新的 SerializeStaticMembers 设置,添加了关于使用 JsonConverter<> 进行 enum 值序列化的描述:2015-4-3
  3. 重大更改:所有扩展属性都以 Json 命名。更新源代码文件,添加了关于升级后的 JsonConverter<> 的更多描述:2015-4-14
  4. 版本 2.3.3:重写此文章以反映 PowerJSON 的最新更改。下载中还附带了文档文件和编译后的 DLL 文件。2015-5-11
  5. 版本 2.3.4:IJsonConverter 可应用于 classesstructs。性能增强。重大更改:原始类型中的 Dictionary<string, object> 更改为 IDictionary<string,object> 2015-5-22
  6. 版本 2.3.5:重大更改JsonConverter<,> 中的 ConvertRevert 方法更改为 protected,以允许序列化类型为 internal 类型。添加了更多自定义 IJsonConverter。将 SerliazeDeserialize 委托标记为已过时,因为它们可以被 IJsonConverter 取代。2015-5-24
  7. 版本 2.4:允许私有成员序列化。部分支持 DataContractAttribute重大更改MemberOverride 类的 SerializableDeserializable 属性更改为可为空的布尔值。2015-7-17
  8. 版本 2.5:支持序列化实现 IEnumerable 的类型。2015-8-25
© . All rights reserved.