.NET中自定义JSON转换器的实战指南

 更新时间:2025年08月05日 08:46:39   作者:墨夶  
在.NET开发中,JSON序列化与反序列化是日常开发的核心操作,然而,标准的序列化器(如System.Text.Json)并不能满足所有场景需求,自定义JSON转换器正是解决这些问题的利器,本文将通过基本模式和工厂模式的实战案例,带你掌握自定义转换器的核心技巧,需要的朋友可以参考下

一、为什么需要自定义 JSON 转换器?

在 .NET 开发中,JSON 序列化与反序列化是日常开发的核心操作。然而,标准的序列化器(如 System.Text.Json)并不能满足所有场景需求。以下是常见的痛点:

  1. 数据格式不兼容
    • 例如:前端要求日期格式为 "yyyy-MM-dd",而默认格式为 ISO 8601。
  2. 复杂类型处理
    • 例如:枚举类型需要自定义映射规则(如 Enumstring 而非 int)。
  3. 性能瓶颈
    • 例如:需要避免频繁创建临时对象或优化内存分配。
  4. 特殊业务逻辑
    • 例如:将数据库中的科学计数法字段(1.23E+08)转为正常数字格式。

自定义 JSON 转换器JsonConverter<T>)正是解决这些问题的利器。本文将通过 基本模式工厂模式 的实战案例,带你掌握自定义转换器的核心技巧。

二、基本模式:实现 JsonConverter<T>

2.1 适用场景

  • 非泛型类型封闭式泛型类型(如 List<DateTime>Dictionary<string, int>)。
  • 需要控制特定类型的序列化/反序列化逻辑。

2.2 核心方法

  • Read:将 JSON 反序列化为 .NET 对象。
  • Write:将 .NET 对象序列化为 JSON。
  • CanConvert(可选):判断是否支持该类型。

2.3 实战案例:日期格式自定义转换器

场景需求

  • 目标:将 DateTime 字段从默认的 ISO 8601 格式("2025-07-19T17:11:39Z")转为 "yyyy-MM-dd" 格式。
  • 代码实现
using System;
using System.Text.Json;
using System.Text.Json.Serialization;

// 自定义 DateTime 转换器
public class CustomDateTimeConverter : JsonConverter<DateTime>
{
    private const string DateFormat = "yyyy-MM-dd";

    // 反序列化:JSON -> DateTime
    public override DateTime Read(
        ref Utf8JsonReader reader, 
        Type typeToConvert, 
        JsonSerializerOptions options)
    {
        // 处理 null 值
        if (reader.TokenType == JsonTokenType.Null)
            return default;

        // 读取 JSON 字符串
        var dateString = reader.GetString();
        if (DateTime.TryParseExact(dateString, DateFormat, null, System.Globalization.DateTimeStyles.None, out var result))
        {
            return result;
        }

        throw new JsonException($"Invalid date format. Expected '{DateFormat}' but got '{dateString}'");
    }

    // 序列化:DateTime -> JSON
    public override void Write(
        Utf8JsonWriter writer, 
        DateTime value, 
        JsonSerializerOptions options)
    {
        // 格式化为指定格式
        writer.WriteStringValue(value.ToString(DateFormat));
    }
}

使用示例

public class Person
{
    public string Name { get; set; }
    [JsonConverter(typeof(CustomDateTimeConverter))] // 绑定转换器
    public DateTime Birthday { get; set; }
}

// 测试代码
var person = new Person 
{ 
    Name = "Alice", 
    Birthday = new DateTime(2025, 7, 19) 
};

var options = new JsonSerializerOptions
{
    WriteIndented = true,
    Converters = { new CustomDateTimeConverter() } // 注册全局转换器
};

string json = JsonSerializer.Serialize(person, options);
Console.WriteLine(json); 
// 输出: {"Name":"Alice","Birthday":"2025-07-19"}

技术细节

  • Utf8JsonReader:高性能的 JSON 解析器,直接操作 UTF-8 缓冲区,避免字符串转换开销。
  • Utf8JsonWriter:直接生成 UTF-8 字节流,减少中间字符串分配。
  • 异常处理JsonException 是序列化框架的标准异常类型。

三、工厂模式:处理开放式泛型类型

3.1 适用场景

  • 开放式泛型类型(如 List<T>Dictionary<TKey, TValue>)。
  • 多态类型(如 Enum 或动态类型)。

