Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
8c176f6
Add System.Text.Json support for F# discriminated unions
eiriktsarpalis Mar 16, 2026
28bfa24
Fix IL2121 build errors: replace unused UnconditionalSuppressMessage …
eiriktsarpalis Mar 16, 2026
7064f6b
Fix CS0117: use FormatterServices.GetUninitializedObject on netstanda…
eiriktsarpalis Mar 16, 2026
68b67c5
Address PR review feedback for F# union converter
eiriktsarpalis Mar 16, 2026
f1a6ae4
Merge branch 'main' into feature/fsharp-unions
eiriktsarpalis Mar 23, 2026
9f5dd92
Merge branch 'main' into feature/fsharp-unions
eiriktsarpalis Mar 23, 2026
30dec36
Respect UnmappedMemberHandling in FSharpUnionConverter
eiriktsarpalis Mar 23, 2026
fc72410
Replace #pragma with [UnconditionalSuppressMessage] for IL2055
eiriktsarpalis Mar 23, 2026
29e5eeb
Simplify union converter: always scan ahead for discriminator
eiriktsarpalis Mar 23, 2026
2f88313
Case-insensitive discriminator/field conflict check and JsonTypeInfo …
eiriktsarpalis Mar 23, 2026
5153a0e
Address remaining review feedback
eiriktsarpalis Mar 23, 2026
7b2ca20
Remove duplicate discriminator detection
eiriktsarpalis Mar 23, 2026
0e4faa4
Add cross-reference comment to ObjectDefaultConverter
eiriktsarpalis Mar 23, 2026
35f8d90
Remove 64-field limit by replacing long bitmask with BitArray
eiriktsarpalis Mar 23, 2026
4e22e94
Simplify tag array sizing
eiriktsarpalis Mar 23, 2026
e2bf97a
Add ValueBitArray and use it in FSharpUnionConverter
eiriktsarpalis Mar 23, 2026
736436b
Revert "Add ValueBitArray and use it in FSharpUnionConverter"
eiriktsarpalis Mar 23, 2026
625ff15
Avoid BitArray allocation when RespectRequiredConstructorParameters i…
eiriktsarpalis Mar 23, 2026
8f5f005
Honor AllowDuplicateProperties and remove dead null check
eiriktsarpalis Mar 23, 2026
985c312
Forward reference handling state to nested field converters
eiriktsarpalis Mar 23, 2026
0ff8043
Address review feedback: duplicate discriminator, error formatting, c…
eiriktsarpalis Mar 24, 2026
612a1a4
Honor field-level JsonConverter and JsonNumberHandling attributes
eiriktsarpalis Mar 24, 2026
cbaa0b8
Handle $id/$ref metadata in union deserialization
eiriktsarpalis Mar 24, 2026
890d7d3
Improve / metadata handling in union deserialization
eiriktsarpalis Mar 24, 2026
096f9b3
Fix CS8604 nullable warning in FieldReader invocation
eiriktsarpalis Mar 24, 2026
5cb8373
Add test verifying struct unions don't emit $id metadata
eiriktsarpalis Mar 24, 2026
0075ff2
Add test: fieldless struct union stays string with Preserve
eiriktsarpalis Mar 24, 2026
552f8a1
Validate / token types during deserialization
eiriktsarpalis Mar 24, 2026
8bece6e
Reuse GetMetadataPropertyName for / detection
eiriktsarpalis Mar 24, 2026
5b7d1f9
Consolidate metadata property detection into single GetMetadataProper…
eiriktsarpalis Mar 24, 2026
cf661f1
Revert "Consolidate metadata property detection into single GetMetada…
eiriktsarpalis Mar 24, 2026
8535824
Consolidate metadata reading into initial scan
eiriktsarpalis Mar 24, 2026
15e6743
Harden metadata handling: duplicates, validation, unescaped spans
eiriktsarpalis Mar 24, 2026
d6f2315
Consolidate field reading into single second-pass loop
eiriktsarpalis Mar 24, 2026
4e82a44
Fix IL2075 linker error and unused variable
eiriktsarpalis Mar 24, 2026
076124d
Continue scanning for after discriminator, assert TryRead/TryWrite
eiriktsarpalis Mar 24, 2026
39c5579
Unify missing-property error formatting
eiriktsarpalis Mar 24, 2026
7bd5129
Fix IL2121 linker error and add T-Gro test coverage
eiriktsarpalis Mar 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/libraries/System.Text.Json/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -641,8 +641,17 @@
<data name="MissingFSharpCoreMember" xml:space="preserve">
<value>Could not locate required member '{0}' from FSharp.Core. This might happen because your application has enabled member-level trimming.</value>
</data>
<data name="FSharpDiscriminatedUnionsNotSupported" xml:space="preserve">
<value>F# discriminated union serialization is not supported. Consider authoring a custom converter for the type.</value>
<data name="FSharpUnionDuplicateCaseName" xml:space="preserve">
<value>The F# discriminated union type '{0}' has duplicate case discriminator name '{1}'.</value>
</data>
<data name="FSharpUnionDuplicateFieldName" xml:space="preserve">
<value>The F# discriminated union type '{0}' case '{1}' has duplicate field name '{2}'.</value>
</data>
<data name="FSharpUnionFieldConflictsWithDiscriminator" xml:space="preserve">
<value>The F# discriminated union type '{0}' has a case field name '{1}' that conflicts with the type discriminator property name '{2}'.</value>
</data>
<data name="FSharpUnionMissingDiscriminatorProperty" xml:space="preserve">
<value>Required type discriminator property '{0}' not found when deserializing F# discriminated union type '{1}'.</value>
</data>
<data name="Polymorphism_BaseConverterDoesNotSupportMetadata" xml:space="preserve">
<value>The converter for polymorphic type '{0}' does not support metadata writes or reads.</value>
Expand Down
1 change: 1 addition & 0 deletions src/libraries/System.Text.Json/src/System.Text.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
<Compile Include="System\Text\Json\Serialization\Converters\FSharp\FSharpMapConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\FSharp\FSharpSetConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\FSharp\FSharpTypeConverterFactory.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\FSharp\FSharpUnionConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\FSharp\FSharpOptionConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\FSharp\FSharpValueOptionConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Node\JsonArrayConverter.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text.Json.Serialization.Metadata;
using FSharpKind = System.Text.Json.Serialization.Metadata.FSharpCoreReflectionProxy.FSharpKind;

