從 .NET 11 開始,支援 JsonSerializerC# 15 聯集型別。 聯合會在宣告中持有其中一種案件類型。
JsonSerializer 會寫入目前使用中的 case 值,並可將其讀回。
序列化與反序列化聯合集值
宣告一個其各個成員具有不同 JSON Token 類型的聯集:
public union Payload(int, string, Message);
public sealed record Message(string Text);
使用 JsonSerializer 對聯集進行序列化與還原序列化:
Payload payload = new Message("Ready");
string json = JsonSerializer.Serialize(payload);
Payload copy = JsonSerializer.Deserialize<Payload>(json);
序列化的 JSON 包含有效案例值,而非包裝器或判別符:
{"Text":"Ready"}
預設情況下,序列化器會依憑證類型分類接收的 JSON。 在前述聯集中,JSON 數字選擇 int,JSON 字串選擇 string,JSON 物件選擇 Message。
使用 JsonSerializerOptions.Web,也可以從 JSON 字串中讀取 int。
Payload 的 int 和 string 兩種情況接著都會被歸類為字串權杖類型。 甚至在序列化程式剖析任一種情況之前,"25%" 就會因歧義而擲出 JsonException。 若要使用網頁預設值來讀取字串,請提供自訂分類器,以決定大小寫形式。
區分具有相同 JSON 權杖類型的案例
標記分類無法區分兩個同時序列化為 JSON 物件的情況。 應用 JsonUnionAttribute 並選擇 JsonUnionTypeStructuralClassifier 依屬性名稱分類物件案例:
[JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union Pet(Dog, Cat);
public sealed record Dog(string Name, string Breed);
public sealed record Cat(string Name, int Lives);
此處,JsonUnionAttribute 會為現有的 Pet 聯集選取分類器;將它套用到一般型別上,並不會把該型別變成聯集。
分類器在有效載荷包含 Breed 時選擇 Dog,在其包含 Lives 時選擇 Cat:
Pet pet = JsonSerializer.Deserialize<Pet>(
"""{"Name":"Rex","Breed":"Husky"}""");
對於 JSON 物件,結構分類器會從相容的物件案例開始,並在讀取已識別的根層屬性名稱時縮小該集合。 必要屬性不存在時,會排除這些情況。 JsonUnmappedMemberHandling.Disallow 當有效載荷包含該案例未宣告的屬性時,會移除一個案例。 只有當一個案例還在時,分類才會成功。
結構分類器不會檢查屬性值、巢狀物件、字串內容或陣列元素。 請記住以下後果:
- 若某個有效載荷產生 0 個或多個候選項,則會拋出 JsonException。
- 在建立分類器時,重疊或陰影物件合約可能會被拒絕。
- 不支援多個使用相同 JSON 權杖類型的非物件情況。 舉例來說,
Guid兩者string都使用 JSON 字串。 - 一般物件案例不能與字典、
JsonObject或其他非 POCO 的物件形狀案例混用。 - 不支援巢狀聯集類型案例和多型案例。
- 不支援 ReferenceHandler.Preserve。
- 當序列化程式建立分類器時,無法區分大小寫的組態會拋出 NotSupportedException。
選擇聯合或封閉階層
當你需要保留一個你無法控制的無判別器 JSON 格式,或是案件有明顯的 JSON 形狀時,請使用聯合體。 例如,int的 Payload 和 string 情況可依 JSON 令牌類型區分。 然而,對於例如 Pet(Dog, Cat) 這類物件案例,屬性名稱的變更可能會影響結構分類器會選擇哪個案例。
當你控制型別與 JSON 契約時, 帶有推斷多態性的封閉階層 可以用判別子來識別物件案例:
[JsonPolymorphic(InferClosedTypePolymorphism = true)]
public closed record Event;
public sealed record Created(int Id) : Event;
public sealed record Deleted(int Id) : Event;
JsonSerializer.Serialize<Event>(new Created(42)) 寫道 {"$type":"Created","Id":42}。 兩種導出型皆宣告 Id,但判別子會獨立於其性質之外識別該情況。 隨著性質演變,這使案例選擇更穩定。 與聯集情況不同,導出型別必須共享基底類別,且你必須選擇使用推斷多態性;僅靠 closed 修飾符並不能新增判別器。
提供自訂分類器
當預設權杖分類或內建的 JsonTypeClassifierFactory 不符合您的需求時,請繼承自 JsonUnionTypeStructuralClassifier。 自訂分類器可以使用其他結構規則來選擇聯集案例。 請在以下地點之一註冊工廠:
- 當您自訂合約時,請將委派對象指派給 JsonTypeInfo.TypeClassifier。
- 設定 JsonUnionAttribute.TypeClassifier 為一個工會。
- 將該工廠加入到 JsonSerializerOptions.TypeClassifiers 中,以支援以反射為基礎的序列化。
- 設定 JsonSourceGenerationOptionsAttribute.TypeClassifiers 為來源生成上下文。
分類器讀取目前的 JSON 值,並回傳 中其中一個案例類型。JsonTypeClassifierContext.UnionCases 序列化器首先檢查合約代理,接著檢查每個工會工廠、選擇權層級工廠,以及內建的代幣分類。
對於具歧義的聯集,原始碼產生器會回報診斷訊息,除非在產生時已設定分類器。
處理空值與預設聯合值
聯集可以宣告可空的案件類型。 JSON null 會選擇第一個可為空的情況。 如果聯集沒有可空的案例,JSON null 會產生預設的聯集值。 對於編譯器產生的結構聯集,預設值沒有主動案例,並序列化為 JSON null。
客製化工會合約
對於進階情境,請透過 JsonTypeInfo 自訂聯集中繼資料。 聯合的 JsonTypeInfo.Kind 值為 JsonTypeInfoKind.Union。 其合約內容如下:
- JsonTypeInfo.UnionCases,包含 JsonUnionCaseInfo 元素。
- JsonTypeInfo.UnionConstructor,從一個案例類型和值建立聯集。
- JsonTypeInfo.UnionDeconstructor,回傳有效案件類型與值。
- JsonTypeInfo.TypeClassifier,其會在還原序列化期間選取一個案例。
欲了解更多修改資訊 JsonTypeInfo,請參閱 「自訂 JSON 合約」。