今天公司同事在用 .NET 處理一個系統串接需求時,發現對方傳來的 JSON 格式會把應該為「數值」的數字資料使用「字串」的格式來表達,這導致他在使用 System.Text.Json 的 JsonSerializer.Deserialize
進行反序列化時出現錯誤。這篇文章我來分享一個鮮為人知的小秘訣,讓你輕鬆駕馭各種 Web 常見的 JSON 格式。
問題說明
首先,我先假設我們的 JSON 資料長這樣,屬性命名採用 camelCase
規則,但 age
屬性使用 string
型別:
{
"name": "Will",
"birthday": "2000-06-11",
"age": "18",
"createdOn": "2000-06-11T19:53:03"
}
然後我們定義一個資料模型類別來表示這個結構,其中 Age
使用 int
型別:
public class UserProfile
{
[JsonPropertyName("Name")]
public string Name { get; set; }
[JsonPropertyName("birthday")]
public DateTime Birthday { get; set; }
[JsonPropertyName("age")]
public int Age { get; set; }
[JsonPropertyName("createdOn")]
public DateTime CreatedOn { get; set; }
}
最後,我們用 JsonSerializer.Deserialize 方法來進行反序列化操作:
var user = JsonSerializer.Deserialize<UserProfile>(jsonText);
此時你會立刻收到這個例外狀況:
The JSON value could not be converted to System.Int32. Path: $.age | LineNumber: 3 | BytePositionInLine: 15.
遇到這種問題,你從錯誤訊息可以很清楚的判斷出,他沒辦法將 "18"
字串型別轉成 System.Int32
型別!
解決方案
要解決這個問題,主要有兩種:
-
自訂轉換器 (How to write custom converters for JSON serialization (marshalling) in .NET)
自訂轉換器要額外定義一個類別,雖然結構不複雜,但實作上有比較麻煩些。
-
使用 System.Text.Json
內建的 JsonSerializerDefaults.Web
序列化選項
這個解決方案真的出乎意外的簡單,多加一個參數即可:
var user = JsonSerializer.Deserialize<UserProfile>(jsonText,
new JsonSerializerOptions(JsonSerializerDefaults.Web));
注意: JsonSerializerDefaults 列舉是從 .NET 5 才開始有的型別。
認識 JsonSerializerDefaults.Web
列舉
你要是直接翻出 JsonSerializerOptions
建構式的原始碼出來看,就會發現箇中奧妙之處:
/// <summary>
/// Constructs a new <see cref="JsonSerializerOptions"/> instance with a predefined set of options determined by the specified <see cref="JsonSerializerDefaults"/>.
/// </summary>
/// <param name="defaults"> The <see cref="JsonSerializerDefaults"/> to reason about.</param>
public JsonSerializerOptions(JsonSerializerDefaults defaults) : this()
{
if (defaults == JsonSerializerDefaults.Web)
{
_propertyNameCaseInsensitive = true;
_jsonPropertyNamingPolicy = JsonNamingPolicy.CamelCase;
_numberHandling = JsonNumberHandling.AllowReadingFromString;
}
else if (defaults != JsonSerializerDefaults.General)
{
throw new ArgumentOutOfRangeException(nameof(defaults));
}
}
原來這個 JsonSerializerDefaults 列舉(Enum)的 JsonSerializerDefaults.Web
定義了三個序列化的選項(特性),其中包括:
- 屬性名稱會被視為不區分大小寫 (反序列化時使用)
- 屬性命名會採用 camelCase 命名規則 (序列化時使用)
- 允許從「字串」讀入「數值」型別的屬性 (反序列化時使用)
💡 查看 JsonSerializerDefaults 原始碼
由於 JsonSerializerDefaults.Web
包含了 3 個特性,如果你傳入的 JSON 有一個條件不符合,就建議不要這樣用。例如你傳入的 JSON 屬性名稱的命名規則採用 PascalCase
並且不區分大小寫的話,那你應該這樣撰寫程式碼:
var serializerOptions = new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true,
NumberHandling = JsonNumberHandling.AllowReadingFromString
};
var user = JsonSerializer.Deserialize<UserProfile>(jsonText, serializerOptions);
如果你傳入的 JSON 屬性名稱的命名規則採用 camelCase
並且希望區分大小寫的話,那你應該這樣撰寫程式碼:
var serializerOptions = new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = false,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
NumberHandling = JsonNumberHandling.AllowReadingFromString
};
var user = JsonSerializer.Deserialize<UserProfile>(jsonText, serializerOptions);
以上才是 JsonSerializer.Deserialize
的正確使用方式!
相關連結