Json.NET 序列化解析

Json.NET 是一款 .NET 平台的 JSON 框架,它还有一个我们熟知的名字:Newtonsoft.Json。

1. 介绍

Json.NET 有如下功能及特点:

  • 灵活的 JSON 序列化器,用于 .NET 对象和JSON之间的转换;
  • 用于手动读写 JSON 的 LINQ to JSON;
  • 带缩进的,易于阅读的 JSON 格式;
  • JSON 与 XML 之间相互转换;
  • 高性能,比 .NET 内置的 JSON 序列化器快

下面两种方法用于 .NET 对象和 Json 字符串之间的简单转换。

1.1 JsonConvert 类

JsonConvert 提供了 SerializeObject() 和 DeserializeObject() 两个静态方法来完成序列化与反序列化:

namespace Newtonsoft.Json
{
    public static class JsonConvert
    {
        public static string SerializeObject(object? value, Formatting formatting);
		public static T? DeserializeObject<T>(string value);
        ...
    }
}

Person 类:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public Gender Gender { get; set; }
}
public enum Gender
{
    Male = 0,
    Female = 1
}
using Newtonsoft.Json;

Person person1 = new Person() 
{ 
    Name = "张三", 
    Age = 20, 
    Gender = Gender.Male 
};

// 序列化
string json = JsonConvert.SerializeObject(person1, Formatting.Indented);
Console.WriteLine(json);
// 打印结果
{
  "Name": "张三",
  "Age": 20,
  "Gender": 0
}
// 反序列化
Person person2 = JsonConvert.DeserializeObject<Person>(json);
Console.WriteLine(person2.Name);
// 打印结果
张三

1.2 JsonSerializer 类

JsonSerializer 提供了 Serialize() 和 Deserialize() 两个方法将 JSON 字符串读写到流中完成序列化与反序列化:

namespace Newtonsoft.Json
{
    public class JsonSerializer
    {
        public void Serialize(TextWriter textWriter, object? value);
        public object? Deserialize(TextReader reader, Type objectType);
        ...
    }
}
using Newtonsoft.Json;

Person person1 = new Person() 
{ 
    Name = "张三", 
    Age = 20, 
    Gender = Gender.Male 
};
JsonSerializer serializer = new JsonSerializer();
serializer.Formatting = Formatting.Indented;
// 序列化
using(TextWriter writer = new StreamWriter("person.json"))
{
    serializer.Serialize(writer, person1);
}
// person.json
{
  "Name": "张三",
  "Age": 20,
  "Gender": 0
}

// 反序列化
using(TextReader reader = new StreamReader("person.json"))
{
    Person person2 = (Person)serializer.Deserialize(reader, typeof(Person));
    Console.WriteLine(person2.Name);
}
// 打印结果
张三

JsonSerializer 还定义了很多属性来自定义序列化设置。

2. Serialization Settings(序列化设置)

MissingMemberHandling

MissingMemberHandling 属性用于控制反序列化时,当 JSON 包含一个不属于对象成员的属性时如何处理:

描述
Ignore 默认值,在反序列化时会忽略这些不存在的 JSON 属性
Error 缺少成员时抛出错误
using Newtonsoft.Json;
// person.json
{
  "Name": "张三",
  "Age": 20,
  "Gender": 0,
  "IsMarried": false
}
// 反序列化
JsonSerializer serializer = new JsonSerializer();
serializer.MissingMemberHandling = MissingMemberHandling.Error;
using(TextReader reader = new StreamReader("person.json"))
{
    Person person1 = (Person)serializer.Deserialize(reader, typeof(Person));
}
// 打印结果
Unhandled exception. Newtonsoft.Json.JsonSerializationException: Could not find member 'IsMarried' on object of type 'Person'. Path 'IsMarried'

NullValueHandling

NullValueHandling 属性用于控制序列化与反序列化时,如何处理空值:

描述
Include 默认值,序列化时将空值写入JSON,反序列化时,将空值赋给对象字段/属性
Ignore 忽略空值,反序列化时,对象成员使用默认值
using Newtonsoft.Json;

