diff --git a/src/libraries/System.Text.Json/gen/Helpers/KnownTypeSymbols.cs b/src/libraries/System.Text.Json/gen/Helpers/KnownTypeSymbols.cs index 0837c6f42f8871..a0440bfe77be6d 100644 --- a/src/libraries/System.Text.Json/gen/Helpers/KnownTypeSymbols.cs +++ b/src/libraries/System.Text.Json/gen/Helpers/KnownTypeSymbols.cs @@ -52,6 +52,9 @@ public KnownTypeSymbols(Compilation compilation) public INamedTypeSymbol? ISetOfTType => GetOrResolveType(typeof(ISet<>), ref _ISetOfTType); private Option _ISetOfTType; + public INamedTypeSymbol? IReadOnlySetOfTType => GetOrResolveType("System.Collections.Generic.IReadOnlySet`1", ref _IReadOnlySetOfTType); + private Option _IReadOnlySetOfTType; + public INamedTypeSymbol? StackOfTType => GetOrResolveType(typeof(Stack<>), ref _StackOfTType); private Option _StackOfTType; diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index cefc1c51e2f5ca..953fd9ed3d77af 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -1514,7 +1514,7 @@ private static string GetCollectionInfoMethodName(CollectionType collectionType) CollectionType.MemoryOfT => "CreateMemoryInfo", CollectionType.ReadOnlyMemoryOfT => "CreateReadOnlyMemoryInfo", CollectionType.ISet => "CreateISetInfo", - + CollectionType.IReadOnlySetOfT => "CreateIReadOnlySetInfo", CollectionType.Dictionary => "CreateDictionaryInfo", CollectionType.IDictionaryOfTKeyTValue or CollectionType.IDictionary => "CreateIDictionaryInfo", CollectionType.IReadOnlyDictionary => "CreateIReadOnlyDictionaryInfo", diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index fc64ef50b766f3..1b0959368fb193 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -846,6 +846,11 @@ private bool TryResolveCollectionType( collectionType = CollectionType.ISet; valueType = actualTypeToConvert.TypeArguments[0]; } + else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.IReadOnlySetOfTType)) != null) + { + collectionType = CollectionType.IReadOnlySetOfT; + valueType = actualTypeToConvert.TypeArguments[0]; + } else if ((actualTypeToConvert = type.GetCompatibleGenericBaseType(_knownSymbols.ICollectionOfTType)) != null) { collectionType = CollectionType.ICollectionOfT; diff --git a/src/libraries/System.Text.Json/gen/Model/CollectionType.cs b/src/libraries/System.Text.Json/gen/Model/CollectionType.cs index 7692e0973bb296..e2bb8d8d0837fa 100644 --- a/src/libraries/System.Text.Json/gen/Model/CollectionType.cs +++ b/src/libraries/System.Text.Json/gen/Model/CollectionType.cs @@ -30,6 +30,7 @@ public enum CollectionType Queue, ImmutableEnumerable, MemoryOfT, - ReadOnlyMemoryOfT + ReadOnlyMemoryOfT, + IReadOnlySetOfT } } diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs index cea226f6fadf40..13702e69741d95 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs @@ -1344,6 +1344,11 @@ public static partial class JsonMetadataServices public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateImmutableEnumerableInfo(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues collectionInfo, System.Func, TCollection> createRangeFunc) where TCollection : System.Collections.Generic.IEnumerable { throw null; } public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateIReadOnlyDictionaryInfo(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues collectionInfo) where TCollection : System.Collections.Generic.IReadOnlyDictionary where TKey : notnull { throw null; } public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateISetInfo(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues collectionInfo) where TCollection : System.Collections.Generic.ISet { throw null; } + +#if NET + public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateIReadOnlySetInfo(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues collectionInfo) where TCollection : System.Collections.Generic.IReadOnlySet { throw null; } +#endif + public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateListInfo(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues collectionInfo) where TCollection : System.Collections.Generic.List { throw null; } public static System.Text.Json.Serialization.Metadata.JsonTypeInfo> CreateMemoryInfo(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues> collectionInfo) { throw null; } public static System.Text.Json.Serialization.Metadata.JsonTypeInfo CreateObjectInfo(System.Text.Json.JsonSerializerOptions options, System.Text.Json.Serialization.Metadata.JsonObjectInfoValues objectInfo) where T : notnull { throw null; } diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index e8106e1f215008..befb39824c0210 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -388,6 +388,7 @@ The System.Text.Json library is built-in as part of the shared framework in .NET + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactory.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactory.cs index 59b2591152955e..0b9b62442f0670 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactory.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableConverterFactory.cs @@ -108,6 +108,14 @@ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializer converterType = typeof(ISetOfTConverter<,>); elementType = actualTypeToConvert.GetGenericArguments()[0]; } +#if NET + // IReadOnlySet<> + else if ((actualTypeToConvert = typeToConvert.GetCompatibleGenericInterface(typeof(IReadOnlySet<>))) != null) + { + converterType = typeof(IReadOnlySetOfTConverter<,>); + elementType = actualTypeToConvert.GetGenericArguments()[0]; + } +#endif // ICollection<> else if ((actualTypeToConvert = typeToConvert.GetCompatibleGenericInterface(typeof(ICollection<>))) != null) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlySetOfTConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlySetOfTConverter.cs new file mode 100644 index 00000000000000..ae8ea4e487af93 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlySetOfTConverter.cs @@ -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 + : IEnumerableDefaultConverter + where TCollection : IReadOnlySet + { + private readonly bool _isDeserializable = typeof(TCollection).IsAssignableFrom(typeof(HashSet)); + + protected override void Add(in TElement value, ref ReadStack state) + { + // Directly convert to HashSet since IReadOnlySet does not have an Add method. + HashSet collection = (HashSet)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(); + } + + internal override void ConfigureJsonTypeInfo(JsonTypeInfo jsonTypeInfo, JsonSerializerOptions options) + { + // Deserialize as HashSet for interface types that support it. + if (jsonTypeInfo.CreateObject is null && Type.IsAssignableFrom(typeof(HashSet))) + { + Debug.Assert(Type.IsInterface); + jsonTypeInfo.CreateObject = () => new HashSet(); + } + } + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterFactory.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterFactory.cs index 4ab1eca5b1a5d8..0d8e3fbdf85f89 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterFactory.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterFactory.cs @@ -43,7 +43,7 @@ internal JsonConverter GetConverterInternal(Type typeToConvert, JsonSerializerOp ThrowHelper.ThrowInvalidOperationException_SerializerConverterFactoryReturnsNull(GetType()); break; case JsonConverterFactory: - ThrowHelper.ThrowInvalidOperationException_SerializerConverterFactoryReturnsJsonConverterFactorty(GetType()); + ThrowHelper.ThrowInvalidOperationException_SerializerConverterFactoryReturnsJsonConverterFactory(GetType()); break; } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Collections.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Collections.cs index 6e423c6ff7357f..68304fa84133a2 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Collections.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.Collections.cs @@ -211,6 +211,26 @@ public static JsonTypeInfo CreateISetInfo( collectionInfo, new ISetOfTConverter()); +#if NET + /// + /// Creates serialization metadata for types assignable to . + /// + /// The generic definition of the type. + /// The generic definition of the element type. + /// + /// Provides serialization metadata about the collection type. + /// Serialization metadata for the given type. + /// This API is for use by the output of the System.Text.Json source generator and should not be called directly. + public static JsonTypeInfo CreateIReadOnlySetInfo( + JsonSerializerOptions options, + JsonCollectionInfoValues collectionInfo) + where TCollection : IReadOnlySet + => CreateCore( + options, + collectionInfo, + new IReadOnlySetOfTConverter()); +#endif + /// /// Creates serialization metadata for types assignable to . /// diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs index 3292e7602c94b1..80b2acc9f25b4e 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs @@ -376,7 +376,7 @@ public static void ThrowInvalidOperationException_SerializerConverterFactoryRetu } [DoesNotReturn] - public static void ThrowInvalidOperationException_SerializerConverterFactoryReturnsJsonConverterFactorty(Type converterType) + public static void ThrowInvalidOperationException_SerializerConverterFactoryReturnsJsonConverterFactory(Type converterType) { throw new InvalidOperationException(SR.Format(SR.SerializerConverterFactoryReturnsJsonConverterFactory, converterType)); } diff --git a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Generic.Read.cs b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Generic.Read.cs index d02db100547dbd..94d1aa56df64a3 100644 --- a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Generic.Read.cs +++ b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Generic.Read.cs @@ -687,6 +687,86 @@ public async Task ReadSimpleISetT() Assert.Equal(0, result.Count()); } +#if NET + [Fact] + public async Task ReadNullableGenericStructIReadOnlySetWithNullJson() + { + var wrapper = await Serializer.DeserializeWrapper?>("null"); + Assert.False(wrapper.HasValue); + } + + [Fact] + public async Task ReadIReadOnlySetTOfHashSetT() + { + IReadOnlySet> result = await Serializer.DeserializeWrapper>>(@"[[1,2],[3,4]]"); + + if (result.First().Contains(1)) + { + AssertExtensions.Equal(new HashSet { 1, 2 }, result.First()); + AssertExtensions.Equal(new HashSet { 3, 4 }, result.Last()); + } + else + { + AssertExtensions.Equal(new HashSet { 3, 4 }, result.First()); + AssertExtensions.Equal(new HashSet { 1, 2 }, result.Last()); + } + } + + [Fact] + public async Task ReadHashSetTOfIReadOnlySetT() + { + HashSet> result = await Serializer.DeserializeWrapper>>(@"[[1,2],[3,4]]"); + + if (result.First().Contains(1)) + { + Assert.Equal(new HashSet { 1, 2 }, result.First()); + Assert.Equal(new HashSet { 3, 4 }, result.Last()); + } + else + { + Assert.Equal(new HashSet { 3, 4 }, result.First()); + Assert.Equal(new HashSet { 1, 2 }, result.Last()); + } + } + + [Fact] + public async Task ReadIReadOnlySetTOfArray() + { + IReadOnlySet result = await Serializer.DeserializeWrapper>(@"[[1,2],[3,4]]"); + + if (result.First().Contains(1)) + { + Assert.Equal(new HashSet { 1, 2 }, result.First()); + Assert.Equal(new HashSet { 3, 4 }, result.Last()); + } + else + { + Assert.Equal(new HashSet { 3, 4 }, result.First()); + Assert.Equal(new HashSet { 1, 2 }, result.Last()); + } + } + + [Fact] + public async Task ReadArrayOfIReadOnlySetT() + { + IReadOnlySet[] result = await Serializer.DeserializeWrapper[]>(@"[[1,2],[3,4]]"); + + Assert.Equal(new HashSet { 1, 2 }, result.First()); + Assert.Equal(new HashSet { 3, 4 }, result.Last()); + } + + [Fact] + public async Task ReadSimpleIReadOnlySetT() + { + IReadOnlySet result = await Serializer.DeserializeWrapper>(@"[1,2]"); + + Assert.Equal(new HashSet { 1, 2 }, result); + + result = await Serializer.DeserializeWrapper>(@"[]"); + Assert.Equal(0, result.Count()); + } +#endif + [Fact] public async Task StackTOfStackT() { @@ -1136,6 +1216,15 @@ public static IEnumerable ReadSimpleTestClass_GenericWrappers_NoAddMet SimpleTestClassWithStringToStringIReadOnlyDictionaryWrapper.s_json, typeof(GenericIReadOnlyDictionaryWrapper) }; + +#if NET + yield return new object[] + { + typeof(SimpleTestClassWithStringIReadOnlySetWrapper), + SimpleTestClassWithStringIReadOnlySetWrapper.s_json, + typeof(WrapperForIReadOnlySetOfT) + }; +#endif } } @@ -1173,6 +1262,12 @@ public async Task Read_HigherOrderCollectionInheritance_Works() [InlineData(typeof(GenericIListWrapperInternalConstructor), @"[""1""]")] [InlineData(typeof(GenericISetWrapperPrivateConstructor), @"[""1""]")] [InlineData(typeof(GenericISetWrapperInternalConstructor), @"[""1""]")] + +#if NET + [InlineData(typeof(GenericIReadOnlySetWrapperPrivateConstructor), @"[""1""]")] + [InlineData(typeof(GenericIReadOnlySetWrapperInternalConstructor), @"[""1""]")] +#endif + [InlineData(typeof(GenericIDictionaryWrapperPrivateConstructor), @"{""Key"":""Value""}")] [InlineData(typeof(GenericIDictionaryWrapperInternalConstructor), @"{""Key"":""Value""}")] [InlineData(typeof(StringToStringIReadOnlyDictionaryWrapperPrivateConstructor), @"{""Key"":""Value""}")] @@ -1271,6 +1366,11 @@ public static IEnumerable CustomInterfaces_Enumerables() yield return new object[] { typeof(IDerivedICollectionOfT) }; yield return new object[] { typeof(IDerivedIList) }; yield return new object[] { typeof(IDerivedISetOfT) }; + +#if NET + yield return new object[] { typeof(IDerivedIReadOnlySetOfT) }; +#endif + } public static IEnumerable CustomInterfaces_Dictionaries() diff --git a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Generic.Write.cs b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Generic.Write.cs index aa0a740819d8ea..f3fbfb39c71a61 100644 --- a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Generic.Write.cs +++ b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.Generic.Write.cs @@ -523,6 +523,112 @@ public async Task WritePrimitiveISetT() Assert.True(json == "[1,2]" || json == "[2,1]"); } +#if NET + [Fact] + public async Task GenericStructIReadOnlySetWrapperT() + { + { + GenericStructISetWrapper obj = new GenericStructISetWrapper() { 10, 20 }; + Assert.Equal("[10,20]", await Serializer.SerializeWrapper(obj)); + } + + { + GenericStructISetWrapper obj = default; + Assert.Equal("[]", await Serializer.SerializeWrapper(obj)); + } + } + + [Fact] + public async Task WriteIReadOnlySetTOfHashSetT() + { + IReadOnlySet> input = new HashSet> + { + new HashSet() { 1, 2 }, + new HashSet() { 3, 4 } + }; + + string json = await Serializer.SerializeWrapper(input); + + // Because order isn't guaranteed, roundtrip data to ensure write was accurate. + input = await Serializer.DeserializeWrapper>>(json); + + if (input.First().Contains(1)) + { + AssertExtensions.Equal(new HashSet { 1, 2 }, input.First()); + AssertExtensions.Equal(new HashSet { 3, 4 }, input.Last()); + } + else + { + AssertExtensions.Equal(new HashSet { 3, 4 }, input.First()); + AssertExtensions.Equal(new HashSet { 1, 2 }, input.Last()); + } + } + + [Fact] + public async Task WriteHashSetTOfIReadOnlySet() + { + HashSet> input = new HashSet> + { + new HashSet() { 1, 2 }, + new HashSet() { 3, 4 } + }; + + string json = await Serializer.SerializeWrapper(input); + + // Because order isn't guaranteed, roundtrip data to ensure write was accurate. + input = await Serializer.DeserializeWrapper>>(json); + + if (input.First().Contains(1)) + { + Assert.Equal(new HashSet { 1, 2 }, input.First()); + Assert.Equal(new HashSet { 3, 4 }, input.Last()); + } + else + { + Assert.Equal(new HashSet { 3, 4 }, input.First()); + Assert.Equal(new HashSet { 1, 2 }, input.Last()); + } + } + + [Fact] + public async Task WriteIReadOnlySetTOfArray() + { + IReadOnlySet input = new HashSet + { + new int[] { 1, 2 }, + new int[] { 3, 4 } + }; + + string json = await Serializer.SerializeWrapper(input); + Assert.Contains("[1,2]", json); + Assert.Contains("[3,4]", json); + } + + [Fact] + public async Task WriteArrayOfIReadOnlySet() + { + IReadOnlySet[] input = new HashSet[2]; + input[0] = new HashSet() { 1, 2 }; + input[1] = new HashSet() { 3, 4 }; + + string json = await Serializer.SerializeWrapper(input); + + // Because order isn't guaranteed, roundtrip data to ensure write was accurate. + input = await Serializer.DeserializeWrapper[]>(json); + Assert.Equal(new HashSet { 1, 2 }, input.First()); + Assert.Equal(new HashSet { 3, 4 }, input.Last()); + } + + [Fact] + public async Task WritePrimitiveIReadOnlySet() + { + IReadOnlySet input = new HashSet { 1, 2 }; + + string json = await Serializer.SerializeWrapper(input); + Assert.True(json == "[1,2]" || json == "[2,1]"); + } +#endif + [Fact] public async Task WriteStackTOfStackT() { @@ -802,12 +908,18 @@ public async Task WriteGenericCollectionWrappers() SimpleTestClassWithStringIReadOnlyCollectionWrapper obj3 = new SimpleTestClassWithStringIReadOnlyCollectionWrapper(); SimpleTestClassWithStringIReadOnlyListWrapper obj4 = new SimpleTestClassWithStringIReadOnlyListWrapper(); SimpleTestClassWithStringToStringIReadOnlyDictionaryWrapper obj5 = new SimpleTestClassWithStringToStringIReadOnlyDictionaryWrapper(); +#if NET + SimpleTestClassWithStringIReadOnlySetWrapper obj6 = new SimpleTestClassWithStringIReadOnlySetWrapper(); +#endif obj1.Initialize(); obj2.Initialize(); obj3.Initialize(); obj4.Initialize(); obj5.Initialize(); +#if NET + obj6.Initialize(); +#endif Assert.Equal(SimpleTestClassWithGenericCollectionWrappers.s_json.StripWhitespace(), await Serializer.SerializeWrapper(obj1)); Assert.Equal(SimpleTestClassWithGenericCollectionWrappers.s_json.StripWhitespace(), await Serializer.SerializeWrapper(obj1)); @@ -823,6 +935,11 @@ public async Task WriteGenericCollectionWrappers() Assert.Equal(SimpleTestClassWithStringToStringIReadOnlyDictionaryWrapper.s_json.StripWhitespace(), await Serializer.SerializeWrapper(obj5)); Assert.Equal(SimpleTestClassWithStringToStringIReadOnlyDictionaryWrapper.s_json.StripWhitespace(), await Serializer.SerializeWrapper(obj5)); + +#if NET + Assert.Equal(SimpleTestClassWithStringIReadOnlySetWrapper.s_json.StripWhitespace(), await Serializer.SerializeWrapper(obj6)); + Assert.Equal(SimpleTestClassWithStringIReadOnlySetWrapper.s_json.StripWhitespace(), await Serializer.SerializeWrapper(obj6)); +#endif } [Fact] @@ -957,6 +1074,20 @@ public async Task WriteISetT_DisposesEnumerators() } } +#if NET + [Fact] + public async Task WriteIReadOnlySetT_DisposesEnumerators() + { + for (int count = 0; count < 5; count++) + { + var items = new RefCountedSet(Enumerable.Range(1, count)); + _ = await Serializer.SerializeWrapper((IReadOnlySet)items); + + Assert.Equal(0, items.RefCount); + } + } +#endif + [Fact] public async Task WriteIEnumerableT_ElementSerializationThrows_DisposesEnumerators() { diff --git a/src/libraries/System.Text.Json/tests/Common/JsonCreationHandlingTests.Generic.cs b/src/libraries/System.Text.Json/tests/Common/JsonCreationHandlingTests.Generic.cs index b02cece0945ed6..3b63c20af6f4e9 100644 --- a/src/libraries/System.Text.Json/tests/Common/JsonCreationHandlingTests.Generic.cs +++ b/src/libraries/System.Text.Json/tests/Common/JsonCreationHandlingTests.Generic.cs @@ -260,6 +260,86 @@ public void Validate() } } +#if NET + internal struct StructReadOnlySet : IReadOnlySet + { + private HashSet _set = new HashSet(); + + // we track count separately to make sure tests are not passing by accident because we use reference to list inside of struct + private int _count; + + public int Count => _count; + + public StructReadOnlySet() { } + + /// + /// This method is used to initialize the HashSet because IReadOnlySet does not have an Add method. + /// We use this instead of a constructor to allow for parameterless construction, which is important for some tests. + /// + public StructReadOnlySet Initialize(HashSet items) + { + foreach (T item in items) + { + _set.Add(item); + _count++; + } + + return this; + } + + public bool Contains(T item) + { + return _set.Contains(item); + } + + public IEnumerator GetEnumerator() + { + return ((IReadOnlySet)_set).GetEnumerator(); + } + + public bool IsProperSubsetOf(IEnumerable other) + { + return _set.IsProperSubsetOf(other); + } + + public bool IsProperSupersetOf(IEnumerable other) + { + return _set.IsProperSupersetOf(other); + } + + public bool IsSubsetOf(IEnumerable other) + { + return _set.IsSubsetOf(other); + } + + public bool IsSupersetOf(IEnumerable other) + { + return _set.IsSupersetOf(other); + } + + public bool Overlaps(IEnumerable other) + { + return _set.Overlaps(other); + } + + public bool SetEquals(IEnumerable other) + { + return _set.SetEquals(other); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IReadOnlySet)_set).GetEnumerator(); + } + + public void Validate() + { + // This can fail only if we modified a copy of this struct + Assert.Equal(_count, _set.Count); + } + } +#endif + internal struct StructDictionary : IDictionary, IDictionary { private Dictionary _dict = new(); diff --git a/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.GenericCollections.cs b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.GenericCollections.cs index 92ea12e8089455..8c3e658559f251 100644 --- a/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.GenericCollections.cs +++ b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.GenericCollections.cs @@ -114,6 +114,26 @@ public void Initialize() } } +#if NET + public class SimpleTestClassWithStringIReadOnlySetWrapper + { + public WrapperForIReadOnlySetOfT MyStringIReadOnlySetWrapper { get; set; } + + public static readonly string s_json = + @"{" + + @"""MyStringIReadOnlySetWrapper"" : [""Hello""]" + + @"}"; + + public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json); + + // Call only when testing serialization. + public void Initialize() + { + MyStringIReadOnlySetWrapper = new WrapperForIReadOnlySetOfT(new HashSet { "Hello" }); + } + } +#endif + public class SimpleTestClassWithStringIReadOnlyListWrapper { public StringIReadOnlyListWrapper MyStringIReadOnlyListWrapper { get; set; } @@ -438,6 +458,70 @@ IEnumerator IEnumerable.GetEnumerator() } } +#if NET + public class WrapperForIReadOnlySetOfT : IReadOnlySet + { + private readonly HashSet _hashset; + + public WrapperForIReadOnlySetOfT() + { + _hashset = new HashSet(); + } + + public WrapperForIReadOnlySetOfT(IEnumerable items) + { + _hashset = new HashSet(items); + } + + public int Count => _hashset.Count; + + public bool Contains(T item) + { + return _hashset.Contains(item); + } + + public IEnumerator GetEnumerator() + { + return ((IReadOnlySet)_hashset).GetEnumerator(); + } + + public bool IsProperSubsetOf(IEnumerable other) + { + return _hashset.IsProperSubsetOf(other); + } + + public bool IsProperSupersetOf(IEnumerable other) + { + return _hashset.IsProperSupersetOf(other); + } + + public bool IsSubsetOf(IEnumerable other) + { + return _hashset.IsSubsetOf(other); + } + + public bool IsSupersetOf(IEnumerable other) + { + return _hashset.IsSupersetOf(other); + } + + public bool Overlaps(IEnumerable other) + { + return _hashset.Overlaps(other); + } + + public bool SetEquals(IEnumerable other) + { + return _hashset.SetEquals(other); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IReadOnlySet)_hashset).GetEnumerator(); + } + } +#endif + public class GenericIReadOnlyCollectionWrapper : IReadOnlyCollection { private readonly List _list = new List(); @@ -727,6 +811,146 @@ public class GenericISetWrapperInternalConstructor : GenericISetWrapper internal GenericISetWrapperInternalConstructor() { } } +#if NET + public class StringIReadOnlySetWrapper : IReadOnlySet + { + private readonly HashSet _hashset = new HashSet(); + public int Count => _hashset.Count; + + /// + /// This method is used to initialize the HashSet because IReadOnlySet does not have an Add method. + /// We use this instead of a constructor to allow for parameterless construction, which is important for some tests. + /// + public StringIReadOnlySetWrapper Initialize(HashSet items) + { + foreach (string item in items) + { + _hashset.Add(item); + } + + return this; + } + public bool Contains(string item) + { + return _hashset.Contains(item); + } + public IEnumerator GetEnumerator() + { + return ((IReadOnlySet)_hashset).GetEnumerator(); + } + + public bool IsProperSubsetOf(IEnumerable other) + { + return _hashset.IsProperSubsetOf(other); + } + + public bool IsProperSupersetOf(IEnumerable other) + { + return _hashset.IsProperSupersetOf(other); + } + + public bool IsSubsetOf(IEnumerable other) + { + return _hashset.IsSubsetOf(other); + } + + public bool IsSupersetOf(IEnumerable other) + { + return _hashset.IsSupersetOf(other); + } + + public bool Overlaps(IEnumerable other) + { + return _hashset.Overlaps(other); + } + public bool SetEquals(IEnumerable other) + { + return _hashset.SetEquals(other); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IReadOnlySet)_hashset).GetEnumerator(); + } + } + + public class GenericIReadOnlySetWrapper : IReadOnlySet + { + private readonly HashSet _hashset = new HashSet(); + + public int Count => _hashset.Count; + + /// + /// This method is used to initialize the HashSet because IReadOnlySet does not have an Add method. + /// We use this instead of a constructor to allow for parameterless construction, which is important for some tests. + /// + public GenericIReadOnlySetWrapper Initialize(HashSet items) + { + foreach (T item in items) + { + _hashset.Add(item); + } + + return this; + } + + public bool Contains(T item) + { + return _hashset.Contains(item); + } + + public IEnumerator GetEnumerator() + { + return ((IReadOnlySet)_hashset).GetEnumerator(); + } + + public bool IsProperSubsetOf(IEnumerable other) + { + return _hashset.IsProperSubsetOf(other); + } + + public bool IsProperSupersetOf(IEnumerable other) + { + return _hashset.IsProperSupersetOf(other); + } + + public bool IsSubsetOf(IEnumerable other) + { + return _hashset.IsSubsetOf(other); + } + + public bool IsSupersetOf(IEnumerable other) + { + return _hashset.IsSupersetOf(other); + } + + public bool Overlaps(IEnumerable other) + { + return _hashset.Overlaps(other); + } + + public bool SetEquals(IEnumerable other) + { + return _hashset.SetEquals(other); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IReadOnlySet)_hashset).GetEnumerator(); + } + } + + public class GenericIReadOnlySetWrapperPrivateConstructor : GenericIReadOnlySetWrapper + { + private GenericIReadOnlySetWrapperPrivateConstructor() { } + } + + public class GenericIReadOnlySetWrapperInternalConstructor : GenericIReadOnlySetWrapper + { + internal GenericIReadOnlySetWrapperInternalConstructor() { } + } +#endif + public class GenericIDictionaryWrapper : IDictionary { private readonly Dictionary _dict; @@ -1175,6 +1399,10 @@ public interface IDerivedIDictionaryOfTKeyTValue : IDictionary : ISet { } +#if NET + public interface IDerivedIReadOnlySetOfT : IReadOnlySet { } +#endif + public struct GenericStructIListWrapper : IList { private List _list; @@ -1449,6 +1677,89 @@ IEnumerator IEnumerable.GetEnumerator() } } +#if NET + public struct GenericStructIReadOnlySetWrapper : IReadOnlySet + { + private HashSet _hashset; + + /// + /// This method is used to initialize the HashSet because IReadOnlySet does not have an Add method. + /// We use this instead of a constructor to allow for parameterless construction, which is important for some tests. + /// + public GenericStructIReadOnlySetWrapper Initialize(HashSet items) + { + InitializeIfNull(); + foreach (T item in items) + { + _hashset.Add(item); + } + + return this; + } + + private void InitializeIfNull() + { + if (_hashset == null) + { + _hashset = new HashSet(); + } + } + + public int Count => _hashset == null ? 0 : _hashset.Count; + + public bool Contains(T item) + { + InitializeIfNull(); + return _hashset.Contains(item); + } + public IEnumerator GetEnumerator() + { + InitializeIfNull(); + return ((ISet)_hashset).GetEnumerator(); + } + + public bool IsProperSubsetOf(IEnumerable other) + { + InitializeIfNull(); + return _hashset.IsProperSubsetOf(other); + } + + public bool IsProperSupersetOf(IEnumerable other) + { + InitializeIfNull(); + return _hashset.IsProperSupersetOf(other); + } + + public bool IsSubsetOf(IEnumerable other) + { + InitializeIfNull(); + return _hashset.IsSubsetOf(other); + } + + public bool IsSupersetOf(IEnumerable other) + { + InitializeIfNull(); + return _hashset.IsSupersetOf(other); + } + + public bool Overlaps(IEnumerable other) + { + InitializeIfNull(); + return _hashset.Overlaps(other); + } + public bool SetEquals(IEnumerable other) + { + InitializeIfNull(); + return _hashset.SetEquals(other); + } + + IEnumerator IEnumerable.GetEnumerator() + { + InitializeIfNull(); + return ((ISet)_hashset).GetEnumerator(); + } + } +#endif public struct GenericStructIDictionaryWrapper : IDictionary { private Dictionary _dict; diff --git a/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.Polymorphic.cs b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.Polymorphic.cs index f4f2394d12b09e..b53d1d61b18149 100644 --- a/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.Polymorphic.cs +++ b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.Polymorphic.cs @@ -136,6 +136,7 @@ public class ObjectWithObjectProperties public object? /*IReadOnlyCollection*/ IReadOnlyCollectionT { get; set; } public object? /*IReadOnlyList*/ IReadOnlyListT { get; set; } public object? /*ISet*/ ISetT { get; set; } + public object? /*IReadOnlySet*/ IReadOnlySetT { get; set; } public object? /*Stack*/ StackT { get; set; } public object? /*Queue*/ QueueT { get; set; } public object? /*HashSet*/ HashSetT { get; set; } @@ -171,6 +172,7 @@ public ObjectWithObjectProperties() IReadOnlyCollectionT = new List { "Hello", "World" }; IReadOnlyListT = new List { "Hello", "World" }; ISetT = new HashSet { "Hello", "World" }; + IReadOnlySetT = new HashSet { "Hello", "World" }; StackT = new Stack(new List { "Hello", "World" }); QueueT = new Queue(new List { "Hello", "World" }); HashSetT = new HashSet(new List { "Hello", "World" }); diff --git a/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.cs b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.cs index e77d766398b8b8..9b16ef6ea52d44 100644 --- a/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.cs +++ b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestClasses.cs @@ -685,6 +685,42 @@ public void Verify() } } +#if NET + public class TestClassWithObjectIReadOnlySetT : ITestClass + { + public IReadOnlySet MyData { get; set; } + + public static readonly byte[] s_data = Encoding.UTF8.GetBytes( + @"{" + + @"""MyData"":[" + + SimpleTestClass.s_json + "," + + SimpleTestClass.s_json + + @"]" + + @"}"); + + public void Initialize() + { + SimpleTestClass obj1 = new SimpleTestClass(); + obj1.Initialize(); + + SimpleTestClass obj2 = new SimpleTestClass(); + obj2.Initialize(); + + MyData = new HashSet { obj1, obj2 }; + } + + public void Verify() + { + Assert.Equal(2, MyData.Count); + + foreach (SimpleTestClass obj in MyData) + { + obj.Verify(); + } + } + } +#endif + public class TestClassWithInitializedArray { public int[] Values { get; set; } @@ -1117,6 +1153,53 @@ public void Verify() } } +#if NET + public class TestClassWithGenericIReadOnlySetT : ITestClass + { + public IReadOnlySet MyData { get; set; } + + public static readonly byte[] s_data = Encoding.UTF8.GetBytes( + @"{" + + @"""MyData"":[" + + @"""Hello""," + + @"""World""" + + @"]" + + @"}"); + + public void Initialize() + { + MyData = new HashSet + { + "Hello", + "World" + }; + Assert.Equal(2, MyData.Count); + } + + public void Verify() + { + Assert.Equal(2, MyData.Count); + + bool helloSeen = false; + bool worldSeen = false; + + foreach (string data in MyData) + { + if (data == "Hello") + { + helloSeen = true; + } + else if (data == "World") + { + worldSeen = true; + } + } + + Assert.True(helloSeen && worldSeen); + } + } +#endif + public class TestClassWithStringToPrimitiveDictionary : ITestClass { public Dictionary MyInt32Dict { get; set; } diff --git a/src/libraries/System.Text.Json/tests/Common/TestClasses/TestData.cs b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestData.cs index 1228d1171a1c03..6df6e80e905953 100644 --- a/src/libraries/System.Text.Json/tests/Common/TestClasses/TestData.cs +++ b/src/libraries/System.Text.Json/tests/Common/TestClasses/TestData.cs @@ -33,6 +33,11 @@ public static IEnumerable ReadSuccessCases yield return new object[] { typeof(TestClassWithObjectIReadOnlyCollectionT), TestClassWithObjectIReadOnlyCollectionT.s_data }; yield return new object[] { typeof(TestClassWithObjectIReadOnlyListT), TestClassWithObjectIReadOnlyListT.s_data }; yield return new object[] { typeof(TestClassWithObjectISetT), TestClassWithObjectISetT.s_data }; + +#if NET + yield return new object[] { typeof(TestClassWithObjectIReadOnlySetT), TestClassWithObjectIReadOnlySetT.s_data }; +#endif + yield return new object[] { typeof(TestClassWithStringArray), TestClassWithStringArray.s_data }; yield return new object[] { typeof(TestClassWithGenericList), TestClassWithGenericList.s_data }; yield return new object[] { typeof(TestClassWithGenericIEnumerable), TestClassWithGenericIEnumerable.s_data }; @@ -44,6 +49,11 @@ public static IEnumerable ReadSuccessCases yield return new object[] { typeof(TestClassWithGenericIReadOnlyCollectionT), TestClassWithGenericIReadOnlyCollectionT.s_data }; yield return new object[] { typeof(TestClassWithGenericIReadOnlyListT), TestClassWithGenericIReadOnlyListT.s_data }; yield return new object[] { typeof(TestClassWithGenericISetT), TestClassWithGenericISetT.s_data }; + +#if NET + yield return new object[] { typeof(TestClassWithGenericIReadOnlySetT), TestClassWithGenericIReadOnlySetT.s_data }; +#endif + yield return new object[] { typeof(TestClassWithStringToPrimitiveDictionary), TestClassWithStringToPrimitiveDictionary.s_data }; yield return new object[] { typeof(TestClassWithObjectIEnumerableConstructibleTypes), TestClassWithObjectIEnumerableConstructibleTypes.s_data }; yield return new object[] { typeof(TestClassWithObjectImmutableTypes), TestClassWithObjectImmutableTypes.s_data }; @@ -79,6 +89,11 @@ public static IEnumerable WriteSuccessCases yield return new object[] { new TestClassWithObjectIReadOnlyCollectionT() }; yield return new object[] { new TestClassWithObjectIReadOnlyListT() }; yield return new object[] { new TestClassWithObjectISetT() }; + +#if NET + yield return new object[] { new TestClassWithObjectIReadOnlySetT() }; +#endif + yield return new object[] { new TestClassWithStringArray() }; yield return new object[] { new TestClassWithGenericList() }; yield return new object[] { new TestClassWithGenericIEnumerable() }; @@ -90,6 +105,11 @@ public static IEnumerable WriteSuccessCases yield return new object[] { new TestClassWithGenericIReadOnlyCollectionT() }; yield return new object[] { new TestClassWithGenericIReadOnlyListT() }; yield return new object[] { new TestClassWithGenericISetT() }; + +#if NET + yield return new object[] { new TestClassWithGenericIReadOnlySetT() }; +#endif + yield return new object[] { new TestClassWithStringToPrimitiveDictionary() }; yield return new object[] { new TestClassWithObjectIEnumerableConstructibleTypes() }; yield return new object[] { new TestClassWithObjectImmutableTypes() }; diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/CollectionTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/CollectionTests.cs index cb88437b6b552a..5a8d6a81e91092 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/CollectionTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/CollectionTests.cs @@ -204,6 +204,19 @@ public async Task DeserializeAsyncEnumerable() [JsonSerializable(typeof(ISet))] [JsonSerializable(typeof(ISet[]), TypeInfoPropertyName = "ArrayOfIntISet")] [JsonSerializable(typeof(ISet))] + +#if NET + [JsonSerializable(typeof(IReadOnlySet>))] + [JsonSerializable(typeof(GenericIReadOnlySetWrapper))] + [JsonSerializable(typeof(GenericStructIReadOnlySetWrapper))] + [JsonSerializable(typeof(GenericStructIReadOnlySetWrapper?))] + [JsonSerializable(typeof(IReadOnlySet>))] + [JsonSerializable(typeof(HashSet>))] + [JsonSerializable(typeof(IReadOnlySet))] + [JsonSerializable(typeof(IReadOnlySet[]), TypeInfoPropertyName = "ArrayOfIntIReadOnlySet")] + [JsonSerializable(typeof(IReadOnlySet))] +#endif + [JsonSerializable(typeof(Stack>))] [JsonSerializable(typeof(GenericStackWrapper))] [JsonSerializable(typeof(Stack))] @@ -401,6 +414,12 @@ public async Task DeserializeAsyncEnumerable() [JsonSerializable(typeof(GenericIListWrapperInternalConstructor))] [JsonSerializable(typeof(GenericISetWrapperPrivateConstructor))] [JsonSerializable(typeof(GenericISetWrapperInternalConstructor))] + +#if NET + [JsonSerializable(typeof(GenericIReadOnlySetWrapperPrivateConstructor))] + [JsonSerializable(typeof(GenericIReadOnlySetWrapperInternalConstructor))] +#endif + [JsonSerializable(typeof(GenericIDictionaryWrapperPrivateConstructor))] [JsonSerializable(typeof(GenericIDictionaryWrapperInternalConstructor))] [JsonSerializable(typeof(StringToStringIReadOnlyDictionaryWrapperPrivateConstructor))] @@ -641,6 +660,19 @@ public CollectionTests_Default() [JsonSerializable(typeof(ISet))] [JsonSerializable(typeof(ISet[]), TypeInfoPropertyName = "ArrayOfIntISet")] [JsonSerializable(typeof(ISet))] + +#if NET + [JsonSerializable(typeof(IReadOnlySet>))] + [JsonSerializable(typeof(GenericIReadOnlySetWrapper))] + [JsonSerializable(typeof(GenericStructIReadOnlySetWrapper))] + [JsonSerializable(typeof(GenericStructIReadOnlySetWrapper?))] + [JsonSerializable(typeof(IReadOnlySet>))] + [JsonSerializable(typeof(HashSet>))] + [JsonSerializable(typeof(IReadOnlySet))] + [JsonSerializable(typeof(IReadOnlySet[]), TypeInfoPropertyName = "ArrayOfIntIReadOnlySet")] + [JsonSerializable(typeof(IReadOnlySet))] +#endif + [JsonSerializable(typeof(Stack>))] [JsonSerializable(typeof(GenericStackWrapper))] [JsonSerializable(typeof(Stack))] diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonCreationHandlingTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonCreationHandlingTests.cs index 5862387200f6a4..95d77fc1f6c492 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonCreationHandlingTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonCreationHandlingTests.cs @@ -118,6 +118,9 @@ public sealed class JsonCreationHandlingTests_AsyncStreamWithSmallBuffer() [JsonSerializable(typeof(ClassWithReadOnlyPropertyISetOfInt_BackedBy_StructSetOfIntWithNumberHandling))] [JsonSerializable(typeof(ClassWithReadOnlyPropertyISetOfInt_BackedBy_StructSetOfIntWithNumberHandlingWithAttributeOnType))] [JsonSerializable(typeof(ClassWithReadOnlyPropertyISetOfInt_BackedBy_StructSetOfIntWithNumberHandlingWithoutPopulateAttribute))] + // TODO: Should these cases also be tested (L121 - L129) for IReadOnlySet? + // This goes for here, and JsonCreationHandlingTests.Enumerable.cs as well. + // The tests ABOVE these tests in JsonCreationHandlingTests.Enumerable.cs are for ISet, which I thought are more relevant, so I didn't add them here yet. [JsonSerializable(typeof(StructWithWritablePropertyStructSetOfInt))] [JsonSerializable(typeof(StructWithWritablePropertyStructSetOfIntWithAttributeOnType))] [JsonSerializable(typeof(StructWithWritablePropertyStructSetOfIntWithoutPopulateAttribute))] @@ -127,7 +130,6 @@ public sealed class JsonCreationHandlingTests_AsyncStreamWithSmallBuffer() [JsonSerializable(typeof(StructWithWritableFieldNullableStructSetOfInt))] [JsonSerializable(typeof(StructWithWritableFieldNullableStructSetOfIntWithAttributeOnType))] [JsonSerializable(typeof(StructWithWritableFieldNullableStructSetOfIntWithoutPopulateAttribute))] - [JsonSerializable(typeof(ClassWithReadOnlyPropertyDictionaryOfStringToInt))] [JsonSerializable(typeof(ClassWithReadOnlyPropertyDictionaryOfStringToIntWithAttributeOnType))] [JsonSerializable(typeof(ClassWithReadOnlyPropertyDictionaryOfStringToIntWithoutPopulateAttribute))] @@ -222,6 +224,11 @@ public sealed class JsonCreationHandlingTests_AsyncStreamWithSmallBuffer() [JsonSerializable(typeof(ClassWithWritableProperty>))] [JsonSerializable(typeof(ClassWithWritableProperty>))] [JsonSerializable(typeof(ClassWithWritableProperty>))] + +#if NET + [JsonSerializable(typeof(ClassWithWritableProperty>))] +#endif + [JsonSerializable(typeof(ClassWithWritableProperty>))] [JsonSerializable(typeof(ClassWithWritableProperty>))] [JsonSerializable(typeof(ClassWithWritableProperty))] @@ -230,6 +237,11 @@ public sealed class JsonCreationHandlingTests_AsyncStreamWithSmallBuffer() [JsonSerializable(typeof(ClassWithWritableProperty?>))] [JsonSerializable(typeof(ClassWithWritableProperty?>))] [JsonSerializable(typeof(ClassWithWritableProperty?>))] + +#if NET + [JsonSerializable(typeof(ClassWithWritableProperty?>))] +#endif + [JsonSerializable(typeof(ClassWithWritableProperty?>))] [JsonSerializable(typeof(ClassWithWritableProperty>))] [JsonSerializable(typeof(ClassWithWritableProperty))] @@ -248,6 +260,11 @@ public sealed class JsonCreationHandlingTests_AsyncStreamWithSmallBuffer() [JsonSerializable(typeof(ClassWithReadOnlyProperty>))] [JsonSerializable(typeof(ClassWithReadOnlyProperty>))] [JsonSerializable(typeof(ClassWithReadOnlyProperty>))] + +#if NET + [JsonSerializable(typeof(ClassWithReadOnlyProperty>))] +#endif + [JsonSerializable(typeof(ClassWithReadOnlyProperty>))] [JsonSerializable(typeof(ClassWithReadOnlyProperty>))] [JsonSerializable(typeof(ClassWithReadOnlyProperty))] @@ -259,9 +276,14 @@ public sealed class JsonCreationHandlingTests_AsyncStreamWithSmallBuffer() [JsonSerializable(typeof(ClassWithReadOnlyProperty?>))] [JsonSerializable(typeof(ClassWithReadOnlyProperty>))] [JsonSerializable(typeof(ClassWithReadOnlyProperty?>))] + +#if NET + [JsonSerializable(typeof(ClassWithReadOnlyProperty>))] + [JsonSerializable(typeof(ClassWithReadOnlyProperty?>))] +#endif + [JsonSerializable(typeof(ClassWithReadOnlyProperty>))] [JsonSerializable(typeof(ClassWithReadOnlyProperty?>))] - [JsonSerializable(typeof(ClassWithReadOnlyInitializedField))] [JsonSerializable(typeof(ClassWithInitializedField))] [JsonSerializable(typeof(ClassWithReadOnlyInitializedProperty))] diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ReferenceHandlerTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ReferenceHandlerTests.cs index 36cb8c42f4efda..20c2aae7fa3b8c 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ReferenceHandlerTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ReferenceHandlerTests.cs @@ -100,6 +100,11 @@ public ReferenceHandlerTests_Metadata(JsonSerializerWrapper serializer) [JsonSerializable(typeof(TestClassWithObjectIReadOnlyCollectionT))] [JsonSerializable(typeof(TestClassWithObjectIReadOnlyListT))] [JsonSerializable(typeof(TestClassWithObjectISetT))] + +#if NET + [JsonSerializable(typeof(TestClassWithObjectIReadOnlySetT))] +#endif + [JsonSerializable(typeof(TestClassWithStringArray))] [JsonSerializable(typeof(TestClassWithGenericList))] [JsonSerializable(typeof(TestClassWithGenericIEnumerable))] @@ -111,6 +116,11 @@ public ReferenceHandlerTests_Metadata(JsonSerializerWrapper serializer) [JsonSerializable(typeof(TestClassWithGenericIReadOnlyCollectionT))] [JsonSerializable(typeof(TestClassWithGenericIReadOnlyListT))] [JsonSerializable(typeof(TestClassWithGenericISetT))] + +#if NET + [JsonSerializable(typeof(TestClassWithGenericIReadOnlySetT))] +#endif + [JsonSerializable(typeof(TestClassWithStringToPrimitiveDictionary))] [JsonSerializable(typeof(TestClassWithObjectIEnumerableConstructibleTypes))] [JsonSerializable(typeof(TestClassWithObjectImmutableTypes))] @@ -247,6 +257,11 @@ public ReferenceHandlerTests_Default(JsonSerializerWrapper serializer) [JsonSerializable(typeof(TestClassWithObjectIReadOnlyCollectionT))] [JsonSerializable(typeof(TestClassWithObjectIReadOnlyListT))] [JsonSerializable(typeof(TestClassWithObjectISetT))] + +#if NET + [JsonSerializable(typeof(TestClassWithObjectIReadOnlySetT))] +#endif + [JsonSerializable(typeof(TestClassWithStringArray))] [JsonSerializable(typeof(TestClassWithGenericList))] [JsonSerializable(typeof(TestClassWithGenericIEnumerable))] @@ -258,6 +273,11 @@ public ReferenceHandlerTests_Default(JsonSerializerWrapper serializer) [JsonSerializable(typeof(TestClassWithGenericIReadOnlyCollectionT))] [JsonSerializable(typeof(TestClassWithGenericIReadOnlyListT))] [JsonSerializable(typeof(TestClassWithGenericISetT))] + +#if NET + [JsonSerializable(typeof(TestClassWithGenericIReadOnlySetT))] +#endif + [JsonSerializable(typeof(TestClassWithStringToPrimitiveDictionary))] [JsonSerializable(typeof(TestClassWithObjectIEnumerableConstructibleTypes))] [JsonSerializable(typeof(TestClassWithObjectImmutableTypes))] diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/NewtonsoftTests/ImmutableCollectionsTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/NewtonsoftTests/ImmutableCollectionsTests.cs index c0c0900468191f..2100b128ed84db 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/NewtonsoftTests/ImmutableCollectionsTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/NewtonsoftTests/ImmutableCollectionsTests.cs @@ -292,6 +292,25 @@ public void DeserializeHashSetInterface() Assert.True(data.Contains("II")); Assert.True(data.Contains("One")); } + +#if NET + [Fact] + public void DeserializeIReadOnlySetInterface() + { + string json = @"[ + ""One"", + ""II"", + ""3"" + ]"; + + IReadOnlySet data = JsonSerializer.Deserialize>(json); + + Assert.Equal(3, data.Count); + Assert.True(data.Contains("3")); + Assert.True(data.Contains("II")); + Assert.True(data.Contains("One")); + } +#endif #endregion #region SortedSet diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Array.ReadTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Array.ReadTests.cs index d06465de159395..0ca339a486f3e1 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Array.ReadTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Array.ReadTests.cs @@ -370,6 +370,15 @@ public static void ReadClassWithObjectISetT() obj.Verify(); } +#if NET + [Fact] + public static void ReadClassWithObjectIReadOnlySetT() + { + TestClassWithObjectIReadOnlySetT obj = JsonSerializer.Deserialize(TestClassWithObjectIReadOnlySetT.s_data); + obj.Verify(); + } +#endif + [Fact] public static void ReadClassWithGenericIEnumerableT() { @@ -412,6 +421,15 @@ public static void ReadClassWithGenericISetT() obj.Verify(); } +#if NET + [Fact] + public static void ReadClassWithGenericIReadOnlySetT() + { + TestClassWithGenericIReadOnlySetT obj = JsonSerializer.Deserialize(TestClassWithGenericIReadOnlySetT.s_data); + obj.Verify(); + } +#endif + [Fact] public static void ReadClassWithObjectIEnumerableConstructibleTypes() { diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Null.WriteTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Null.WriteTests.cs index 7de51c9146c54f..5a2b82bb6f4e4a 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Null.WriteTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/Null.WriteTests.cs @@ -141,6 +141,7 @@ public static void NullReferences() obj.StackT = null; obj.QueueT = null; obj.HashSetT = null; + obj.IReadOnlySetT = null; obj.LinkedListT = null; obj.SortedSetT = null; obj.NullableInt = null; @@ -159,6 +160,7 @@ public static void NullReferences() Assert.Contains(@"""StackT"":null", json); Assert.Contains(@"""QueueT"":null", json); Assert.Contains(@"""HashSetT"":null", json); + Assert.Contains(@"""IReadOnlySetT"":null", json); Assert.Contains(@"""LinkedListT"":null", json); Assert.Contains(@"""SortedSetT"":null", json); Assert.Contains(@"""NullableInt"":null", json); diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.cs index 33707fb41c0eca..b4f657e4dfac44 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PolymorphicTests.cs @@ -358,6 +358,7 @@ void Verify(string json) Assert.Contains(@"""IReadOnlyCollectionT"":[""Hello"",""World""]", json); Assert.Contains(@"""IReadOnlyListT"":[""Hello"",""World""]", json); Assert.Contains(@"""ISetT"":[""Hello"",""World""]", json); + Assert.Contains(@"""IReadOnlySetT"":[""Hello"",""World""]", json); Assert.Contains(@"""StackT"":[""World"",""Hello""]", json); Assert.Contains(@"""QueueT"":[""Hello"",""World""]", json); Assert.Contains(@"""HashSetT"":[""Hello"",""World""]", json);