-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Add IReadOnlySet support to System.Text.Json #120306
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
506bb05
80a7bb6
419b232
ad9f947
34ec615
4f80f13
5bb7f66
195a155
c5ab159
6221944
587f501
2c17fa1
bdf9655
9f0f3fe
c3f4662
91a1ab0
c8dc1d3
5a54b07
fee078f
f41d710
ee3a3f8
6f4714b
deee409
9d4b385
78226c1
3052f6b
361f77e
ca4534d
d00b1e9
309ee17
3086bfc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1344,6 +1344,12 @@ public static partial class JsonMetadataServices | |
| public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<TCollection> CreateImmutableEnumerableInfo<TCollection, TElement>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues<TCollection> collectionInfo, System.Func<System.Collections.Generic.IEnumerable<TElement>, TCollection> createRangeFunc) where TCollection : System.Collections.Generic.IEnumerable<TElement> { throw null; } | ||
| public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<TCollection> CreateIReadOnlyDictionaryInfo<TCollection, TKey, TValue>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues<TCollection> collectionInfo) where TCollection : System.Collections.Generic.IReadOnlyDictionary<TKey, TValue> where TKey : notnull { throw null; } | ||
| public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<TCollection> CreateISetInfo<TCollection, TElement>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues<TCollection> collectionInfo) where TCollection : System.Collections.Generic.ISet<TElement> { throw null; } | ||
|
|
||
| // Only modern .NET (>= 5.0) supports IReadOnlySet<T>. | ||
| #if NET | ||
| public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<TCollection> CreateIReadOnlySetInfo<TCollection, TElement>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues<TCollection> collectionInfo) where TCollection : System.Collections.Generic.IReadOnlySet<TElement> { throw null; } | ||
| #endif | ||
|
|
||
|
Comment on lines
+1347
to
+1352
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Public API can't be introduced without going through API review process. We need to go through the review like #86442 and Eirik would be responsible for this. |
||
| public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<TCollection> CreateListInfo<TCollection, TElement>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues<TCollection> collectionInfo) where TCollection : System.Collections.Generic.List<TElement> { throw null; } | ||
| public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<System.Memory<TElement>> CreateMemoryInfo<TElement>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues<System.Memory<TElement>> collectionInfo) { throw null; } | ||
| public static System.Text.Json.Serialization.Metadata.JsonTypeInfo<T> CreateObjectInfo<T>(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonObjectInfoValues<T> objectInfo) where T : notnull { throw null; } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -351,6 +351,10 @@ The System.Text.Json library is built-in as part of the shared framework in .NET | |
| <Compile Include="$(CoreLibSharedDir)System\Marvin.cs" Link="Common\System\Marvin.cs" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' and $([MSBuild]::VersionGreaterThanOrEquals('$(TargetFrameworkVersion)', '5.0'))"> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. .NET 5~7 aren't directly targeted by System.Text.Json any more. Just add it into the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would then include .NET Core, which did not have support for IReadOnlySet. The >= net 5.0 check is necessary for successful compilation There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. System.Text.Json doesn't target .NET Core 1.1 ~ 7.0 in current version. No compilation will be affected. |
||
| <Compile Include="System\Text\Json\Serialization\Converters\Collection\IReadOnlySetOfTConverter.cs" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'"> | ||
| <Compile Include="$(CoreLibSharedDir)System\Collections\Generic\ReferenceEqualityComparer.cs" /> | ||
| <Compile Include="$(CoreLibSharedDir)System\Runtime\CompilerServices\IsExternalInit.cs" /> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| // 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; | ||
| using System.Text.Json.Serialization.Metadata; | ||
|
|
||
| namespace System.Text.Json.Serialization.Converters | ||
| { | ||
| internal sealed class IReadOnlySetOfTConverter<TCollection, TElement> | ||
| : IEnumerableDefaultConverter<TCollection, TElement> | ||
| where TCollection : IReadOnlySet<TElement> | ||
| { | ||
| private readonly bool _isDeserializable = typeof(TCollection).IsAssignableFrom(typeof(HashSet<TElement>)); | ||
|
|
||
| protected override void Add(in TElement value, ref ReadStack state) | ||
| { | ||
| // Directly convert to HashSet<TElement> since IReadOnlySet<T> does not have an Add method. | ||
| HashSet<TElement> collection = (HashSet<TElement>)state.Current.ReturnValue!; | ||
| collection.Add(value); | ||
| if (IsValueType) | ||
| { | ||
| state.Current.ReturnValue = collection; | ||
| } | ||
| } | ||
|
|
||
| protected override void CreateCollection(ref Utf8JsonReader reader, scoped ref ReadStack state, JsonSerializerOptions options) | ||
| { | ||
| if (!_isDeserializable) | ||
| { | ||
| ThrowHelper.ThrowNotSupportedException_CannotPopulateCollection(Type, ref reader, ref state); | ||
| } | ||
|
|
||
| state.Current.ReturnValue = new HashSet<TElement>(); | ||
| } | ||
|
|
||
| internal override void ConfigureJsonTypeInfo(JsonTypeInfo jsonTypeInfo, JsonSerializerOptions options) | ||
| { | ||
| // Deserialize as HashSet<TElement> for interface types that support it. | ||
| if (jsonTypeInfo.CreateObject is null && Type.IsAssignableFrom(typeof(HashSet<TElement>))) | ||
| { | ||
| Debug.Assert(Type.IsInterface); | ||
| jsonTypeInfo.CreateObject = () => new HashSet<TElement>(); | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -687,6 +687,87 @@ public async Task ReadSimpleISetT() | |
| Assert.Equal(0, result.Count()); | ||
| } | ||
|
|
||
| // Only modern .NET (>= 5.0) supports IReadOnlySet<T>. | ||
| #if NET | ||
| [Fact] | ||
| public async Task ReadNullableGenericStructIReadOnlySetWithNullJson() | ||
| { | ||
| var wrapper = await Serializer.DeserializeWrapper<GenericStructIReadOnlySetWrapper<int>?>("null"); | ||
| Assert.False(wrapper.HasValue); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task ReadIReadOnlySetTOfHashSetT() | ||
| { | ||
| IReadOnlySet<HashSet<int>> result = await Serializer.DeserializeWrapper<IReadOnlySet<HashSet<int>>>(@"[[1,2],[3,4]]"); | ||
|
|
||
| if (result.First().Contains(1)) | ||
| { | ||
| AssertExtensions.Equal(new HashSet<int> { 1, 2 }, result.First()); | ||
| AssertExtensions.Equal(new HashSet<int> { 3, 4 }, result.Last()); | ||
| } | ||
| else | ||
| { | ||
| AssertExtensions.Equal(new HashSet<int> { 3, 4 }, result.First()); | ||
| AssertExtensions.Equal(new HashSet<int> { 1, 2 }, result.Last()); | ||
| } | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task ReadHashSetTOfIReadOnlySetT() | ||
| { | ||
| HashSet<IReadOnlySet<int>> result = await Serializer.DeserializeWrapper<HashSet<IReadOnlySet<int>>>(@"[[1,2],[3,4]]"); | ||
|
|
||
| if (result.First().Contains(1)) | ||
| { | ||
| Assert.Equal(new HashSet<int> { 1, 2 }, result.First()); | ||
| Assert.Equal(new HashSet<int> { 3, 4 }, result.Last()); | ||
| } | ||
| else | ||
| { | ||
| Assert.Equal(new HashSet<int> { 3, 4 }, result.First()); | ||
| Assert.Equal(new HashSet<int> { 1, 2 }, result.Last()); | ||
| } | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task ReadIReadOnlySetTOfArray() | ||
| { | ||
| IReadOnlySet<int[]> result = await Serializer.DeserializeWrapper<IReadOnlySet<int[]>>(@"[[1,2],[3,4]]"); | ||
|
|
||
| if (result.First().Contains(1)) | ||
| { | ||
| Assert.Equal(new HashSet<int> { 1, 2 }, result.First()); | ||
| Assert.Equal(new HashSet<int> { 3, 4 }, result.Last()); | ||
| } | ||
| else | ||
| { | ||
| Assert.Equal(new HashSet<int> { 3, 4 }, result.First()); | ||
| Assert.Equal(new HashSet<int> { 1, 2 }, result.Last()); | ||
| } | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task ReadArrayOfIReadOnlySetT() | ||
| { | ||
| IReadOnlySet<int>[] result = await Serializer.DeserializeWrapper<IReadOnlySet<int>[]>(@"[[1,2],[3,4]]"); | ||
|
|
||
| Assert.Equal(new HashSet<int> { 1, 2 }, result.First()); | ||
| Assert.Equal(new HashSet<int> { 3, 4 }, result.Last()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task ReadSimpleIReadOnlySetT() | ||
| { | ||
| IReadOnlySet<int> result = await Serializer.DeserializeWrapper<IReadOnlySet<int>>(@"[1,2]"); | ||
|
|
||
| Assert.Equal(new HashSet<int> { 1, 2 }, result); | ||
|
|
||
| result = await Serializer.DeserializeWrapper<IReadOnlySet<int>>(@"[]"); | ||
| Assert.Equal(0, result.Count()); | ||
| } | ||
| #endif | ||
|
|
||
| [Fact] | ||
| public async Task StackTOfStackT() | ||
| { | ||
|
|
@@ -1136,6 +1217,16 @@ public static IEnumerable<object[]> ReadSimpleTestClass_GenericWrappers_NoAddMet | |
| SimpleTestClassWithStringToStringIReadOnlyDictionaryWrapper.s_json, | ||
| typeof(GenericIReadOnlyDictionaryWrapper<string, string>) | ||
| }; | ||
|
|
||
| // Only modern .NET (>= 5.0) supports IReadOnlySet<T>. | ||
| #if NET | ||
| yield return new object[] | ||
| { | ||
| typeof(SimpleTestClassWithStringIReadOnlySetWrapper), | ||
| SimpleTestClassWithStringIReadOnlySetWrapper.s_json, | ||
| typeof(WrapperForIReadOnlySetOfT<string>) | ||
| }; | ||
| #endif | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -1173,6 +1264,12 @@ public async Task Read_HigherOrderCollectionInheritance_Works() | |
| [InlineData(typeof(GenericIListWrapperInternalConstructor<string>), @"[""1""]")] | ||
| [InlineData(typeof(GenericISetWrapperPrivateConstructor<string>), @"[""1""]")] | ||
| [InlineData(typeof(GenericISetWrapperInternalConstructor<string>), @"[""1""]")] | ||
|
|
||
| #if NET | ||
| [InlineData(typeof(GenericIReadOnlySetWrapperPrivateConstructor<string>), @"[""1""]")] | ||
| [InlineData(typeof(GenericIReadOnlySetWrapperInternalConstructor<string>), @"[""1""]")] | ||
| #endif | ||
|
|
||
| [InlineData(typeof(GenericIDictionaryWrapperPrivateConstructor<string, string>), @"{""Key"":""Value""}")] | ||
| [InlineData(typeof(GenericIDictionaryWrapperInternalConstructor<string, string>), @"{""Key"":""Value""}")] | ||
| [InlineData(typeof(StringToStringIReadOnlyDictionaryWrapperPrivateConstructor), @"{""Key"":""Value""}")] | ||
|
|
@@ -1271,6 +1368,12 @@ public static IEnumerable<object[]> CustomInterfaces_Enumerables() | |
| yield return new object[] { typeof(IDerivedICollectionOfT<string>) }; | ||
| yield return new object[] { typeof(IDerivedIList) }; | ||
| yield return new object[] { typeof(IDerivedISetOfT<string>) }; | ||
|
|
||
| // Only modern .NET (>= 5.0) supports IReadOnlySet<T>. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: redundant comment; other types that only available on higher frameworks aren't commented like this. |
||
| #if NET | ||
| yield return new object[] { typeof(IDerivedIReadOnlySetOfT<string>) }; | ||
| #endif | ||
|
|
||
| } | ||
|
|
||
| public static IEnumerable<object[]> CustomInterfaces_Dictionaries() | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's incorrect to use
#ifon target framework in source generator code. The generator itself can run on a different framework (.NET Framework 4.8 when using Visual Studio) than the target framework. See howMemory<T>is handled.