Person person1 = new Person()
{ 
    Name = null,
    Age = 20,
    Gender = Gender.Male
};
JsonSerializer serializer = new JsonSerializer();
serializer.Formatting = Formatting.Indented;
serializer.NullValueHandling = NullValueHandling.Ignore;
// 序列化
using(TextWriter writer = new StreamWriter("person.json"))
{
    serializer.Serialize(writer, person1);
}
// person.json
{
  "Age": 20,
  "Gender": 0
}

DefaultValueHandling

DefaultValueHandling 属性用于控制序列化与反序列化时,如何处理默认值:

描述
Include 默认值,序列化时将默认值写入JSON,反序列化时,将默认值赋给对象字段/属性
Ignore 序列化与反序列化时,忽略值为默认值的属性
using Newtonsoft.Json;
Person person1 = new Person()
{ 
    Name = "张三",
    Age = 20,
    Gender = Gender.Male
};
JsonSerializer serializer = new JsonSerializer();
serializer.Formatting = Formatting.Indented;
serializer.DefaultValueHandling = DefaultValueHandling.Ignore;
// 序列化
using(TextWriter writer = new StreamWriter("person.json"))
{
    serializer.Serialize(writer, person1);
}
// person.json
{
  "Name": "张三",
  "Age": 20
}

ConstructorHandling

ConstructorHandling 属性用于反序列化时控制如何使用构造函数:

描述
Default 默认值,首先会查找标记了 JsonConstructorAttribute 特性的构造函数,然后查找公共无参的构造函数,接下来查找单个带参数的公共构造函数,最后查找非公共默认构造函数。如果类型只有多个带参数的公共构造函数,可以使用 JsonConstructorAttribute 特性,否则,会抛出错误。
AllowNonPublicDefaultConstructor 在查找带参数的构造函数前会先查找私有的默认构造函数

Person 类:

public class Person
{
    private Person()
    {
        Console.WriteLine("private default constructor");
    }
    //[JsonConstructor]
    public Person(string name)
    {
        Console.WriteLine($"public constructor: {name}");
    }

    public string Name { get; set; }
    public int Age { get; set; }
    public Gender Gender { get; set; }
}
using Newtonsoft.Json;
// person.json
{
  "Name": "张三",
  "Age": 20,
  "Gender": 0
}
JsonSerializer serializer = new JsonSerializer();
serializer.ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor;
// 反序列化
using (TextReader reader = new StreamReader("person.json"))
{
    Person person2 = (Person)serializer.Deserialize(reader, typeof(Person));
    Console.WriteLine("Name:"+person2.Name);
    Console.WriteLine("Age:"+person2.Age);
    Console.WriteLine("Gender:"+person2.Gender);
}
// 打印结果 1
private default constructor
Name:张三
Age:20
Gender:Male
// 取消注释,打印结果 2
public constructor: 张三
Name:
Age:20
Gender:Male

当使用带参数的构造函数时,会查找 JSON 中与形参匹配(不区分大小写)的属性,如果没有对应的属性,则参数值为默认值;反之,使用对应的属性值,同时,类型的同名成员使用默认值。如果类型不同,会进行默认转换,转换可能会抛出异常。

3. Serialization Attributes(序列化特性)

Json.NET 中的特性可以用于控制如何序列化与反序列化 .NET 对象。在此之前,先来了解一下 .NET 中的特性(Attribute)。

3.1 .NET 特性

特性(attribute)是一种允许我们向程序的程序集添加元数据的语言结构。它是用于保 存程序结构信息的特殊类型的类。

可以通过在结构前放置特性片段来应用某一特性,特性片段由方括号包围特性名和参数列表实现,如下所示:

[特性名]
[特性名(参数1,...)]

按照惯例,特性名使用 Pascal 命名法并且以 Attribute 后缀结尾,当应用特性时,可以不使用后缀。例如 .NET 的预定义特性 SerializableAttribute 可以这样应用:

[Serializable]
public class MyClass
{
	...
}

.NET 预定义了很多特性,我们也可以声明自定义特性。

3.1.1 预定义的保留特性