3.2 核心方法

  • CanConvert:判断是否支持当前类型。
  • CreateConverter:动态创建特定类型的转换器实例。

3.3 实战案例:泛型集合转换器

场景需求

  • 目标:支持任意 List<T> 类型的序列化,其中 T 可能是 intstring 或自定义类型。

代码实现

using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;

// 工厂类:处理 List<T>
public class ListConverterFactory : JsonConverterFactory
{
    // 判断是否支持当前类型
    public override bool CanConvert(Type typeToConvert)
    {
        // 仅支持 List<T> 类型
        return typeToConvert.IsGenericType && 
               typeToConvert.GetGenericTypeDefinition() == typeof(List<>);
    }

    // 动态创建转换器
    public override JsonConverter CreateConverter(
        Type typeToConvert, 
        JsonSerializerOptions options)
    {
        // 获取 T 的类型
        var genericType = typeToConvert.GetGenericArguments()[0];
        // 创建泛型转换器实例
        return (JsonConverter)Activator.CreateInstance(
            typeof(ListConverter<>).MakeGenericType(genericType));
    }
}

// 泛型转换器:处理 List<T>
public class ListConverter<T> : JsonConverter<List<T>>
{
    // 反序列化:JSON -> List<T>
    public override List<T> Read(
        ref Utf8JsonReader reader, 
        Type typeToConvert, 
        JsonSerializerOptions options)
    {
        if (reader.TokenType != JsonTokenType.StartArray)
            throw new JsonException("Expected start of array");

        var list = new List<T>();
        while (reader.Read())
        {
            if (reader.TokenType == JsonTokenType.EndArray)
                break;

            var item = JsonSerializer.Deserialize<T>(ref reader, options);
            list.Add(item);
        }

        return list;
    }

    // 序列化:List<T> -> JSON
    public override void Write(
        Utf8JsonWriter writer, 
        List<T> value, 
        JsonSerializerOptions options)
    {
        writer.WriteStartArray();
        foreach (var item in value)
        {
            JsonSerializer.Serialize(writer, item, options);
        }
        writer.WriteEndArray();
    }
}

使用示例

public class Product
{
    public string Name { get; set; }
    public List<int> Ratings { get; set; }
}

// 注册工厂转换器
var options = new JsonSerializerOptions
{
    WriteIndented = true,
    Converters = { new ListConverterFactory() }
};

var product = new Product
{
    Name = "Laptop",
    Ratings = new List<int> { 5, 4, 5 }
};

string json = JsonSerializer.Serialize(product, options);
Console.WriteLine(json);
// 输出: {"Name":"Laptop","Ratings":[5,4,5]}

技术细节

  • 泛型反射:通过 MakeGenericType 动态创建泛型转换器。
  • 递归序列化JsonSerializer.Deserialize<T>() 自动处理嵌套类型。

四、性能优化技巧

4.1 避免频繁创建对象

  • 复用 JsonSerializerOptions:全局配置并复用,避免重复解析设置。
  • 缓存 JsonConverter 实例:在工厂模式中缓存已创建的转换器。

4.2 使用高性能 API

  • Utf8JsonReader / Utf8JsonWriter:比 JsonTextReader 快 30%+,且内存占用更低。
  • Span 支持:避免不必要的字符串分配。

4.3 内存优化

  • 异步处理:对于大型数据,使用 JsonSerializer.DeserializeAsync
  • 流式处理:结合 Stream 逐块读写,避免一次性加载大文件。

五、高级场景:科学计数法转字符串

5.1 问题描述

数据库中的 decimal(18,2) 字段在 JSON 中可能被序列化为科学计数法:

{ "Price": "1.23E+08" }

而前端期望的是:

{ "Price": 123000000 }

5.2 解决方案

public class DecimalConverter : JsonConverter<decimal>
{
    public override decimal Read(
        ref Utf8JsonReader reader, 
        Type typeToConvert, 
        JsonSerializerOptions options)
    {
        // 解析 JSON 字符串为 decimal
        return decimal.Parse(reader.GetString()!);
    }

    public override void Write(
        Utf8JsonWriter writer, 
        decimal value, 
        JsonSerializerOptions options)
    {
        // 将 decimal 转为固定格式的字符串
        writer.WriteStringValue(value.ToString("F2"));
    }
}

绑定模型

public class Product
{
    [JsonConverter(typeof(DecimalConverter))]
    public decimal Price { get; set; }
}

六、常见陷阱与解决方案

