Add System.Text.Json support for F# discriminated unions#125610
Draft
eiriktsarpalis wants to merge 4 commits intomainfrom
Draft
Add System.Text.Json support for F# discriminated unions#125610eiriktsarpalis wants to merge 4 commits intomainfrom
eiriktsarpalis wants to merge 4 commits intomainfrom
Conversation
Implements JSON serialization and deserialization for F# discriminated
unions using a custom JsonConverter. Fieldless cases serialize as JSON
strings; cases with fields serialize as JSON objects with a type
discriminator property.
Key features:
- Fieldless cases: "Point" <-> Point
- Cases with fields: {"":"Circle","radius":3.14} <-> Circle 3.14
- Supports both class and struct unions, recursive unions, and option fields
- Honors PropertyNamingPolicy, PropertyNameCaseInsensitive, and
JsonPropertyNameAttribute on union cases
- Custom discriminator property name via [JsonPolymorphic] attribute
- AllowOutOfOrderMetadataProperties support via checkpoint/restore
- RespectRequiredConstructorParameters support with bitmask validation
- Missing fields default to default(T) when not required
- String form tolerated for cases with fields when not required
- Lazy converter resolution for recursive union types
- Reflection-only (source generators not supported)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…with pragma Replace [UnconditionalSuppressMessage] attributes for IL2075 and IL2055 with #pragma warning disable directives. ILLink no longer emits these warnings (triggering IL2121 unused-suppression errors), but the Roslyn trim analyzer still does, so pragma directives are the correct suppression mechanism. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rd2.0/netfx RuntimeHelpers.GetUninitializedObject is not available on netstandard2.0 or net462. Use FormatterServices.GetUninitializedObject on those targets instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 7 comments.
You can also share your feedback on Copilot code review. Take the survey.
...ystem.Text.Json/src/System/Text/Json/Serialization/Converters/FSharp/FSharpUnionConverter.cs
Outdated
Show resolved
Hide resolved
...ystem.Text.Json/src/System/Text/Json/Serialization/Converters/FSharp/FSharpUnionConverter.cs
Show resolved
Hide resolved
...ystem.Text.Json/src/System/Text/Json/Serialization/Converters/FSharp/FSharpUnionConverter.cs
Show resolved
Hide resolved
...ystem.Text.Json/src/System/Text/Json/Serialization/Converters/FSharp/FSharpUnionConverter.cs
Show resolved
Hide resolved
...Text.Json/src/System/Text/Json/Serialization/Converters/FSharp/FSharpTypeConverterFactory.cs
Show resolved
Hide resolved
...es/System.Text.Json/src/System/Text/Json/Serialization/Metadata/FSharpCoreReflectionProxy.cs
Outdated
Show resolved
Hide resolved
...ystem.Text.Json/src/System/Text/Json/Serialization/Converters/FSharp/FSharpUnionConverter.cs
Outdated
Show resolved
Hide resolved
- Detect duplicate field names in BuildFieldIndexMap and throw (#1) - Honor JsonPropertyNameAttribute on union case field PropertyInfo (#2) - Validate discriminator name doesn't conflict with field names (#3) - Validate token type is String before reading discriminator value (#4) - Validate TypeDiscriminatorPropertyName against reserved names (#5) - Use Delegate.CreateDelegate in ConvertFSharpFunc for performance (#6) - Detect case-insensitive discriminator name collisions and throw (#7) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This was referenced Mar 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
System.Text.Json: Add support for F# discriminated unions
Fixes #55744
Summary
Adds JSON serialization and deserialization support for F# discriminated unions to
System.Text.Json. This is the last remaining F# type that was not supported by STJ.Serialization Format
Fieldless union cases serialize as JSON strings. Cases with fields serialize as JSON objects with a
$typetype discriminator property:Point"Point"Circle 3.14{"$type":"Circle","radius":3.14}Rectangle(10.0, 20.0){"$type":"Rectangle","height":10,"length":20}Both forms are accepted on deserialization:
"Point"and{"$type":"Point"}are equivalent for fieldless cases.Features
PropertyNamingPolicyapplies to both case discriminator names and field names.PropertyNameCaseInsensitiveapplies to both case names and field names during deserialization.JsonPropertyNameAttributeon union cases overrides the discriminator name (takes precedence over naming policy).[<JsonPolymorphic(TypeDiscriminatorPropertyName = "kind")>]on the union type.AllowOutOfOrderMetadataProperties— the$typediscriminator does not need to be the first property.RespectRequiredConstructorParameters— when enabled, all union case fields are treated as required (matching F# record behavior). When disabled, missing fields default todefault(T)."Circle"is accepted for cases with fields whenRespectRequiredConstructorParametersis off (constructs with all-default field values).Examples
Reflection-only
This feature works exclusively with the reflection-based serializer. It does not work with:
JsonSourceGenerationOptions/JsonSerializerContext) — the source generator does not emit metadata for F# union types. Attempting to include an F# union type in a source-generated context will produce a compile-time error or fall back to the defaultobjectserialization.FSharp.Corereflection APIs for reading union case metadata,Func<object[], object>constructors, andFunc<object, object[]>field readers), it requires theRequiresUnreferencedCodeandRequiresDynamicCodecode paths. F# unions will not serialize correctly in trimmed or AOT-compiled applications.Implementation
FSharpUnionConverter<T>— aJsonConverter<T>withConverterStrategy.Valuethat handles both string and object JSON forms.FSharpCoreReflectionProxy— extended with union case metadata extraction (FSharpUnionCaseInfo, tag readers, constructors, field readers).FSharpTypeConverterFactory— updated to detect union types and create the converter, reading[JsonPolymorphic]for custom discriminator names.