C# System.Text.Json 中 JsonConverter 使用详解

总目录


前言

在 C# 开发中,System.Text.Json 是一个高性能的 JSON 处理库,广泛用于序列化和反序列化对象。当默认的序列化行为无法满足需求时,JsonConverter 提供了强大的自定义能力。本文将详细讲解 JsonConverter 的使用方法,帮助你灵活处理复杂的 JSON 数据。


一、 JsonConverter 是什么?

1. 概述

JsonConverterSystem.Text.Json.Serialization 命名空间中的一个抽象类,它提供了将对象或值转换为 JSON 格式以及将 JSON 格式转换回对象或值的功能。System.Text.Json 提供了内置的 JsonConverter 实现,用于处理大多数基本类型,同时开发者也可以通过实现 JsonConverter创建自定义的 JsonConverter 来满足特定需求。

JsonConverterSystem.Text.Json 中用于 自定义序列化和反序列化逻辑的核心类。它允许开发者完全控制 .NET 对象与 JSON 格式之间的转换过程,

2. 为什么需要 JsonConverter?

默认的序列化行为可能无法满足以下需求:

  • 特殊数据格式:日期、货币、自定义对象,如日期需格式化为 yyyy-MM-dd HH:mm:ss
  • 非标准 JSON 结构:如将嵌套对象转换为扁平化 JSON。
  • 安全处理:过滤敏感字段或转换加密数据。
  • 性能优化:避免反射或减少内存分配。

通过继承 JsonConverter,开发者可以完全控制类型与 JSON 之间的转换过程。

3. JsonConverter 核心原理

JsonConverter 是一个泛型类,要求实现以下两个关键方法:

  1. Read:从 Utf8JsonReader 中读取 JSON 数据并转换为 .NET 对象。
  2. Write:将 .NET 对象写入 Utf8JsonWriter 生成 JSON。

代码示例:

public class CustomConverter : JsonConverter<CustomType>
{
    public override CustomType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        // 实现反序列化逻辑
    }

    public override void Write(Utf8JsonWriter writer, CustomType value, JsonSerializerOptions options)
    {
        // 实现序列化逻辑
    }
}

二、使用

1. 内置 JsonConverter

1)内置 JsonConverter 介绍

▶ 内置转换器

System.Text.Json 为映射到 JavaScript 基元的大多数基本类型提供了内置转换器。这些内置转换器可以处理以下基本类型:字符串、整数、浮点数、布尔值、数组和集合、字典、日期和时间(ISO 8601 格式)。

对于日期和时间类型,System.Text.Json 实现了 ISO 8601-1:2019 扩展配置文件,定义了日期和时间表示形式的组件。
详见:System.Text.Json 中的 DateTime 和 DateTimeOffset 支持。

▶ 使用内置 JsonConverter

System.Text.Json 提供的内置转换器可以处理大多数常见类型,无需额外配置即可使用。以下是一个简单的使用示例:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public DateTime BirthDate { get; set; }
}
class Program
{
    static void Main()
    {
        // 使用内置转换器序列化对象
        var person = new Person
        {
            Name = "John Doe",
            Age = 30,
            BirthDate = new DateTime(1990, 1, 1)
        };
        string json = JsonSerializer.Serialize(person);
        Console.WriteLine(json);
        // 输出:{"Name":"John Doe","Age":30,"BirthDate":"1990-01-01T00:00:00"}
    }
}

在上述示例中,System.Text.Json 使用内置转换器自动处理了 Person 对象的所有属性,包括 DateTime 类型的 BirthDate 属性,以 ISO 8601 格式进行序列化。

2)内置转换器:JsonStringEnumConverter

JsonStringEnumConverterSystem.Text.Json 中用于 将枚举(Enum)类型序列化为字符串,并支持从字符串反序列化为枚举值的内置转换器。它是 JsonConverter 的一个特化实现,专门处理枚举类型,解决了默认序列化(将枚举值转为整数)的局限性。

▶ 通过JsonSerializerOptions全局使用

定义枚举类型和相关对象:

public enum Status { Active, Inactive, Pending }
public enum CountryType { China, USA,Japan }