Expand Down Expand Up @@ -66,13 +67,40 @@ public override bool CanConvert(Type typeToConvert) =>
Debug.Assert(objectFactory.CanConvert(typeToConvert));
return objectFactory.CreateConverter(typeToConvert, options);
case FSharpKind.Union:
return UnsupportedTypeConverterFactory.CreateUnsupportedConverterForType(typeToConvert, SR.FSharpDiscriminatedUnionsNotSupported);
return CreateFSharpUnionConverter(typeToConvert, options);
default:
Debug.Fail("Unrecognized F# type.");
throw new Exception();
}

return (JsonConverter)Activator.CreateInstance(converterFactoryType, constructorArguments)!;
}

[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "The ctor is marked RequiresUnreferencedCode.")]
private static JsonConverter CreateFSharpUnionConverter(Type typeToConvert, JsonSerializerOptions options)
{
FSharpCoreReflectionProxy proxy = FSharpCoreReflectionProxy.Instance;
FSharpCoreReflectionProxy.FSharpUnionCaseInfo[] caseInfos = proxy.GetUnionCaseInfos(typeToConvert);
Func<object, int> tagReader = proxy.CreateUnionTagReader(typeToConvert);

// Read [JsonPolymorphic] for TypeDiscriminatorPropertyName customization.
string typeDiscriminatorPropertyName = typeToConvert
.GetCustomAttribute<JsonPolymorphicAttribute>(inherit: false)?.TypeDiscriminatorPropertyName
?? JsonSerializer.TypePropertyName;

// Validate the discriminator property name doesn't conflict with reserved metadata properties ($id, $ref, $values).
if (!typeDiscriminatorPropertyName.Equals(JsonSerializer.TypePropertyName, StringComparison.Ordinal))
{
byte[] utf8EncodedName = System.Text.Encoding.UTF8.GetBytes(typeDiscriminatorPropertyName);
if ((JsonSerializer.GetMetadataPropertyName(utf8EncodedName, resolver: null) & ~MetadataPropertyName.Type) != 0)
{
ThrowHelper.ThrowInvalidOperationException_InvalidCustomTypeDiscriminatorPropertyName();
}
}

Type converterType = typeof(FSharpUnionConverter<>).MakeGenericType(typeToConvert);
return (JsonConverter)Activator.CreateInstance(converterType, new object[] { caseInfos, tagReader, options, typeDiscriminatorPropertyName })!;
Comment thread
eiriktsarpalis marked this conversation as resolved.
}
}
}
Loading
Loading