在了解如何自定义特性之前,本节先介绍几个 .NET 预定义特性。

Obsolete 特性

Obsolete 特性可以将程序结构标注为 “弃用”。在很多时候,我们经常需要编写新方法来替换老方法实现类似的功能,同时也希望之后的团队成员都使用新方法,要警告他们不要用旧方法,这种情况下就可以使用 Obsolete 特性,例如:

public class MyClass
{
    [Obsolete] //无参数,生成默认警告信息
    public void Method1()
    {
    }
    [Obsolete("请使用 MyClass.Method4()")] // 带附加信息,生成带附加信息的警告
    public void Method2()
    {
    }
    [Obsolete("请使用 MyClass.Method4()", true)] // 带附加信息,生成带附加信息的错误
    public void Method3()
    {
    }
    public void Method4()
    {
    }
}

在编写代码的时候,如果我们尝试调用旧方法,编辑器会给出 “[弃用的]” 提示,编译程序时,控制台会输出相应的警告和错误来通知我们正在使用一个过时的结构:

// 调用旧方法
MyClass myClass = new MyClass();
myClass.Method1();
myClass.Method2();
myClass.Method3();

// 编译时控制台输出
warning CS0612: “MyClass.Method1()”已过时
warning CS0618: “MyClass.Method2()”已过时:“请使用 MyClass.Method4()”
error CS0619: “MyClass.Method3()”已过时:“请使用 MyClass.Method4()”

Conditional 特性

Conditional 特性允许我们包括或排斥特定方法的所有调用。该特性用于指示编译器,除非定义了指定的条件编译符号,否则应忽略方法调用。

例如:

#define WIN10
...
public class MyClass
{
    [Conditional("WIN10")]
    public void Method1()
    {
        Console.WriteLine("Method1");
    }
}
// 方法调用
MyClass myClass = new MyClass();
myClass.Method1();

由于定义了编译符号,那么编译器会包含所有调用这个方法的代码,和普通方法没什么区别,否则,编译器会忽略代码中这个方法的所有调用。定义方法的CIL代码本身总是会包含在程序集中,只是调用代码会被插入或忽略。

在方法上使用 Conditional 特性有如下规则:

  • 该方法必须是类或结构体的方法;
  • 该方法必须是 void 类型;
  • 该方法不能被声明为 override,但可以标记为 virtual;
  • 该方法不能是接口方法的实现。

其他预定义特性

.NET 框架预定义了很多编译器和 CLR 能理解和解释的特性,如下表所示;

特性 意义
CLSCompliant 声明公开暴露的成员应该被编译器检测其是否符合 CLS。兼容的程序集可以被任何兼容 .NET 的语言使用
Serializable 声明结构可以被序列化
NonSerialized 声明结构不能被序列化
DLLImport 声明是非托管代码实现的
AttributeUsage 声明特性能应用于什么类型的程序结构。将这个特性应用到特性声明上

3.1.2 自定义特性

特性是一种特殊的类,所有特性类都派生自 System.Attribute 类,特性类以 Attribute 为后缀,一般声明为 sealed。

public sealed class MyAttributeAttribute : System.Attribute
{
    public MyAttributeAttribute(string varA, string varB)
    {
        ...
    }
    public string VarC { get; set; }
}

由于特性持有目标的信息,所有特性类的公有成员只能是:字段、属性和构造函数。

特性和其他类一样,有构造函数。每一个特性必须至少有一个公共构造函数。当我们为目标应用特性时,其实是在指定应该使用哪个构造函数来创建特性的实例。在应用特性时,构造函数的实参必须是在编译时能确定值的常量表达式。如果没有参数,可以省略圆括号。

构造函数中的位置参数和命名参数

与普通的类相似,特性的构造函数可以使用位置参数和命名参数,同时还可以在构造函数中为公共成员赋值,例如:

[MyAttribute("a", varB:"b", VarC ="c")]

限制特性的使用

由于特性本身就是类,所以我们可以将 AttributeUsage 特性应用到自定义特性上,使用它来限制将特性应用在某个目标类型上。

