You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
JsonSerializerOptions.InferClosedTypePolymorphism and JsonSourceGenerationOptionsAttribute.InferClosedTypePolymorphism (approved in #129041 and implemented by #130808) let the serializer take the derived-type list for a closed hierarchy from compiler-emitted metadata instead of requiring [JsonDerivedType(...)] entries.
Both switches are global: they turn inference on for every closed hierarchy handled by that JsonSerializerOptions instance or JsonSerializerContext. There is no way to opt a single hierarchy in.
The concrete gap is a library or application with one closed hierarchy that should be serialized polymorphically via inference, while the same serializer options or context handles other closed types that use closed-hierarchy semantics for purposes such as pattern matching and should not start emitting $type payloads. Today the author must either enable inference globally or hand-write [JsonDerivedType] registrations for the one hierarchy.
This proposes a declaration-scoped opt-in that mirrors the global flag's semantics but applies only to the type carrying [JsonPolymorphic].
The original #129041 proposal included an attribute-scoped shape named InferDerivedTypes. API review moved it to the options and source-generation surfaces so it could apply to multiple types at once (review notes). This proposal adds the per-declaration variant as a complement: the global switch opts many types in; the attribute opts exactly one in.
namespaceSystem.Text.Json.Serialization;publicsealedpartialclassJsonPolymorphicAttribute:JsonAttribute{// EXISTING// public JsonPolymorphicAttribute() { }// public bool IgnoreUnrecognizedTypeDiscriminators { get; set; }// public Type? TypeClassifier { get; set; }// public string? TypeDiscriminatorPropertyName { get; set; }// public JsonUnknownDerivedTypeHandling UnknownDerivedTypeHandling { get; set; }publicboolInferClosedTypePolymorphism{get;set;}}
Semantics
When true on a base whose compiler metadata marks it as a closed hierarchy, one JsonDerivedType is registered per member of the closed set. Each derived type's simple name, equivalent to nameof, is used as its type discriminator.
Effective inference is global option OR attribute flag. The attribute enables inference for its declaration even when the serializer/source-generation option is false. If global inference is already enabled, the attribute is redundant rather than erroneous.
An inferred derived type must be at least as visible as the base; otherwise reflection throws InvalidOperationException and source generation reports the corresponding diagnostic.
Both the reflection resolver and source generator honor the flag.
Open questions for API review
As a bool, this can only opt in. An unset value is indistinguishable from explicitly setting false, so it cannot suppress inference for one type when the global switch is enabled. Is opt-in-only sufficient, or is a per-type suppression mechanism needed?
The global switch is a no-op for a type without closed-hierarchy metadata. Since this attribute expresses explicit per-type intent, should applying it to a non-closed base instead throw or produce a source-generation diagnostic? The original attribute concept in [API Proposal]: Add STJ support for closed hierarchies #129041 threw InvalidOperationException for this case.
Prototype: none; this is a declaration-scoped form of the behavior implemented by #130808.
API Usage
Opt one hierarchy into inferred polymorphism without enabling it for other closed types handled by the same serializer:
usingSystem.Text.Json;usingSystem.Text.Json.Serialization;[JsonPolymorphic(InferClosedTypePolymorphism=true)]closed class Animal
{publicrequiredstringName{get;set;}}sealedclassDog:Animal{publicrequiredstringBreed{get;set;}}sealedclassCat:Animal{publicintLives{get;set;}}// Not opted in, so this hierarchy remains non-polymorphic.closedclass Shape {}sealedclassCircle:Shape{}stringjson=JsonSerializer.Serialize<Animal>(newDog{Name="Rex",Breed="Lab"});// {"$type":"Dog","Name":"Rex","Breed":"Lab"}
Alternative Designs
Use the global JsonSerializerOptions or JsonSourceGenerationOptionsAttribute switch. This is all-or-nothing across the serializer or context and cannot target one hierarchy.
Hand-write [JsonDerivedType] registrations. This reintroduces the boilerplate and drift that closed-hierarchy inference removes.
Use a constructor parameter.JsonPolymorphicAttribute currently exposes configuration through named properties; another property is consistent with that shape.
Risks
This is an additive settable bool on an existing sealed attribute, defaulting to false. It introduces no behavior change unless explicitly set. When enabled, it reuses the inference behavior implemented by #130808.
Background and motivation
JsonSerializerOptions.InferClosedTypePolymorphismandJsonSourceGenerationOptionsAttribute.InferClosedTypePolymorphism(approved in #129041 and implemented by #130808) let the serializer take the derived-type list for aclosedhierarchy from compiler-emitted metadata instead of requiring[JsonDerivedType(...)]entries.Both switches are global: they turn inference on for every closed hierarchy handled by that
JsonSerializerOptionsinstance orJsonSerializerContext. There is no way to opt a single hierarchy in.The concrete gap is a library or application with one
closedhierarchy that should be serialized polymorphically via inference, while the same serializer options or context handles other closed types that use closed-hierarchy semantics for purposes such as pattern matching and should not start emitting$typepayloads. Today the author must either enable inference globally or hand-write[JsonDerivedType]registrations for the one hierarchy.This proposes a declaration-scoped opt-in that mirrors the global flag's semantics but applies only to the type carrying
[JsonPolymorphic].The original #129041 proposal included an attribute-scoped shape named
InferDerivedTypes. API review moved it to the options and source-generation surfaces so it could apply to multiple types at once (review notes). This proposal adds the per-declaration variant as a complement: the global switch opts many types in; the attribute opts exactly one in.Related: #129041, #130808.
API Proposal
Semantics
trueon a base whose compiler metadata marks it as aclosedhierarchy, oneJsonDerivedTypeis registered per member of the closed set. Each derived type's simple name, equivalent tonameof, is used as its type discriminator.global option OR attribute flag. The attribute enables inference for its declaration even when the serializer/source-generation option isfalse. If global inference is already enabled, the attribute is redundant rather than erroneous.[JsonDerivedType]registrations win wholesale. Matching Infer System.Text.Json polymorphism from closed type hierarchies #130808, inference is skipped when the type has any explicit registration; inferred and explicit lists are not merged.InvalidOperationExceptionand source generation reports the corresponding diagnostic.Open questions for API review
bool, this can only opt in. An unset value is indistinguishable from explicitly settingfalse, so it cannot suppress inference for one type when the global switch is enabled. Is opt-in-only sufficient, or is a per-type suppression mechanism needed?closedbase instead throw or produce a source-generation diagnostic? The original attribute concept in [API Proposal]: Add STJ support for closed hierarchies #129041 threwInvalidOperationExceptionfor this case.Prototype: none; this is a declaration-scoped form of the behavior implemented by #130808.
API Usage
Opt one hierarchy into inferred polymorphism without enabling it for other closed types handled by the same serializer:
Alternative Designs
JsonSerializerOptionsorJsonSourceGenerationOptionsAttributeswitch. This is all-or-nothing across the serializer or context and cannot target one hierarchy.[JsonDerivedType]registrations. This reintroduces the boilerplate and drift that closed-hierarchy inference removes.JsonPolymorphicAttributecurrently exposes configuration through named properties; another property is consistent with that shape.Risks
This is an additive settable
boolon an existing sealed attribute, defaulting tofalse. It introduces no behavior change unless explicitly set. When enabled, it reuses the inference behavior implemented by #130808.Note
This issue was drafted with GitHub Copilot.