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

发布于:2025-04-04 ⋅ 阅读:(41) ⋅ 点赞:(0)

总目录


前言

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


一、 JsonConverter 是什么?

1. 概述

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

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

2. 为什么需要 JsonConverter?

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

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

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

3. JsonConverter 核心原理

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

  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<T>

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 知识汇总
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。


参考资料:


网站公告

今日签到

点亮在社区的每一天
去签到