例如,如果希望自定义特性 MyAttribute 只能应用到字段和属性上,那么可以以如下形式使用 AttributeUsage:

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class MyAttributeAttribute : System.Attribute
{
	...
}

访问特性

  • 使用 System.Reflection.MemberInfo 的 IsDefined 方法可以判断某个特性是否应用到了某个结构上;
  • 使用 System.Reflection.MemberInfo 的 GetCustomAttributes 方法可以获取到应用到某个结构的所有特性实例(特性的实例在这个时候创建)。

自定义特性实践

现在有这么一个需求,我们希望获取某个枚举字段的中文意义,这在打印枚举值的时候很有用,比如下面这个枚举:

public enum Unit
{
    Character = 1,
    Point = 2, 
    CM = 3
}

当我们想打印 Unit.Character 的时候,希望输出的是它的中文意义“字符”。那么,我们就可以利用特性,将携带中文意义的特性应用到枚举字段上,如下所示:

using System;

[AttributeUsage(AttributeTargets.Field)] // 限制只能在字段上应用
public sealed class DescriptionAttribute : Attribute
{
    // 指定描述信息
    public DescriptionAttribute(string description)
    {
        Val = description;
    }

    public string Val { get; private set; }
}

现在,将 Description 特性应用到 Unit 枚举上:

public enum Unit
{
    [Description("字符")]
    Character,
    [Description("磅")]
    Point, 
    [Description("厘米")]
    CM
}

然后,通过获取字段的特性实例,就可以读取到我们添加的中文描述信息:

Unit unit = Unit.Character;
DescriptionAttribute attr = typeof(Unit) // 获取类型信息
    .GetField(unit.ToString()) // 获取字段信息
    .GetCustomAttribute<DescriptionAttribute>(); // 获取自定义特性
if(attr != null)
{
    Console.WriteLine(attr.Val);
}
// 打印结果
字符

最后,我们将上述代码封装为一个扩展方法:

public static class EnumExtentions
{
    public static string Description<T>(this T value) where T : Enum
    {
        Type type = value.GetType();
        string fieldName = Enum.GetName(type, value);
        DescriptionAttribute attr = type.GetField(fieldName).GetCustomAttribute<DescriptionAttribute>();
        return attr?.Val ?? fieldName;
    }
}

// 
Unit unit = Unit.Character;
Console.WriteLine(unit.Description());

3.2 Json.NET 序列化特性

JsonObjectAttribute

JsonObjectAttribute 特性可以应用于 .NET 类上,结构如下:

namespace Newtonsoft.Json
{
    public sealed class JsonObjectAttribute : JsonContainerAttribute
	{
    	public JsonObjectAttribute(MemberSerialization memberSerialization);
    	...
	}
}

JsonObjectAttribute 特性中有一个 MemberSerialization 枚举标志,该枚举有三个成员:

描述
OptOut 所有的公共成员默认会被序列化。可以使用 JsonIgnoreAttribute 特性来取消序列化。这是默认的成员序列化模式。
OptIn 只有标记了 JsonPropertyAttribute 特性的成员才能被序列化。

例如:

[JsonObject(MemberSerialization.OptIn)]
public class Person
{
    [JsonProperty]
    public string Name { get; set; }
    public int Age { get; set; }
    public Gender Gender { get; set; }
}

JsonPropertyAttribute

JsonPropertyAttribute 特性的结构如下:

using System;
namespace Newtonsoft.Json
{
    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
    public sealed class JsonPropertyAttribute : Attribute
    {
        public JsonPropertyAttribute();
        public JsonPropertyAttribute(string propertyName);
        public string? PropertyName { get; set; }
        ...
    }
}

该特性有下列作用:

  • 默认情况下,JSON 属性名称与 .NET 属性相同。此特性允许自定义该名称;
  • 当 MemberSerialization 为 OptIn 时可以用该特性指定要参与序列化的成员;
  • 指定非公共成员参与序列化。

示例,修改 Person 类如下:

