默認情況下,枚舉是以其整數形式進行 JSON 序列化,這通常會導致與消費者應用缺乏互操作性,因為他們需要事先了解這些數字的實際含義。因此,我們希望它們在一些情況下以字符串的形式進行序列化。本文將講解實現這一目標的各種方法。
1枚舉序列化的默認行為
為了演示,我們來創建一個簡單的 Model:
public?class?Circle
{public?double Radius { get; set; }public Color BgColor { get; set; }
}public?enum Color
{White, LightGray, Black
}
我們用 System.Text.Json
中的 Serialize
方法序列化一個 Circle
對象:
using System.Text.Json;var json = JsonSerializer.Serialize(new Circle
{Radius = 10.1,BgColor = Color.Black
});Console.WriteLine(json);
我們來看一下這個 Model 序列化為 Json 后的字符串:
{?"Radius":?10.1,?"BgColor":?2?}
正如你看到的,枚舉屬性的值(Color.Black
)被序列化為 JSON 字符串后表現為一個整數(2
)。
作為序列化結果 JSON 的消費者,在沒有文檔的情況下,無法知道這個整數代表的業務含義。
所以,問題來了。如何將枚舉屬性序列為更直觀的字符串形式呢?即,如何將上面示例中的對象序列化為:
{?"Radius":?10.1,?"BgColor":?"Black"?}
要把枚舉序列化為字符串,Dotnet 標準庫 System.Text.Json
和 Newtonsoft 庫都為此提供了一個轉換器,分別為 JsonStringEnumConverter
和 StringEnumConverter
。下面來看看它們的用法。
2基于標注的方式
Dotnet 標準庫和 Newtonsoft 庫都提供了一個轉換器特性 JsonConverterAttribute
,我們可以用它來選擇性地標注要序列化為字符串形式的枚舉屬性或類型。
標注枚舉屬性
我們可以在枚舉屬性上使用 JsonConverterAttribute
特性來標注 BgColor
屬性,并使用 JsonStringEnumConverter
轉換器,如下:
using System.Text.Json.Serialization;public?class?Circle
{public?double Radius { get; set; }[JsonConverter(typeof(JsonStringEnumConverter))]public Color BgColor { get; set; }
}
此時序列化的結果為:
{?"Radius":?10.1,?"BgColor":?"Black"?}
Newtonsoft 庫也是類似的,把 JsonStringEnumConverter
替換為 StringEnumConverter
即可,本文就不重復舉列了。
標注枚舉類型
JsonConverterAttribute
特性也可以應用在自定義類型上面,把它應用在 Color
枚舉類型上:
using System.Text.Json.Serialization;[JsonConverter(typeof(JsonStringEnumConverter))]
public?enum Color
{White, LightGray, Black
}
這樣,所有的 Color
枚舉都會序列化為字符串形式。
3基于選項的方式
在一些不方便修改 Model 或枚舉類型定義的情況下(如調用第三方 SDK),就不能使用標注的方式將枚舉序列化為字符串了。Dotnet 標準庫和 Newtonsoft 庫還提供了帶有轉換選項參數的序列化方法,允許我們使用行內(Inline)方式實現這一目標。
Dotnet 標準庫示例如下:
public?static?string?SerializeWithStringEnum(object obj)
{var options = new JsonSerializerOptions();options.Converters.Add(new JsonStringEnumConverter());return JsonSerializer.Serialize(obj, options);
}
而 Newtonsoft 庫稍有不同:
public?static?string?SerializeWithStringEnum(object obj)
{var converter = new StringEnumConverter();return JsonConvert.SerializeObject(obj, converter);
}
4全局注冊的方式
基于特性標注的方式讓我們可以靈活地以可控的方式來操作枚舉的序列化。而基于選項的方式允許我們在特定環境下按需處理對象的序列化。但是,有沒有辦法讓所有的枚舉在默認情況下被序列化為字符串呢?答案是有的。
對控制臺應用程序或 Dotnet 類庫就簡單了,直接封裝一個通用的序列化方法即可。這里我們主要關心如何在 ASP.NET Core 應用程序中注冊全局的序列化規則。
在 ASP.NET Core Web API 應用中,需要在 DI 管道中注冊枚舉轉換器,如下:
var builder = WebApplication.CreateBuilder(args);builder.Services.AddControllers().AddJsonOptions(options =>{options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());});var app = builder.Build();app.MapControllers();app.Run();
在 ASP.NET Core Minimal API 中,也是需要在 DI 管道中注冊枚舉轉換器,但使用的是 Configure
方式:
var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<JsonOptions>(options =>
{options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
});var app = builder.Build();app.MapGet("/", () => new Circle
{Radius = 10.1,BgColor = Color.Black
});app.Run();
如上所述,我們可以通過全局注冊的方式來設置應用程序在默認情況下將枚舉序列化為字符串,這樣可以保持 Model 的干凈,也不需要在序列化時指定明確的選項。
5標志枚舉的序列化
標志枚舉(bit-mask 枚舉)的默認序列化輸出也是一個整數,而不是使用標志(Flag)的組合來表示。例如:
using System.Text.Json;var colors = Color.LightGray | Color.Black;
var json = JsonSerializer.Serialize(new { Colors = colors });Console.WriteLine(json);[Flags]
public?enum Color
{White = 0, LightGray = 1, Black = 2
}
序列化結果為:
{?"Colors":?3?}
我們希望輸出的是枚舉的組合,結果卻是一個數字,這對消費者來說很難理解它真正的意義,我們希望它序列化為文件的組合。使用前面的枚舉字符串轉換器可以解決這個問題,以基于選項的方式為例,示例代碼如下:
using System.Text.Json;
using System.Text.Json.Serialization;var options = new JsonSerializerOptions();
options.Converters.Add(new JsonStringEnumConverter());var colors = Color.LightGray | Color.Black;
var json = JsonSerializer.Serialize(new { Colors = colors }, options);Console.WriteLine(json);
{?"Colors":?"LightGray, Black"?}
這樣就得到我們想要的文本組合序列化結果了。
6自定義枚舉字符串
在將枚舉序列化為字符串時,我們可能希望進行一些微調。例如,轉換為 camelCase 或其它更有意義的文本形式。
序列化為 camelCase 形式
JsonStringEnumConverter
有一個接收命名策略的重載構造函數,給其傳入 JsonNamingPolicy.CamelCase
參數即可將枚舉序列為 camelCase 文本形式,例如:
var options = new JsonSerializerOptions();
options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
var json = JsonSerializer.Serialize(new Circle
{Radius = 10.1,BgColor = Color.LightGray
});Console.WriteLine(json);
序列化結果為:
{?"Radius":?10.1,?"BgColor":?"lightGray"?}
序列化為自定義文本
由于語言中的變量命名規則,枚舉成員在以常規方式序列化時并不總是能傳達有意義的文本。為了解決這個問題,我們可以使用 EnumMemberAttribute
特性,它是專門為此目的引入的。我們來定義一個新的枚舉:
public?enum ColorScheme
{[EnumMember(Value = "白/藍")]WhiteBlue,[EnumMember(Value = "紅/黑")]RedBlack,
}
我們定義了一個 ColorScheme
枚舉,在其成員上標注 EnumMember
特性,以提供更有意義的文本表達。
遺憾的,Donet 標準庫 System.Text.Json
原生暫不支持這種自定義文本的序列化。但 Newtonsoft 庫是支持的,示例代碼如下:
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System.Runtime.Serialization;var converter = new StringEnumConverter();
var json = JsonConvert.SerializeObject(new TShirt
{Name = "男士襯衫-休閑款",ColorScheme = ColorScheme.WhiteBlue
}, converter);Console.WriteLine(json);public?class?TShirt
{public?string? Name { get; set; }public ColorScheme ColorScheme { get; set; }
}
序列化后輸出結果:
{?"Name":?"男士襯衫-休閑款",?"ColorScheme":?"白/藍"?}
序列化后,我們可以看到它輸出了我們想要的文本。
注:雖然 Donet 標準庫原生暫不支持將枚舉序列化為自定義的文本,但依然可以通過自定義轉換器來實現。
7總結
枚舉默認是以其整數形式進行 JSON 序列化的,這要求消費者事先了解這些數字的實際含義,給代碼的理解也帶來了一定的困難。所以 Dotnet 基礎庫和流行的 Newtonsoft 庫都提供了將枚舉序列化為字符串的多種方法。根據需求,可以選擇使用本文講的基于特性標注的方式、基于選項參數的方式和全局方式。
另外,除了可以將枚舉序列化為成員名字符串,還可以自定義文本的輸出,如輸出為 camelCase 文本形式和自定義輸出的文本內容等。