6.1 CanConvert 方法误判

  • 问题:未正确覆盖 CanConvert,导致转换器无法匹配目标类型。
  • 解决方案:在工厂模式中,严格判断类型是否符合预期。

6.2 嵌套类型处理失败

  • 问题:转换器未处理嵌套对象(如 Dictionary<string, List<int>>)。
  • 解决方案:递归调用 JsonSerializer,或手动拆解结构。

6.3 线程安全问题

  • 问题:共享的 JsonSerializerOptions 被多个线程修改。
  • 解决方案:使用 ThreadLocal<JsonSerializerOptions> 或每次创建新实例。

七、自定义转换器的设计哲学

模式适用类型核心方法典型场景
基本模式非泛型/封闭式泛型Read / Write日期格式化、枚举映射
工厂模式开放式泛型/多态CanConvert / CreateConverter泛型集合、动态类型

设计原则

  1. 最小化依赖:避免在转换器中引入复杂逻辑。
  2. 高内聚低耦合:每个转换器只处理一种类型或模式。
  3. 性能优先:优先使用 Utf8JsonReaderUtf8JsonWriter
  4. 可测试性:通过单元测试验证转换器的正确性。

代码模板

基本模式模板

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

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

工厂模式模板

public class CustomConverterFactory : JsonConverterFactory
{
    public override bool CanConvert(Type typeToConvert)
    {
        // 判断是否支持类型
    }

    public override JsonConverter CreateConverter(
        Type typeToConvert, 
        JsonSerializerOptions options)
    {
        // 动态创建转换器
    }
}

以上就是.NET中自定义JSON转换器的实战指南的详细内容,更多关于.NET自定义JSON转换器的资料请关注脚本之家其它相关文章!

相关文章

  • C#加密算法汇总(推荐)

    C#加密算法汇总(推荐)

    以下是对C#中的加密算法进行了汇总介绍,需要的朋友可以过来参考下
    2013-09-09
  • C#中按字符串截取长字符串实例

    C#中按字符串截取长字符串实例

    这篇文章主要介绍了C#中按字符串截取长字符串的实现方法,以实例形式展示了C#中正则匹配截取字符串的技巧,需要的朋友可以参考下
    2014-11-11
  • C#实现自定义光标并动态切换

    C#实现自定义光标并动态切换

    这篇文章主要为大家详细介绍了如何利用C#语言实现自定义光标、并动态切换光标类型,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下
    2022-07-07
  • Unity InputFiled TMP属性和各种监听示例详解

    Unity InputFiled TMP属性和各种监听示例详解

    这篇文章主要为大家介绍了Unity InputFiled TMP属性和各种监听示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-01-01
  • 详解C#获取特定进程CPU和内存使用率

    详解C#获取特定进程CPU和内存使用率

    本篇文章主要介绍详解C#获取特定进程CPU和内存使用率,非常具有实用价值,需要的朋友可以参考下。
    2016-11-11
  • WPF实现绘制扇形统计图的示例代码

    WPF实现绘制扇形统计图的示例代码

    这篇文章主要介绍了如何利用WPF绘制扇形统计图,文中的示例代码讲解详细,对我们学习或工作有一定帮助,感兴趣的小伙伴可以了解一下
    2022-09-09
  • C#中enum和string的相互转换

    C#中enum和string的相互转换

    这篇文章主要介绍了C#中enum和string的相互转换的相关资料,需要的朋友可以参考下
    2017-09-09
  • C#处理TCP数据的方法详解

    C#处理TCP数据的方法详解

    Tcp是一个面向连接的流数据传输协议,用人话说就是传输是一个已经建立好连接的管道,数据都在管道里像流水一样流淌到对端,那么数据必然存在几个问题,比如数据如何持续的读取,数据包的边界等,本文给大家介绍了C#处理TCP数据的方法,需要的朋友可以参考下
    2024-06-06
  • C#枚举类型与结构类型实例解析

    C#枚举类型与结构类型实例解析

    这篇文章主要介绍了C#枚举类型与结构类型实例,需要的朋友可以参考下
    2014-07-07
  • C# MemoryStream中ToArray和GetBuffer的区别小小结

    C# MemoryStream中ToArray和GetBuffer的区别小小结

    MemoryStream 中的 GetBuffer() 和 ToArray() 是两个用于获取流数据的方法,核心区别在于数据范围、内存占用和安全性,本文就来介绍一下两者的区别,感兴趣的额可以了解一下
    2025-07-07

最新评论