public class Person
{
    [JsonProperty("姓名")]
    public string Name { get; set; }
    [JsonProperty("年龄")]
    public int Age { get; set; }
    [JsonProperty("性别")]
    public Gender Gender { get; set; }
}
using Newtonsoft.Json;
Person person1 = new Person()
{ 
    Name = "张三",
    Age = 20,
    Gender = Gender.Male
};
JsonSerializer serializer = new JsonSerializer();
serializer.Formatting = Formatting.Indented;
// 序列化
using(TextWriter writer = new StreamWriter("person.json"))
{
    serializer.Serialize(writer, person1);
}
// person.json
{
  "姓名": "张三",
  "年龄": 20,
  "性别": 0
}

同理,反序列化时,会查找名称为指定 PropertyName 的 JSON 属性对成员进行赋值。

JsonIgnoreAttribute

JsonIgnoreAttribute 特性用于序列化时排除某个字段或属性。

public class Person
{
    [JsonIgnore]
    public string Name { get; set; }
    [JsonProperty("年龄")]
    public int Age { get; set; }
    [JsonProperty("性别")]
    public Gender Gender { get; set; }
}
// 序列化
{
  "年龄": 20,
  "性别": 0
}

JsonConverterAttribute

JsonConverterAttribute 特性用于指定对象转换时使用的 JsonConverter。该特性可以用于类和成员,当应用于成员(字段和属性)时,则该成员的值由该转换器进行序列化。

该特性的结构如下:

namespace Newtonsoft.Json
{
    public sealed class JsonConverterAttribute : Attribute
    {
        public JsonConverterAttribute(Type converterType);
    }
}

特性的参数需要一个 JsonConverter 类型,下列示例使用了 Json.NET 内置的 StringEnumConverter:

public class Person
{
    [JsonProperty("姓名")]
    public string Name { get; set; }
    [JsonProperty("年龄")]
    public int Age { get; set; }
    [JsonProperty("性别")]
    [JsonConverter(typeof(StringEnumConverter))]
    public Gender Gender { get; set; }
}
// 序列化
{
  "姓名": "张三",
  "年龄": 20,
  "性别": "Male"
}

4. 自定义 JsonConverter

JsonConverter 类的结构如下:

namespace Newtonsoft.Json
{
    public abstract class JsonConverter
    {
        // 序列化时调用
        public abstract void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer);

        // 反序列化时调用
        public abstract object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer);

        // 判断当前类型是否支持转换
        public abstract bool CanConvert(Type objectType);

        // 指示当前转换器是否支持读JSON
        public virtual bool CanRead => true;

        // 指示当前转换器是否支持写JSON
        public virtual bool CanWrite => true;
    }
}

由于 JsonConverter 是一个抽象类,所以我们应该定义它的派生类。

首先来了解一下 StringEnumConverter 的实现:

点击查看代码
namespace Newtonsoft.Json.Converters
{
    public class StringEnumConverter : JsonConverter
    {
        public StringEnumConverter()
        {
        }
        // 序列化时写 JSON
        public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
        {
            if (value == null)
            {
                writer.WriteNull();
                return;
            }

            Enum e = (Enum)value;

            if (!EnumUtils.TryToString(e.GetType(), value, NamingStrategy, out string? enumName))
            {
                if (!AllowIntegerValues)
                {
                    throw JsonSerializationException.Create(null, writer.ContainerPath, "Integer value {0} is not allowed.".FormatWith(CultureInfo.InvariantCulture, e.ToString("D")), null);
                }

                // enum value has no name so write number
                writer.WriteValue(value);
            }
            else
            {
                writer.WriteValue(enumName);
            }
        }
		// 反序列化时读 JSON
        public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null)
            {
                if (!ReflectionUtils.IsNullableType(objectType))
                {
                    throw JsonSerializationException.Create(reader, "Cannot convert null value to {0}.".FormatWith(CultureInfo.InvariantCulture, objectType));
                }

                return null;
            }

            bool isNullable = ReflectionUtils.IsNullableType(objectType);
            Type t = isNullable ? Nullable.GetUnderlyingType(objectType)! : objectType;