public class Order
{
    public int Id { get; set; }
    public string Name { get; set; }
    public CountryType CountryType { get; set; }
    public Status Status { get; set; }
}

默认枚举会序列化为整数,使用 JsonStringEnumConverter 可转为字符串:

class Program
{
    static void Main()
    {
        // 正常序列化
        var order = new Order { Id = 1, Name = "Order0001", CountryType = CountryType.China, Status = Status.Pending };
        string json = JsonSerializer.Serialize(order);
        Console.WriteLine(json);
        // 输出:{"Id":1,"Name":"Order0001","CountryType":0,"Status":2}

        // 方式1
        var options = new JsonSerializerOptions
        {
            Converters = { new JsonStringEnumConverter() }
        };
        json = JsonSerializer.Serialize(order, options);
        Console.WriteLine(json);
        // 输出:{"Id":1,"Name":"Order0001","CountryType":"China","Status":"Pending"}

        // 方式2
        var options2 = new JsonSerializerOptions();
        options2.Converters.Add(new JsonStringEnumConverter());
        json = JsonSerializer.Serialize(order, options);
        Console.WriteLine(json);
        // 输出:{"Id":1,"Name":"Order0001","CountryType":"China","Status":"Pending"}
    }
}

▶ 针对特定枚举类型

如下例中通过JsonSerializerOptions中的Converters 配置只针对 Status 枚举的转换

var options = new JsonSerializerOptions
{
    Converters = { new JsonStringEnumConverter<Status>() }
};
json = JsonSerializer.Serialize(order, options);
Console.WriteLine(json);
// 输出:{"Id":1,"Name":"Order0001","CountryType":0,"Status":"Pending"}
▶ 通过JsonConverter 特性使用

直接在枚举类型上使用 [JsonConverter] 特性,适用于指定特定属性使用

// 第一种方式:直接在枚举上添加特性
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum Status{ Active, Inactive, Pending}
public enum CountryType{China, USA,Japan}

public class Order
{
    public int Id { get; set; }
    public string Name { get; set; }
    
    // 第二种方式:在对象相关属性上添加特性
    [JsonConverter(typeof(JsonStringEnumConverter))]
    public CountryType CountryType { get; set; }
    public Status Status { get; set; }
}
class Program
{
    static void Main()
    {
        // 正常序列化
        var order = new Order { Id = 1, Name = "Order0001", CountryType = CountryType.China, Status = Status.Pending };
        string json = JsonSerializer.Serialize(order);
        Console.WriteLine(json);
        // 输出:{"Id":1,"Name":"Order0001","CountryType":"China","Status":"Pending"}
    }
}
▶ 配置JsonStringEnumConverter 的命名策略

可通过 JsonNamingPolicy 自定义枚举值的输出格式(如 CamelCase、PascalCase 等)。

public enum Status{ Active, Inactive, Pending}
public enum CountryType{China, USA,Japan}

public class Order
{
    public int Id { get; set; }
    public string Name { get; set; }   
    public CountryType CountryType { get; set; }
    public Status Status { get; set; }
}
class Program
{
    static void Main()
    {
        // 正常序列化
        var order = new Order { Id = 1, Name = "Order0001", CountryType = CountryType.China, Status = Status.Pending };
        var options = new JsonSerializerOptions() 
        { 
            Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)  }
        };
        string json = JsonSerializer.Serialize(order,options);
        Console.WriteLine(json);
        // 输出:{"Id":1,"Name":"Order0001","CountryType":"china","Status":"pending"}
    }
}

2. 自定义 JsonConverter

当内置转换器无法满足需求时,开发者可以创建自定义的 JsonConverter。自定义转换器需要继承 JsonConverter 类并实现两个主要方法:Read 和 Write。

下面 将通过 将日期序列化为 yyyy-MM-dd 的案例来说明如何自定义 JsonConverter

1)创建自定义转换器

创建自定义转换器类,继承 JsonConverter

public class DateFormatterConverter : JsonConverter<DateTime>
{
    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
       // 实现 反序列化逻辑:将Json 转化为 .NET对象
    }

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        // 实现 序列化逻辑:将 .NET对象 转化为Json
    }
}

2)实现 ReadWrite 方法

