Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
55 changes: 31 additions & 24 deletions src/libraries/System.Text.Json/System.Text.Json.sln
Original file line number Diff line number Diff line change
Expand Up @@ -35,35 +35,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{676B6044-FA4
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{74017ACD-3AC1-4BB5-804B-D57E305FFBD9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.SourceGeneration", "gen\System.Text.Json.SourceGeneration.csproj", "{6485EED4-C313-4551-9865-8ADCED603629}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Json.SourceGeneration", "gen\System.Text.Json.SourceGeneration.csproj", "{6485EED4-C313-4551-9865-8ADCED603629}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.Tests", "tests\System.Text.Json.Tests\System.Text.Json.Tests.csproj", "{A0178BAA-A1AF-4C69-8E4A-A700A2723DDC}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Json.Tests", "tests\System.Text.Json.Tests\System.Text.Json.Tests.csproj", "{A0178BAA-A1AF-4C69-8E4A-A700A2723DDC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.SourceGeneration.Tests", "tests\System.Text.Json.SourceGeneration.Tests\System.Text.Json.SourceGeneration.Tests.csproj", "{33599A6C-F340-4E1B-9B4D-CB8946C22140}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Json.SourceGeneration.Tests", "tests\System.Text.Json.SourceGeneration.Tests\System.Text.Json.SourceGeneration.Tests.csproj", "{33599A6C-F340-4E1B-9B4D-CB8946C22140}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.SourceGeneration.UnitTests", "tests\System.Text.Json.SourceGeneration.UnitTests\System.Text.Json.SourceGeneration.UnitTests.csproj", "{18173CEC-895F-4F62-B7BB-B724457FEDCD}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Text.Json.SourceGeneration.UnitTests", "tests\System.Text.Json.SourceGeneration.UnitTests\System.Text.Json.SourceGeneration.UnitTests.csproj", "{18173CEC-895F-4F62-B7BB-B724457FEDCD}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "System.Text.Json.FSharp.Tests", "tests\System.Text.Json.FSharp.Tests\System.Text.Json.FSharp.Tests.fsproj", "{5720BF06-2031-4AD8-B9B4-31A01E27ABB8}"
EndProject
Global
GlobalSection(NestedProjects) = preSolution
{102945CA-3736-4B2C-8E68-242A0B247F2B} = {3C544454-BD8B-44F4-A174-B61F18957613}
{73D5739C-E382-4E22-A7D3-B82705C58C74} = {EC8CE194-261A-4115-9582-E2DB1A25CAFB}
{25C42754-B384-4842-8FA7-75D7A79ADF0D} = {EC8CE194-261A-4115-9582-E2DB1A25CAFB}
{4774F56D-16A8-4ABB-8C73-5F57609F1773} = {EC8CE194-261A-4115-9582-E2DB1A25CAFB}
{E2077991-EB83-471C-B17F-72F569FFCE6D} = {EC8CE194-261A-4115-9582-E2DB1A25CAFB}
{BE230195-2A1C-4674-BACB-502C2CD864E9} = {EC8CE194-261A-4115-9582-E2DB1A25CAFB}
{D7276D7D-F117-47C5-B514-8E3E964769BE} = {EC8CE194-261A-4115-9582-E2DB1A25CAFB}
{7015E94D-D20D-48C8-86D7-6A996BE99E0E} = {EC8CE194-261A-4115-9582-E2DB1A25CAFB}
{E9AA0AEB-AEAE-4B28-8D4D-17A6D7C89D17} = {676B6044-FA47-4B7D-AEC2-FA94DB23A423}
{1C8262DB-7355-40A8-A2EC-4EED7363134A} = {676B6044-FA47-4B7D-AEC2-FA94DB23A423}
{D05FD93A-BC51-466E-BD56-3F3D6BBE6B06} = {676B6044-FA47-4B7D-AEC2-FA94DB23A423}
{7909EB27-0D6E-46E6-B9F9-8A1EFD557018} = {676B6044-FA47-4B7D-AEC2-FA94DB23A423}
{9BCCDA15-8907-4AE3-8871-2F17775DDE4C} = {676B6044-FA47-4B7D-AEC2-FA94DB23A423}
{1285FF43-F491-4BE0-B92C-37DA689CBD4B} = {676B6044-FA47-4B7D-AEC2-FA94DB23A423}
{6485EED4-C313-4551-9865-8ADCED603629} = {74017ACD-3AC1-4BB5-804B-D57E305FFBD9}
{A0178BAA-A1AF-4C69-8E4A-A700A2723DDC} = {3C544454-BD8B-44F4-A174-B61F18957613}
{33599A6C-F340-4E1B-9B4D-CB8946C22140} = {3C544454-BD8B-44F4-A174-B61F18957613}
{18173CEC-895F-4F62-B7BB-B724457FEDCD} = {3C544454-BD8B-44F4-A174-B61F18957613}
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
Expand Down Expand Up @@ -141,10 +123,35 @@ Global
{18173CEC-895F-4F62-B7BB-B724457FEDCD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{18173CEC-895F-4F62-B7BB-B724457FEDCD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{18173CEC-895F-4F62-B7BB-B724457FEDCD}.Release|Any CPU.Build.0 = Release|Any CPU
{5720BF06-2031-4AD8-B9B4-31A01E27ABB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5720BF06-2031-4AD8-B9B4-31A01E27ABB8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5720BF06-2031-4AD8-B9B4-31A01E27ABB8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5720BF06-2031-4AD8-B9B4-31A01E27ABB8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{102945CA-3736-4B2C-8E68-242A0B247F2B} = {3C544454-BD8B-44F4-A174-B61F18957613}
{73D5739C-E382-4E22-A7D3-B82705C58C74} = {EC8CE194-261A-4115-9582-E2DB1A25CAFB}
{E9AA0AEB-AEAE-4B28-8D4D-17A6D7C89D17} = {676B6044-FA47-4B7D-AEC2-FA94DB23A423}
{25C42754-B384-4842-8FA7-75D7A79ADF0D} = {EC8CE194-261A-4115-9582-E2DB1A25CAFB}
{1C8262DB-7355-40A8-A2EC-4EED7363134A} = {676B6044-FA47-4B7D-AEC2-FA94DB23A423}
{4774F56D-16A8-4ABB-8C73-5F57609F1773} = {EC8CE194-261A-4115-9582-E2DB1A25CAFB}
{D05FD93A-BC51-466E-BD56-3F3D6BBE6B06} = {676B6044-FA47-4B7D-AEC2-FA94DB23A423}
{E2077991-EB83-471C-B17F-72F569FFCE6D} = {EC8CE194-261A-4115-9582-E2DB1A25CAFB}
{7909EB27-0D6E-46E6-B9F9-8A1EFD557018} = {676B6044-FA47-4B7D-AEC2-FA94DB23A423}
{BE230195-2A1C-4674-BACB-502C2CD864E9} = {EC8CE194-261A-4115-9582-E2DB1A25CAFB}
{D7276D7D-F117-47C5-B514-8E3E964769BE} = {EC8CE194-261A-4115-9582-E2DB1A25CAFB}
{9BCCDA15-8907-4AE3-8871-2F17775DDE4C} = {676B6044-FA47-4B7D-AEC2-FA94DB23A423}
{7015E94D-D20D-48C8-86D7-6A996BE99E0E} = {EC8CE194-261A-4115-9582-E2DB1A25CAFB}
{1285FF43-F491-4BE0-B92C-37DA689CBD4B} = {676B6044-FA47-4B7D-AEC2-FA94DB23A423}
{6485EED4-C313-4551-9865-8ADCED603629} = {74017ACD-3AC1-4BB5-804B-D57E305FFBD9}
{A0178BAA-A1AF-4C69-8E4A-A700A2723DDC} = {3C544454-BD8B-44F4-A174-B61F18957613}
{33599A6C-F340-4E1B-9B4D-CB8946C22140} = {3C544454-BD8B-44F4-A174-B61F18957613}
{18173CEC-895F-4F62-B7BB-B724457FEDCD} = {3C544454-BD8B-44F4-A174-B61F18957613}
{5720BF06-2031-4AD8-B9B4-31A01E27ABB8} = {3C544454-BD8B-44F4-A174-B61F18957613}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5868B757-D821-41FC-952E-2113A0519506}
EndGlobalSection
Expand Down
8 changes: 7 additions & 1 deletion src/libraries/System.Text.Json/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -611,4 +611,10 @@
<data name="FieldCannotBeVirtual" xml:space="preserve">
<value>A 'field' member cannot be 'virtual'. See arguments for the '{0}' and '{1}' parameters. </value>
</data>
</root>
<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>
</root>
7 changes: 7 additions & 0 deletions src/libraries/System.Text.Json/src/System.Text.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
<Compile Include="System\Text\Json\Serialization\IJsonOnSerialized.cs" />
<Compile Include="System\Text\Json\Serialization\IJsonOnSerializing.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializerContext.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\FSharpCoreReflectionProxy.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonMetadataServices.Collections.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonMetadataServices.Converters.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfoInternalOfT.cs" />
Expand Down Expand Up @@ -132,6 +133,12 @@
<Compile Include="System\Text\Json\Serialization\Converters\Collection\ListOfTConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\QueueOfTConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Collection\StackOfTConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\FSharp\FSharpListConverter.cs" />
<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\FSharpOptionConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\FSharp\FSharpValueOptionConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Node\JsonArrayConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Node\JsonNodeConverter.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\Node\JsonNodeConverterFactory.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization.Metadata;

namespace System.Text.Json.Serialization.Converters
{
// Converter for F# lists: https://fsharp.github.io/fsharp-core-docs/reference/fsharp-collections-list-1.html
internal sealed class FSharpListConverter<TList, TElement> : IEnumerableDefaultConverter<TList, TElement>
where TList : IEnumerable<TElement>
{
private readonly Func<IEnumerable<TElement>, TList> _listConstructor;

[RequiresUnreferencedCode(FSharpCoreReflectionProxy.FSharpCoreUnreferencedCodeMessage)]
public FSharpListConverter()
{
_listConstructor = FSharpCoreReflectionProxy.Instance.CreateFSharpListConstructor<TList, TElement>();
}

protected override void Add(in TElement value, ref ReadStack state)
{
((List<TElement>)state.Current.ReturnValue!).Add(value);
}

protected override void CreateCollection(ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options)
{
state.Current.ReturnValue = new List<TElement>();
}

protected override void ConvertCollection(ref ReadStack state, JsonSerializerOptions options)
{
state.Current.ReturnValue = _listConstructor((List<TElement>)state.Current.ReturnValue!);
}

protected override bool OnWriteResume(Utf8JsonWriter writer, TList value, JsonSerializerOptions options, ref WriteStack state)
{
IEnumerator<TElement> enumerator;
if (state.Current.CollectionEnumerator == null)
{
enumerator = value.GetEnumerator();
if (!enumerator.MoveNext())
{
enumerator.Dispose();
return true;
}
}
else
{
enumerator = (IEnumerator<TElement>)state.Current.CollectionEnumerator;
}

JsonConverter<TElement> converter = GetElementConverter(ref state);
do
{
if (ShouldFlush(writer, ref state))
{
state.Current.CollectionEnumerator = enumerator;
return false;
}

TElement element = enumerator.Current;
if (!converter.TryWrite(writer, element, options, ref state))
{
state.Current.CollectionEnumerator = enumerator;
return false;
}
} while (enumerator.MoveNext());

enumerator.Dispose();
return true;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization.Metadata;

namespace System.Text.Json.Serialization.Converters
{
// Converter for F# maps: https://fsharp.github.io/fsharp-core-docs/reference/fsharp-collections-fsharpmap-2.html
internal sealed class FSharpMapConverter<TMap, TKey, TValue> : DictionaryDefaultConverter<TMap, TKey, TValue>
where TMap : IEnumerable<KeyValuePair<TKey, TValue>>
where TKey : notnull
{
private readonly Func<IEnumerable<Tuple<TKey, TValue>>, TMap> _mapConstructor;

[RequiresUnreferencedCode(FSharpCoreReflectionProxy.FSharpCoreUnreferencedCodeMessage)]
public FSharpMapConverter()
{
_mapConstructor = FSharpCoreReflectionProxy.Instance.CreateFSharpMapConstructor<TMap, TKey, TValue>();
}

protected override void Add(TKey key, in TValue value, JsonSerializerOptions options, ref ReadStack state)
{
((List<Tuple<TKey, TValue>>)state.Current.ReturnValue!).Add (new Tuple<TKey, TValue>(key, value));
}

internal override bool CanHaveIdMetadata => false;

protected override void CreateCollection(ref Utf8JsonReader reader, ref ReadStack state)
{
state.Current.ReturnValue = new List<Tuple<TKey, TValue>>();
}

protected override void ConvertCollection(ref ReadStack state, JsonSerializerOptions options)
{
state.Current.ReturnValue = _mapConstructor((List<Tuple<TKey, TValue>>)state.Current.ReturnValue!);
}

protected internal override bool OnWriteResume(Utf8JsonWriter writer, TMap value, JsonSerializerOptions options, ref WriteStack state)
{
IEnumerator<KeyValuePair<TKey, TValue>> enumerator;
if (state.Current.CollectionEnumerator == null)
{
enumerator = value.GetEnumerator();
if (!enumerator.MoveNext())
{
enumerator.Dispose();
return true;
}
}
else
{
enumerator = (IEnumerator<KeyValuePair<TKey, TValue>>)state.Current.CollectionEnumerator;
}

JsonTypeInfo typeInfo = state.Current.JsonTypeInfo;
_keyConverter ??= GetConverter<TKey>(typeInfo.KeyTypeInfo!);
_valueConverter ??= GetConverter<TValue>(typeInfo.ElementTypeInfo!);

do
{
if (ShouldFlush(writer, ref state))
{
state.Current.CollectionEnumerator = enumerator;
return false;
}

if (state.Current.PropertyState < StackFramePropertyState.Name)
{
state.Current.PropertyState = StackFramePropertyState.Name;

TKey key = enumerator.Current.Key;
_keyConverter.WriteWithQuotes(writer, key, options, ref state);
}

TValue element = enumerator.Current.Value;
if (!_valueConverter.TryWrite(writer, element, options, ref state))
{
state.Current.CollectionEnumerator = enumerator;
return false;
}

state.Current.EndDictionaryElement();
} while (enumerator.MoveNext());

enumerator.Dispose();
return true;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization.Metadata;

namespace System.Text.Json.Serialization.Converters
{
// Converter for F# optional values: https://fsharp.github.io/fsharp-core-docs/reference/fsharp-core-option-1.html
// Serializes `Some(value)` using the format of `value` and `None` values as `null`.
internal sealed class FSharpOptionConverter<TOption, TElement> : JsonConverter<TOption>
where TOption : class
{
// Reflect the converter strategy of the element type, since we use the identical contract for Some(_) values.
internal override ConverterStrategy ConverterStrategy => _converterStrategy;
internal override Type? ElementType => typeof(TElement);
// 'None' is encoded using 'null' at runtime and serialized as 'null' in JSON.
public override bool HandleNull => true;

private readonly JsonConverter<TElement> _elementConverter;
private readonly Func<TOption, TElement> _optionValueGetter;
private readonly Func<TElement?, TOption> _optionConstructor;
private readonly ConverterStrategy _converterStrategy;

[RequiresUnreferencedCode(FSharpCoreReflectionProxy.FSharpCoreUnreferencedCodeMessage)]
public FSharpOptionConverter(JsonConverter<TElement> elementConverter)
{
_elementConverter = elementConverter;
_optionValueGetter = FSharpCoreReflectionProxy.Instance.CreateFSharpOptionValueGetter<TOption, TElement>();
_optionConstructor = FSharpCoreReflectionProxy.Instance.CreateFSharpOptionSomeConstructor<TOption, TElement>();

// temporary workaround for JsonConverter base constructor needing to access
// ConverterStrategy when calculating `CanUseDirectReadOrWrite`.
// TODO move `CanUseDirectReadOrWrite` from JsonConverter to JsonTypeInfo.
_converterStrategy = _elementConverter.ConverterStrategy;
CanUseDirectReadOrWrite = _converterStrategy == ConverterStrategy.Value;
}

internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, ref ReadStack state, out TOption? value)
{
// `null` values deserialize as `None`
if (!state.IsContinuation && reader.TokenType == JsonTokenType.Null)
{
value = null;
return true;
}

state.Current.JsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo;
if (_elementConverter.TryRead(ref reader, typeof(TElement), options, ref state, out TElement? element))
{
value = _optionConstructor(element);
return true;
}

value = null;
return false;
}

internal override bool OnTryWrite(Utf8JsonWriter writer, TOption value, JsonSerializerOptions options, ref WriteStack state)
{
if (value is null)
{
// Write `None` values as null
writer.WriteNullValue();
return true;
}

TElement element = _optionValueGetter(value);
state.Current.DeclaredJsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo;
return _elementConverter.TryWrite(writer, element, options, ref state);
}

// Since this is a hybrid converter (ConverterStrategy depends on the element converter),
// we need to override the value converter Write and Read methods too.

public override void Write(Utf8JsonWriter writer, TOption value, JsonSerializerOptions options)
{
if (value is null)
{
writer.WriteNullValue();
}
else
{
TElement element = _optionValueGetter(value);
_elementConverter.Write(writer, element, options);
}
}

public override TOption? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
{
return null;
}

TElement? element = _elementConverter.Read(ref reader, typeToConvert, options);
return _optionConstructor(element);
}
}
}
Loading