.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转换器的资料请关注脚本之家其它相关文章!

相关文章

  • WinForm中实现picturebox自适应图片大小的方法

    WinForm中实现picturebox自适应图片大小的方法

    这篇文章主要介绍了WinForm中实现picturebox自适应图片大小的方法,涉及pictureBox控件相关属性设置技巧,需要的朋友可以参考下
    2017-05-05
  • C# datatable 不能通过已删除的行访问该行的信息处理方法

    C# datatable 不能通过已删除的行访问该行的信息处理方法

    采用datatable.Rows[i].Delete()删除行后再访问该表时出现出现“不能通过已删除的行访问该行的信息”的错误
    2012-11-11
  • unity scrollRect实现按页码翻页效果

    unity scrollRect实现按页码翻页效果

    这篇文章主要为大家详细介绍了unity scrollRect实现按页码翻页效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-04-04
  • DevExpress实现自定义TreeListNode的Tooltip的方法

    DevExpress实现自定义TreeListNode的Tooltip的方法

    这篇文章主要介绍了DevExpress实现自定义TreeListNode的Tooltip的方法,需要的朋友可以参考下
    2014-08-08
  • C# GetField方法的应用实例讲解

    C# GetField方法的应用实例讲解

    C#中的GetField是一个反射方法,用于获取指定类型的字段信息,它可以通过字段名称来获取字段对象,并且可以在运行时动态地访问和操作这些字段,本文给大家介绍了C# GetField方法的应用,需要的朋友可以参考下
    2024-04-04
  • 利用C#快速查出哪些QQ好友空间屏蔽了自己

    利用C#快速查出哪些QQ好友空间屏蔽了自己

    我们经常会遇到以下情况吧:想点击好友空间看看他最近的动态,结果发现自己需要申请权限!别担心,本文将为大家介绍如何利用C#快速查出哪些QQ好友空间屏蔽了自己,需要的可以参考一下
    2022-02-02
  • C# 枚举的使用简介

    C# 枚举的使用简介

    这篇文章主要介绍了C# 枚举的简单使用,帮助大家更好的理解和学习使用c#,感兴趣的朋友可以了解下
    2021-03-03
  • WinForm解决窗体大量控件加载闪烁卡顿的问题

    WinForm解决窗体大量控件加载闪烁卡顿的问题

    在使用 WinForm 桌面应用程序时,当窗体中包含大量控件(如按钮、标签、文本框等)或进行频繁界面更新时,常常会出现界面闪烁、加载卡顿、重绘不完整等问题,本文将介绍一种高效且广泛验证的解决方案,需要的朋友可以参考下
    2025-08-08
  • C#对NULL的简化赋值的方法总结

    C#对NULL的简化赋值的方法总结

    在C#中,对null赋值意味着将引用类型的变量设置为无引用或者说空引用,当你声明一个引用类型的变量,如果没有给它初始化具体的对象,那么它的值就是null,本文给大家介绍了C#对NULL的简化赋值的方法总结,需要的朋友可以参考下
    2024-11-11
  • C#实现汉字转区位码的示例代码

    C#实现汉字转区位码的示例代码

    区位码是一个4位的十进制数,每个区位码都对应着一个唯一的汉字,区位码的前两位叫做区码,后两位叫做位码,下面我们就来看看如何使用C#实现汉字转区位码吧
    2024-01-01

最新评论