public class DateFormatterConverter : JsonConverter<DateTime>
{
    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return DateTime.Parse(reader.GetString()!);
    }

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString("yyyy-MM-dd"));
    }
}

3)使用自定义JsonConverter

▶ 通过JsonSerializerOptions全局使用
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public DateTime BirthDate { get; set; }
}

class Program
{
    static void Main()
    {
        var person = new Person
        {
            Name = "John Doe",
            Age = 30,
            BirthDate = new DateTime(1990, 1, 1)
        };

        var options = new JsonSerializerOptions() 
        { 
            Converters = { new DateFormatterConverter()  }
        };
        string json = JsonSerializer.Serialize(person, options);
        Console.WriteLine(json);
        // 输出:{"Name":"John Doe","Age":30,"BirthDate":"1990-01-01"}
    }
}
▶ 通过JsonConverter 特性使用
public class Person
{
    public string Name { get; set; }
    
    public int Age { get; set; }
    
    [JsonConverter(typeof(DateFormatterConverter))]
    public DateTime BirthDate { get; set; }
}

class Program
{
    static void Main()
    {
        var person = new Person
        {
            Name = "John Doe",
            Age = 30,
            BirthDate = new DateTime(1990, 1, 1)
        };
        string json = JsonSerializer.Serialize(person);
        Console.WriteLine(json);
        // 输出:{"Name":"John Doe","Age":30,"BirthDate":"1990-01-01"}
    }
}

三、实战案例

1. 自定义日期格式

1)创建自定义转换器类

System.Text.Json 默认使用 ISO 8601 格式序列化日期。如果需要自定义日期格式,可以创建一个处理 DateTime 的自定义转换器:

public class CustomDateTimeConverter : JsonConverter<DateTime>
{
    private readonly string _format;
    public CustomDateTimeConverter(string format)
    {
        _format = format;
    }
    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TokenType == JsonTokenType.String)
        {
            return DateTime.ParseExact(reader.GetString()!, _format, System.Globalization.CultureInfo.InvariantCulture);
        }
        throw new JsonException();
    }

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString(_format));
    }
}

2)使用自定义转换器

class Program
{
    static void Main()
    {
        // 使用自定义日期格式
        var options = new JsonSerializerOptions
        {
            Converters = { new CustomDateTimeConverter("yyyy-MM-dd") }
        };

        var person = new
        {
            Name = "John Doe",
            Age = 30,
            BirthDate = new DateTime(1990, 1, 1)
        };
        string json = JsonSerializer.Serialize(person,options);
        Console.WriteLine(json);
        // 输出:{"Name":"John Doe","Age":30,"BirthDate":"1990-01-01"}

        var options2 = new JsonSerializerOptions();
        options2.Converters.Add(new CustomDateTimeConverter("yyyy-MM-dd HH:mm:ss"));
        json = JsonSerializer.Serialize(person,options2);
        Console.WriteLine(json);
        // 输出:{"Name":"John Doe","Age":30,"BirthDate":"1990-01-01 00:00:00"}
    }
}

在这个示例中,StringEnumConverter 用于将 DayOfWeek 枚举转换为字符串。Read 方法将 JSON 字符串解析为 DayOfWeek 枚举值,Write 方法将 DayOfWeek 枚举值写为 JSON 字符串。

2. 自定义枚举格式

如果需要自定义枚举的序列化行为,可以创建自己的转换器:

public class CustomEnumConverter : JsonConverter<DayOfWeek>
{
    public override DayOfWeek Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        string dayString = reader.GetString();
        if (Enum.TryParse(dayString, out DayOfWeek day))
        {
            return day;
        }
        throw new JsonException("Invalid day of week.");
    }

    public override void Write(Utf8JsonWriter writer, DayOfWeek value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString());
    }
}

3. 将对象转换为扁平化 JSON

需求:将嵌套对象扁平化为 JSON。

public class Address
{
    public string City { get; set; }
    public string ZipCode { get; set; }
}

public class User
{
    public string Name { get; set; }
    public Address Address { get; set; }
}