            try
            {
                if (reader.TokenType == JsonToken.String)
                {
                    string? enumText = reader.Value?.ToString();

                    if (StringUtils.IsNullOrEmpty(enumText) && isNullable)
                    {
                        return null;
                    }

                    return EnumUtils.ParseEnum(t, NamingStrategy, enumText!, !AllowIntegerValues);
                }

                if (reader.TokenType == JsonToken.Integer)
                {
                    if (!AllowIntegerValues)
                    {
                        throw JsonSerializationException.Create(reader, "Integer value {0} is not allowed.".FormatWith(CultureInfo.InvariantCulture, reader.Value));
                    }

                    return ConvertUtils.ConvertOrCast(reader.Value, CultureInfo.InvariantCulture, t);
                }
            }
            catch (Exception ex)
            {
                throw JsonSerializationException.Create(reader, "Error converting value {0} to type '{1}'.".FormatWith(CultureInfo.InvariantCulture, MiscellaneousUtils.ToString(reader.Value), objectType), ex);
            }

            // we don't actually expect to get here.
            throw JsonSerializationException.Create(reader, "Unexpected token {0} when parsing enum.".FormatWith(CultureInfo.InvariantCulture, reader.TokenType));
        }

        // 如果是枚举类型,则可以转换
        public override bool CanConvert(Type objectType)
        {
            Type t = (ReflectionUtils.IsNullableType(objectType))
                ? Nullable.GetUnderlyingType(objectType)!
                : objectType;

            return t.IsEnum();
        }
    }
}

结合之前的 DescriptionAttribute 特性,现在我希望将枚举值序列化为它的特性描述。例如:

public enum Gender
{
    [Description("男性")]
    Male = 0,
    [Description("女性")]
    Female = 1
}

于是,定义了一个 MyStringEnumConverter 类继承于 StringEnumConverter,如下所示:

public class MyStringEnumConverter : StringEnumConverter
{
    public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
    {
        Enum e = (Enum)value;
        writer.WriteValue(e.Description());
    }

    public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
    {
        // 遍历枚举的所有字段
        foreach(var fieldInfo in objectType.GetFields())
        {
            Enum.TryParse(objectType, fieldInfo.Name, out object obj);
	    // 返回描述信息与之匹配的字段
            if ((obj as Enum)?.Description() == reader.Value.ToString())
                return obj;
        }
        return null;
    }
}

现在,改用 MyStringEnumConverter 作为性别的转换器:

public class Person
{
    [JsonProperty("姓名")]
    public string Name { get; set; }
    [JsonProperty("年龄")]
    public int Age { get; set; }
    [JsonProperty("性别")]
    [JsonConverter(typeof(MyStringEnumConverter))]
    public Gender Gender { get; set; }
}
// 序列化结果
{
  "姓名": "张三",
  "年龄": 20,
  "性别": "男性"
}

5. ContractResolver - 数据协定解析器

前面我们已经知道可以使用 JsonProperty 特性自定义属性名称,使用 JsonIgnore 特性忽略序列化。它们都有一个共同点:序列化与反序列化过程是同步的。

public class Person
{
    [JsonProperty("姓名")]
    public string Name { get; set; }
    public int Age { get; set; }
    [JsonIgnore]
    public Gender Gender { get; set; }
}

序列化时,Name 属性会被序列化为 “姓名”,Gender 属性会被忽略; 反序列化时,Name 属性需要一个名为 “姓名” 的 JSON属性,而不是 “Name”,并且 JSON 中的 Gender 属性也会被忽略。

假设有这样一种场景,我们作为一个数据中转服务,接收 A 端发来的 JSON 数据,处理后再转发给 B 端,其中各端的数据格式要求如下:

A:
{
  "name": "张三",
  "age": 20,
  "gender": 0,
  "height": 170,
  "weight": 60
}
B:
{
  "姓名": "张三",
  "年龄": 20,
  "性别": 0,
}

在处理时,我们需要更换 Name,Age,Gender 的属性名,同时去除 height 和 weight 属性。在这种情况下, JsonProperty 和 JsonIgnore 就不适用了。

