將聯集型別序列化為 System.Text.Json

從 .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。 自訂分類器可以使用其他結構規則來選擇聯集案例。 請在以下地點之一註冊工廠:

分類器讀取目前的 JSON 值,並回傳 中其中一個案例類型。JsonTypeClassifierContext.UnionCases 序列化器首先檢查合約代理,接著檢查每個工會工廠、選擇權層級工廠,以及內建的代幣分類。

對於具歧義的聯集,原始碼產生器會回報診斷訊息,除非在產生時已設定分類器。

處理空值與預設聯合值

聯集可以宣告可空的案件類型。 JSON null 會選擇第一個可為空的情況。 如果聯集沒有可空的案例,JSON null 會產生預設的聯集值。 對於編譯器產生的結構聯集,預設值沒有主動案例,並序列化為 JSON null。

客製化工會合約

對於進階情境,請透過 JsonTypeInfo 自訂聯集中繼資料。 聯合的 JsonTypeInfo.Kind 值為 JsonTypeInfoKind.Union。 其合約內容如下:

欲了解更多修改資訊 JsonTypeInfo,請參閱 「自訂 JSON 合約」。

另請參閱