.NET中自定義JSON轉(zhuǎn)換器的實(shí)戰(zhàn)指南
一、為什么需要自定義 JSON 轉(zhuǎn)換器?
在 .NET 開發(fā)中,JSON 序列化與反序列化是日常開發(fā)的核心操作。然而,標(biāo)準(zhǔn)的序列化器(如 System.Text.Json)并不能滿足所有場(chǎng)景需求。以下是常見的痛點(diǎn):
- 數(shù)據(jù)格式不兼容
- 例如:前端要求日期格式為
"yyyy-MM-dd",而默認(rèn)格式為 ISO 8601。
- 例如:前端要求日期格式為
- 復(fù)雜類型處理
- 例如:枚舉類型需要自定義映射規(guī)則(如
Enum轉(zhuǎn)string而非int)。
- 例如:枚舉類型需要自定義映射規(guī)則(如
- 性能瓶頸
- 例如:需要避免頻繁創(chuàng)建臨時(shí)對(duì)象或優(yōu)化內(nèi)存分配。
- 特殊業(yè)務(wù)邏輯
- 例如:將數(shù)據(jù)庫(kù)中的科學(xué)計(jì)數(shù)法字段(
1.23E+08)轉(zhuǎn)為正常數(shù)字格式。
- 例如:將數(shù)據(jù)庫(kù)中的科學(xué)計(jì)數(shù)法字段(
自定義 JSON 轉(zhuǎn)換器(JsonConverter<T>)正是解決這些問(wèn)題的利器。本文將通過(guò) 基本模式 和 工廠模式 的實(shí)戰(zhàn)案例,帶你掌握自定義轉(zhuǎn)換器的核心技巧。
二、基本模式:實(shí)現(xiàn) JsonConverter<T>
2.1 適用場(chǎng)景
- 非泛型類型 或 封閉式泛型類型(如
List<DateTime>、Dictionary<string, int>)。 - 需要控制特定類型的序列化/反序列化邏輯。
2.2 核心方法
Read:將 JSON 反序列化為 .NET 對(duì)象。Write:將 .NET 對(duì)象序列化為 JSON。CanConvert(可選):判斷是否支持該類型。
2.3 實(shí)戰(zhàn)案例:日期格式自定義轉(zhuǎn)換器
場(chǎng)景需求
- 目標(biāo):將
DateTime字段從默認(rèn)的 ISO 8601 格式("2025-07-19T17:11:39Z")轉(zhuǎn)為"yyyy-MM-dd"格式。 - 代碼實(shí)現(xiàn):
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
// 自定義 DateTime 轉(zhuǎn)換器
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))] // 綁定轉(zhuǎn)換器
public DateTime Birthday { get; set; }
}
// 測(cè)試代碼
var person = new Person
{
Name = "Alice",
Birthday = new DateTime(2025, 7, 19)
};
var options = new JsonSerializerOptions
{
WriteIndented = true,
Converters = { new CustomDateTimeConverter() } // 注冊(cè)全局轉(zhuǎn)換器
};
string json = JsonSerializer.Serialize(person, options);
Console.WriteLine(json);
// 輸出: {"Name":"Alice","Birthday":"2025-07-19"}
技術(shù)細(xì)節(jié)
Utf8JsonReader:高性能的 JSON 解析器,直接操作 UTF-8 緩沖區(qū),避免字符串轉(zhuǎn)換開銷。Utf8JsonWriter:直接生成 UTF-8 字節(jié)流,減少中間字符串分配。- 異常處理:
JsonException是序列化框架的標(biāo)準(zhǔn)異常類型。
三、工廠模式:處理開放式泛型類型
3.1 適用場(chǎng)景
- 開放式泛型類型(如
List<T>、Dictionary<TKey, TValue>)。 - 多態(tài)類型(如
Enum或動(dòng)態(tài)類型)。
3.2 核心方法
CanConvert:判斷是否支持當(dāng)前類型。CreateConverter:動(dòng)態(tài)創(chuàng)建特定類型的轉(zhuǎn)換器實(shí)例。
3.3 實(shí)戰(zhàn)案例:泛型集合轉(zhuǎn)換器
場(chǎng)景需求
- 目標(biāo):支持任意
List<T>類型的序列化,其中T可能是int、string或自定義類型。
代碼實(shí)現(xiàn)
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
// 工廠類:處理 List<T>
public class ListConverterFactory : JsonConverterFactory
{
// 判斷是否支持當(dāng)前類型
public override bool CanConvert(Type typeToConvert)
{
// 僅支持 List<T> 類型
return typeToConvert.IsGenericType &&
typeToConvert.GetGenericTypeDefinition() == typeof(List<>);
}
// 動(dòng)態(tài)創(chuàng)建轉(zhuǎn)換器
public override JsonConverter CreateConverter(
Type typeToConvert,
JsonSerializerOptions options)
{
// 獲取 T 的類型
var genericType = typeToConvert.GetGenericArguments()[0];
// 創(chuàng)建泛型轉(zhuǎn)換器實(shí)例
return (JsonConverter)Activator.CreateInstance(
typeof(ListConverter<>).MakeGenericType(genericType));
}
}
// 泛型轉(zhuǎn)換器:處理 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; }
}
// 注冊(cè)工廠轉(zhuǎn)換器
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]}
技術(shù)細(xì)節(jié)
- 泛型反射:通過(guò)
MakeGenericType動(dòng)態(tài)創(chuàng)建泛型轉(zhuǎn)換器。 - 遞歸序列化:
JsonSerializer.Deserialize<T>()自動(dòng)處理嵌套類型。
四、性能優(yōu)化技巧
4.1 避免頻繁創(chuàng)建對(duì)象
- 復(fù)用
JsonSerializerOptions:全局配置并復(fù)用,避免重復(fù)解析設(shè)置。 - 緩存
JsonConverter實(shí)例:在工廠模式中緩存已創(chuàng)建的轉(zhuǎn)換器。
4.2 使用高性能 API
Utf8JsonReader/Utf8JsonWriter:比JsonTextReader快 30%+,且內(nèi)存占用更低。- Span 支持:避免不必要的字符串分配。
4.3 內(nèi)存優(yōu)化
- 異步處理:對(duì)于大型數(shù)據(jù),使用
JsonSerializer.DeserializeAsync。 - 流式處理:結(jié)合
Stream逐塊讀寫,避免一次性加載大文件。
五、高級(jí)場(chǎng)景:科學(xué)計(jì)數(shù)法轉(zhuǎn)字符串
5.1 問(wèn)題描述
數(shù)據(jù)庫(kù)中的 decimal(18,2) 字段在 JSON 中可能被序列化為科學(xué)計(jì)數(shù)法:
{ "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 轉(zhuǎn)為固定格式的字符串
writer.WriteStringValue(value.ToString("F2"));
}
}
綁定模型
public class Product
{
[JsonConverter(typeof(DecimalConverter))]
public decimal Price { get; set; }
}
六、常見陷阱與解決方案
6.1 CanConvert 方法誤判
- 問(wèn)題:未正確覆蓋
CanConvert,導(dǎo)致轉(zhuǎn)換器無(wú)法匹配目標(biāo)類型。 - 解決方案:在工廠模式中,嚴(yán)格判斷類型是否符合預(yù)期。
6.2 嵌套類型處理失敗
- 問(wèn)題:轉(zhuǎn)換器未處理嵌套對(duì)象(如
Dictionary<string, List<int>>)。 - 解決方案:遞歸調(diào)用
JsonSerializer,或手動(dòng)拆解結(jié)構(gòu)。
6.3 線程安全問(wèn)題
- 問(wèn)題:共享的
JsonSerializerOptions被多個(gè)線程修改。 - 解決方案:使用
ThreadLocal<JsonSerializerOptions>或每次創(chuàng)建新實(shí)例。
七、自定義轉(zhuǎn)換器的設(shè)計(jì)哲學(xué)
| 模式 | 適用類型 | 核心方法 | 典型場(chǎng)景 |
|---|---|---|---|
| 基本模式 | 非泛型/封閉式泛型 | Read / Write | 日期格式化、枚舉映射 |
| 工廠模式 | 開放式泛型/多態(tài) | CanConvert / CreateConverter | 泛型集合、動(dòng)態(tài)類型 |
設(shè)計(jì)原則:
- 最小化依賴:避免在轉(zhuǎn)換器中引入復(fù)雜邏輯。
- 高內(nèi)聚低耦合:每個(gè)轉(zhuǎn)換器只處理一種類型或模式。
- 性能優(yōu)先:優(yōu)先使用
Utf8JsonReader和Utf8JsonWriter。 - 可測(cè)試性:通過(guò)單元測(cè)試驗(yàn)證轉(zhuǎn)換器的正確性。
代碼模板
基本模式模板
public class CustomConverter<T> : JsonConverter<T>
{
public override T Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
// 實(shí)現(xiàn)反序列化邏輯
}
public override void Write(
Utf8JsonWriter writer,
T value,
JsonSerializerOptions options)
{
// 實(shí)現(xiàn)序列化邏輯
}
}
工廠模式模板
public class CustomConverterFactory : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert)
{
// 判斷是否支持類型
}
public override JsonConverter CreateConverter(
Type typeToConvert,
JsonSerializerOptions options)
{
// 動(dòng)態(tài)創(chuàng)建轉(zhuǎn)換器
}
}
以上就是.NET中自定義JSON轉(zhuǎn)換器的實(shí)戰(zhàn)指南的詳細(xì)內(nèi)容,更多關(guān)于.NET自定義JSON轉(zhuǎn)換器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#實(shí)現(xiàn)自定義光標(biāo)并動(dòng)態(tài)切換
這篇文章主要為大家詳細(xì)介紹了如何利用C#語(yǔ)言實(shí)現(xiàn)自定義光標(biāo)、并動(dòng)態(tài)切換光標(biāo)類型,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2022-07-07
Unity InputFiled TMP屬性和各種監(jiān)聽示例詳解
這篇文章主要為大家介紹了Unity InputFiled TMP屬性和各種監(jiān)聽示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
WPF實(shí)現(xiàn)繪制扇形統(tǒng)計(jì)圖的示例代碼
這篇文章主要介紹了如何利用WPF繪制扇形統(tǒng)計(jì)圖,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)或工作有一定幫助,感興趣的小伙伴可以了解一下2022-09-09
C#枚舉類型與結(jié)構(gòu)類型實(shí)例解析
這篇文章主要介紹了C#枚舉類型與結(jié)構(gòu)類型實(shí)例,需要的朋友可以參考下2014-07-07
C#?MemoryStream中ToArray和GetBuffer的區(qū)別小小結(jié)
MemoryStream?中的?GetBuffer()?和?ToArray()?是兩個(gè)用于獲取流數(shù)據(jù)的方法,核心區(qū)別在于數(shù)據(jù)范圍、內(nèi)存占用和安全性,本文就來(lái)介紹一下兩者的區(qū)別,感興趣的額可以了解一下2025-07-07