这时,我们就需要自定义数据协定解析器。数据协定是一组字段的抽象说明,其中包含每个字段的名称和数据类型。通过该解析器,就可以自定义属性的名称了。首先,我们需要自定义一个特性用于保存属性名称:

using System;
class CustomPropertyAttribute : Attribute
{
    public CustomPropertyAttribute()
    {
        Serializable = true;
        Deserializable = true;
    }
    /// <summary>
    /// 属性是否可以被序列化
    /// </summary>
    public bool Serializable { get; set; }
    /// <summary>
    /// 属性是否可以被反序列化
    /// </summary>
    public bool Deserializable { get; set; }
    /// <summary>
    /// 属性序列化的Json字段名
    /// </summary>
    public string SerializeName { get; set; }
    /// <summary>
    /// 属性反序列化的Json字段名
    /// </summary>
    public string DeserializeName { get; set; }
}

接着基于 Json.NET 的 DefaultContractResolver 分别定义序列化和反序列化解析器:

using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

// 序列化解析器
public class ContractSerializeResolver : DefaultContractResolver
{
    /// <summary>
    /// 重写 CreateProperty,自定义创建 <see cref="JsonProperty"/>。
    /// </summary>
    /// <param name="member">成员信息</param>
    /// <param name="memberSerialization"></param>
    /// <returns></returns>
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);
        CustomPropertyAttribute attr = member.GetCustomAttribute(typeof(CustomPropertyAttribute)) as CustomPropertyAttribute;
        if(attr != null)
        {
            // 修改属性名为特性的 SerializeName
            property.PropertyName = attr.SerializeName ?? property.PropertyName;
            // 设置当前属性是否支持序列化
            property.Readable = attr.Serializable;
        }
        return property;
    }
}

// 反序列化解析器
public class ContractDeserializeResolver : DefaultContractResolver
{
    /// <summary>
    /// 重写 CreateProperty,自定义创建 <see cref="JsonProperty"/>。
    /// </summary>
    /// <param name="member">成员信息</param>
    /// <param name="memberSerialization"></param>
    /// <returns></returns>
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);
        CustomPropertyAttribute attr = member.GetCustomAttribute(typeof(CustomPropertyAttribute)) as CustomPropertyAttribute;
        if (attr != null)
        {
            // 修改属性名为特性的 DeserializeName
            property.PropertyName = attr.DeserializeName ?? property.PropertyName;
            // 设置当前属性是否支持反序列化
            property.Writable = attr.Deserializable;
        }
        return property;
    }
}

然后为 Person 类设置自定义特性:

public class Person
{
    [CustomProperty(DeserializeName = "name", SerializeName = "姓名")]
    public string Name { get; set; }

    [CustomProperty(DeserializeName = "age", SerializeName = "年龄")]
    public int Age { get; set; }

    [CustomProperty(DeserializeName = "gender", SerializeName = "性别")]
    public Gender Gender { get; set; }

    [CustomProperty(DeserializeName = "height", Serializable = false)]
    public float Height { get; set; }

    [CustomProperty(DeserializeName = "weight", Serializable = false)]
    public float Weight { get; set; }
}
// person.json
{
  "name": "张三",
  "age": 20,
  "gender": 0,
  "height": 170,
  "weight": 60
}
//
JsonSerializer serializer = new JsonSerializer();
serializer.Formatting = Formatting.Indented;
Person person2 = null;
// 设置自定义反序列化解析器
serializer.ContractResolver = new ContractDeserializeResolver();
using (TextReader reader = new StreamReader("person.json"))
{
    person2 = (Person)serializer.Deserialize(reader, typeof(Person));
}
// 设置自定义序列化解析器
serializer.ContractResolver = new ContractSerializeResolver();
using (TextWriter writer = new StreamWriter("person.json"))
{
    serializer.Serialize(writer, person2);
}
// person.json
{
  "姓名": "张三",
  "年龄": 20,
  "性别": 0
}

6. 参考

[1] Json.NET Documentation

posted @ 2023-01-29 09:56  theyangfan  阅读(682)  评论(0编辑  收藏  举报