// 自定义转换器
public class UserConverter : JsonConverter<User>
{
    public override User Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
    {
        var user = new User();
        while (reader.Read())
        {
            if (reader.TokenType == JsonTokenType.EndObject)
                return user;

            if (reader.TokenType == JsonTokenType.PropertyName)
            {
                var propName = reader.GetString();
                reader.Read();
                switch (propName.ToLower())
                {
                    case "name":
                        user.Name = reader.GetString();
                        break;
                    case "city":
                        user.Address ??= new Address();
                        user.Address.City = reader.GetString();
                        break;
                    case "zip":
                        user.Address ??= new Address();
                        user.Address.ZipCode = reader.GetString();
                        break;
                }
            }
        }
        throw new JsonException("Unexpected JSON structure");
    }

    public override void Write(Utf8JsonWriter writer, User user, JsonSerializerOptions options)
    {
        writer.WriteStartObject();
        writer.WriteString("name", user.Name);
        if (user.Address != null)
        {
            writer.WriteString("city", user.Address.City);
            writer.WriteString("zip", user.Address.ZipCode);
        }
        writer.WriteEndObject();
    }
}

使用示例:

class Program
{
    static void Main()
    {
        var user = new User { Name = "Alice", Address = new Address { City = "Beijing", ZipCode = "100000" } };
        // 序列化
        string json = JsonSerializer.Serialize(user); 
        Console.WriteLine(json);
        // 输出:{"Name":"Alice","Address":{"City":"Beijing","ZipCode":"100000"}}

        // 序列化:扁平化JSON 
        var options = new JsonSerializerOptions { Converters = { new UserConverter() } };
        json = JsonSerializer.Serialize(user, options); 
        Console.WriteLine(json);
        // 输出:{"name":"Alice","city":"Beijing","zip":"100000"}

        // 反序列化:扁平化JSON 
        string jsonString = """{"name":"Alice","city":"Beijing","zip":"100000"}""";
        var user2= JsonSerializer.Deserialize<User>(jsonString,options);
        Console.WriteLine($"Name = {user2.Name} , City = {user2.Address.City} , ZipCode = {user2.Address.ZipCode}");
        // 输出:Name = Alice , City = Beijing , ZipCode = 100000
    }
}

4. 过滤敏感字段

需求:序列化时忽略敏感字段(如密码)。

public class User
{
    public string Name { get; set; }
    public string Password { get; set; } // 需要忽略的字段
}

public class SecureUserConverter : JsonConverter<User>
{
    public override User Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
    {
        // 反序列化逻辑(略)
        throw new NotImplementedException();
    }

    public override void Write(Utf8JsonWriter writer, User user, JsonSerializerOptions options)
    {
        writer.WriteStartObject();
        writer.WriteString("name", user.Name);
        // 跳过 Password 字段
        writer.WriteEndObject();
    }
}

关于以上案例中,自定义JsonConverter 里的Read 和 Write 方法所涉及的Utf8JsonReader 与Utf8JsonWriter 对象的详细内容,可见:C# Utf8JsonReader 和 Utf8JsonWriter 使用详解

四、性能优化技巧

1. 避免反射

直接操作 Utf8JsonReaderUtf8JsonWriter,减少对象创建:

public override void Write(Utf8JsonWriter writer, MyType value, JsonSerializerOptions options)
{
    writer.WriteStartObject();
    writer.WriteString("key", value.Property); // 直接写入属性
    writer.WriteEndObject();
}

2. 缓存转换器实例

private static readonly DateFormatterConverter _dateConverter = new DateFormatterConverter();
var options = new JsonSerializerOptions { Converters = { _dateConverter } };

五、异常处理与调试

1. 捕获反序列化错误

public override User Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
{
    try
    {
        // 反序列化逻辑
    }
    catch (Exception ex)
    {
        throw new JsonException("Failed to parse user data", ex);
    }
}

2. 日志记录与调试

ReadWrite 方法中添加日志输出:

Console.WriteLine($"Serializing {value.Name} to JSON");

结语

回到目录页:C#/.NET 知识汇总
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。


参考资料:

  • 如何在 .NET 中编写用于 JSON 序列化(封送)的自定义转换器
  • .NET 官方文档:System.Text.Json
  • JsonConverter 源码分析

你可能感兴趣的:(C#,c#,json,.net)