From 046b36aec32a1c5dfcba71794aaf5ab64f1ca9f4 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Tue, 16 Jul 2019 08:53:06 -0400 Subject: [PATCH] Add support for immutable array (dotnet/corefx#39493) * Add support for ImmutableArray * Refactor immutable materializer strategies to use creator helper * Clean up + re-add tests * Check for immutable array * Correct immutable array check * Fix whitespace Commit migrated from https://github.com/dotnet/corefx/commit/2811879c01bf6f3b95aa4ee9b8994cfb62038610 --- .../System.Text.Json/src/System.Text.Json.csproj | 1 + .../DefaultImmutableDictionaryConverter.cs | 4 +- .../DefaultImmutableEnumerableConverter.cs | 9 ++- .../Serialization/ImmutableCollectionCreator.cs | 87 ++++++++++++++++++++++ .../Json/Serialization/JsonPropertyInfoCommon.cs | 20 ++--- .../JsonSerializer.Read.HandleArray.cs | 4 +- .../Json/Serialization/JsonSerializerOptions.cs | 9 +-- .../Text/Json/Serialization/MemberAccessor.cs | 4 +- .../Text/Json/Serialization/ReadStackFrame.cs | 4 +- .../Serialization/ReflectionEmitMemberAccessor.cs | 64 ++++++++++++++-- .../Json/Serialization/ReflectionMemberAccessor.cs | 30 ++++++-- .../tests/Serialization/PolymorphicTests.cs | 8 ++ .../tests/Serialization/PropertyNameTests.cs | 1 + .../TestClasses.ImmutableCollections.cs | 34 +++++++++ .../tests/Serialization/TestClasses.Polymorphic.cs | 2 + .../TestClasses.SimpleTestClassWithObject.cs | 1 + .../tests/Serialization/TestClasses.cs | 20 +++++ .../Value.ReadTests.ImmutableCollections.cs | 74 ++++++++++++++++-- .../Value.WriteTests.ImmutableCollections.cs | 64 ++++++++++++++++ 19 files changed, 397 insertions(+), 43 deletions(-) create mode 100644 src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ImmutableCollectionCreator.cs 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 b874c93..8d2f4dd 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -84,6 +84,7 @@ + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs index 68644de..b6faa4f 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs @@ -30,8 +30,8 @@ namespace System.Text.Json.Serialization.Converters Type constructingType = underlyingType.Assembly.GetType(constructingTypeName); // Create a delegate which will point to the CreateRange method. - object createRangeDelegate; - createRangeDelegate = options.MemberAccessorStrategy.ImmutableDictionaryCreateRange(constructingType, elementType); + ImmutableCollectionCreator createRangeDelegate; + createRangeDelegate = options.MemberAccessorStrategy.ImmutableDictionaryCreateRange(constructingType, immutableCollectionType, elementType); // Cache the delegate options.TryAddCreateRangeDelegate(delegateKey, createRangeDelegate); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs index 869b4dd..a8c5210 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs @@ -9,7 +9,7 @@ namespace System.Text.Json.Serialization.Converters // This converter returns enumerables in the System.Collections.Immutable namespace. internal sealed class DefaultImmutableEnumerableConverter : JsonEnumerableConverter { - private const string ImmutableArrayTypeName = "System.Collections.Immutable.ImmutableArray"; + public const string ImmutableArrayTypeName = "System.Collections.Immutable.ImmutableArray"; public const string ImmutableArrayGenericTypeName = "System.Collections.Immutable.ImmutableArray`1"; private const string ImmutableListTypeName = "System.Collections.Immutable.ImmutableList"; @@ -43,6 +43,9 @@ namespace System.Text.Json.Serialization.Converters switch (underlyingType.FullName) { + case ImmutableArrayGenericTypeName: + constructingTypeName = ImmutableArrayTypeName; + break; case ImmutableListGenericTypeName: case ImmutableListGenericInterfaceTypeName: constructingTypeName = ImmutableListTypeName; @@ -91,8 +94,8 @@ namespace System.Text.Json.Serialization.Converters Type constructingType = underlyingType.Assembly.GetType(constructingTypeName); // Create a delegate which will point to the CreateRange method. - object createRangeDelegate; - createRangeDelegate = options.MemberAccessorStrategy.ImmutableCollectionCreateRange(constructingType, elementType); + ImmutableCollectionCreator createRangeDelegate; + createRangeDelegate = options.MemberAccessorStrategy.ImmutableCollectionCreateRange(constructingType, immutableCollectionType, elementType); // Cache the delegate options.TryAddCreateRangeDelegate(delegateKey, createRangeDelegate); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ImmutableCollectionCreator.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ImmutableCollectionCreator.cs new file mode 100644 index 0000000..efdfb18 --- /dev/null +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ImmutableCollectionCreator.cs @@ -0,0 +1,87 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; + +namespace System.Text.Json +{ + internal abstract class ImmutableCollectionCreator + { + public abstract void RegisterCreatorDelegateFromMethod(MethodInfo creator); + public abstract bool CreateImmutableEnumerable(IList items, out IEnumerable collection); + public abstract bool CreateImmutableDictionary(IDictionary items, out IDictionary collection); + } + + internal sealed class ImmutableEnumerableCreator : ImmutableCollectionCreator + where TCollection : IEnumerable + { + private Func, TCollection> _creatorDelegate; + + public override void RegisterCreatorDelegateFromMethod(MethodInfo creator) + { + Debug.Assert(_creatorDelegate == null); + _creatorDelegate = (Func, TCollection>)creator.CreateDelegate(typeof(Func, TCollection>)); + } + + public override bool CreateImmutableEnumerable(IList items, out IEnumerable collection) + { + Debug.Assert(_creatorDelegate != null); + collection = _creatorDelegate(CreateGenericTElementIEnumerable(items)); + return true; + } + + public override bool CreateImmutableDictionary(IDictionary items, out IDictionary collection) + { + // Shouldn't be calling this method for immutable dictionaries. + collection = default; + return false; + } + + private IEnumerable CreateGenericTElementIEnumerable(IList sourceList) + { + foreach (object item in sourceList) + { + yield return (TElement)item; + } + } + } + + internal sealed class ImmutableDictionaryCreator : ImmutableCollectionCreator + where TCollection : IReadOnlyDictionary + { + private Func>, TCollection> _creatorDelegate; + + public override void RegisterCreatorDelegateFromMethod(MethodInfo creator) + { + Debug.Assert(_creatorDelegate == null); + _creatorDelegate = (Func>, TCollection>)creator.CreateDelegate( + typeof(Func>, TCollection>)); + } + + public override bool CreateImmutableEnumerable(IList items, out IEnumerable collection) + { + // Shouldn't be calling this method for immutable non-dictionaries. + collection = default; + return false; + } + + public override bool CreateImmutableDictionary(IDictionary items, out IDictionary collection) + { + Debug.Assert(_creatorDelegate != null); + collection = (IDictionary)_creatorDelegate(CreateGenericTElementIDictionary(items)); + return true; + } + + private IEnumerable> CreateGenericTElementIDictionary(IDictionary sourceDictionary) + { + foreach (DictionaryEntry item in sourceDictionary) + { + yield return new KeyValuePair((string)item.Key, (TElement)item.Value); + } + } + } +} diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs index 56d7bd7..22bda60 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs @@ -250,15 +250,15 @@ namespace System.Text.Json // CreateRange method to create and return the desired immutable collection type. public override IEnumerable CreateImmutableCollectionInstance(Type collectionType, string delegateKey, IList sourceList, string jsonPath, JsonSerializerOptions options) { - if (!options.TryGetCreateRangeDelegate(delegateKey, out object createRangeDelegateObj)) + IEnumerable collection = null; + + if (!options.TryGetCreateRangeDelegate(delegateKey, out ImmutableCollectionCreator creator) || + !creator.CreateImmutableEnumerable(sourceList, out collection)) { ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(collectionType, jsonPath); } - JsonSerializerOptions.ImmutableCreateRangeDelegate createRangeDelegate = ( - (JsonSerializerOptions.ImmutableCreateRangeDelegate)createRangeDelegateObj); - - return (IEnumerable)createRangeDelegate.Invoke(CreateGenericTRuntimePropertyIEnumerable(sourceList)); + return collection; } // Creates an IEnumerable and populates it with the items in the @@ -266,15 +266,15 @@ namespace System.Text.Json // CreateRange method to create and return the desired immutable collection type. public override IDictionary CreateImmutableDictionaryInstance(Type collectionType, string delegateKey, IDictionary sourceDictionary, string jsonPath, JsonSerializerOptions options) { - if (!options.TryGetCreateRangeDelegate(delegateKey, out object createRangeDelegateObj)) + IDictionary collection = null; + + if (!options.TryGetCreateRangeDelegate(delegateKey, out ImmutableCollectionCreator creator) || + !creator.CreateImmutableDictionary(sourceDictionary, out collection)) { ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(collectionType, jsonPath); } - JsonSerializerOptions.ImmutableDictCreateRangeDelegate createRangeDelegate = ( - (JsonSerializerOptions.ImmutableDictCreateRangeDelegate)createRangeDelegateObj); - - return (IDictionary)createRangeDelegate.Invoke(CreateGenericIEnumerableFromDictionary(sourceDictionary)); + return collection; } private IEnumerable CreateGenericTRuntimePropertyIEnumerable(IList sourceList) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs index 7c5d335..148c5ec 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs @@ -179,7 +179,9 @@ namespace System.Text.Json else { IList list = (IList)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue); - if (list == null) + if (list == null || + // ImmutableArray is a struct, so default value won't be null. + state.Current.JsonPropertyInfo.RuntimePropertyType.FullName.StartsWith(DefaultImmutableEnumerableConverter.ImmutableArrayGenericTypeName)) { state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs index c3e3a74..fa679aa 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs @@ -20,7 +20,7 @@ namespace System.Text.Json private readonly ConcurrentDictionary _classes = new ConcurrentDictionary(); private readonly ConcurrentDictionary _objectJsonProperties = new ConcurrentDictionary(); - private static ConcurrentDictionary s_createRangeDelegates = new ConcurrentDictionary(); + private static ConcurrentDictionary s_createRangeDelegates = new ConcurrentDictionary(); private MemberAccessor _memberAccessorStrategy; private JsonNamingPolicy _dictionayKeyPolicy; private JsonNamingPolicy _jsonPropertyNamingPolicy; @@ -331,9 +331,6 @@ namespace System.Text.Json }; } - internal delegate object ImmutableCreateRangeDelegate(IEnumerable items); - internal delegate object ImmutableDictCreateRangeDelegate(IEnumerable> items); - internal JsonPropertyInfo GetJsonPropertyInfoFromClassInfo(JsonClassInfo classInfo, JsonSerializerOptions options) { Type objectType = classInfo.Type; @@ -352,12 +349,12 @@ namespace System.Text.Json return s_createRangeDelegates.ContainsKey(key); } - internal bool TryGetCreateRangeDelegate(string delegateKey, out object createRangeDelegate) + internal bool TryGetCreateRangeDelegate(string delegateKey, out ImmutableCollectionCreator createRangeDelegate) { return s_createRangeDelegates.TryGetValue(delegateKey, out createRangeDelegate) && createRangeDelegate != null; } - internal bool TryAddCreateRangeDelegate(string key, object createRangeDelegate) + internal bool TryAddCreateRangeDelegate(string key, ImmutableCollectionCreator createRangeDelegate) { return s_createRangeDelegates.TryAdd(key, createRangeDelegate); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs index 166713e..bb513ac 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs @@ -11,9 +11,9 @@ namespace System.Text.Json { public abstract JsonClassInfo.ConstructorDelegate CreateConstructor(Type classType); - public abstract object ImmutableCollectionCreateRange(Type constructingType, Type elementType); + public abstract ImmutableCollectionCreator ImmutableCollectionCreateRange(Type constructingType, Type collectionType, Type elementType); - public abstract object ImmutableDictionaryCreateRange(Type constructingType, Type elementType); + public abstract ImmutableCollectionCreator ImmutableDictionaryCreateRange(Type constructingType, Type collectionType, Type elementType); protected MethodInfo ImmutableCollectionCreateRangeMethod(Type constructingType, Type elementType) { diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs index f795e62..885dbec 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs @@ -6,6 +6,7 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Text.Json.Serialization.Converters; namespace System.Text.Json { @@ -155,7 +156,8 @@ namespace System.Text.Json state.Current.TempEnumerableValues = converterList; // Clear the value if present to ensure we don't confuse tempEnumerableValues with the collection. - if (!jsonPropertyInfo.IsPropertyPolicy) + if (!jsonPropertyInfo.IsPropertyPolicy && + !state.Current.JsonPropertyInfo.RuntimePropertyType.FullName.StartsWith(DefaultImmutableEnumerableConverter.ImmutableArrayGenericTypeName)) { jsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, null); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs index f41f7cb..27e9812 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs @@ -55,7 +55,7 @@ namespace System.Text.Json } - public override object ImmutableCollectionCreateRange(Type constructingType, Type elementType) + public override ImmutableCollectionCreator ImmutableCollectionCreateRange(Type constructingType, Type collectionType, Type elementType) { MethodInfo createRange = ImmutableCollectionCreateRangeMethod(constructingType, elementType); @@ -64,12 +64,38 @@ namespace System.Text.Json return null; } - return createRange.CreateDelegate( - typeof(JsonSerializerOptions.ImmutableCreateRangeDelegate<>).MakeGenericType(elementType), null); + Type creatorType = typeof(ImmutableEnumerableCreator<,>).MakeGenericType(elementType, collectionType); + + ConstructorInfo realMethod = creatorType.GetConstructor( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, + binder: null, + Type.EmptyTypes, + modifiers: null); + + Debug.Assert(realMethod != null); + + var dynamicMethod = new DynamicMethod( + ConstructorInfo.ConstructorName, + typeof(object), + Type.EmptyTypes, + typeof(ReflectionEmitMemberAccessor).Module, + skipVisibility: true); + + ILGenerator generator = dynamicMethod.GetILGenerator(); + generator.Emit(OpCodes.Newobj, realMethod); + generator.Emit(OpCodes.Ret); + + JsonClassInfo.ConstructorDelegate constructor = (JsonClassInfo.ConstructorDelegate)dynamicMethod.CreateDelegate( + typeof(JsonClassInfo.ConstructorDelegate)); + + ImmutableCollectionCreator creator = (ImmutableCollectionCreator)constructor(); + + creator.RegisterCreatorDelegateFromMethod(createRange); + return creator; } - public override object ImmutableDictionaryCreateRange(Type constructingType, Type elementType) + public override ImmutableCollectionCreator ImmutableDictionaryCreateRange(Type constructingType, Type collectionType, Type elementType) { MethodInfo createRange = ImmutableDictionaryCreateRangeMethod(constructingType, elementType); @@ -78,8 +104,34 @@ namespace System.Text.Json return null; } - return createRange.CreateDelegate( - typeof(JsonSerializerOptions.ImmutableDictCreateRangeDelegate<,>).MakeGenericType(typeof(string), elementType), null); + Type creatorType = typeof(ImmutableDictionaryCreator<,>).MakeGenericType(elementType, collectionType); + + ConstructorInfo realMethod = creatorType.GetConstructor( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, + binder: null, + Type.EmptyTypes, + modifiers: null); + + Debug.Assert(realMethod != null); + + var dynamicMethod = new DynamicMethod( + ConstructorInfo.ConstructorName, + typeof(object), + Type.EmptyTypes, + typeof(ReflectionEmitMemberAccessor).Module, + skipVisibility: true); + + ILGenerator generator = dynamicMethod.GetILGenerator(); + generator.Emit(OpCodes.Newobj, realMethod); + generator.Emit(OpCodes.Ret); + + JsonClassInfo.ConstructorDelegate constructor = (JsonClassInfo.ConstructorDelegate)dynamicMethod.CreateDelegate( + typeof(JsonClassInfo.ConstructorDelegate)); + + ImmutableCollectionCreator creator = (ImmutableCollectionCreator)constructor(); + + creator.RegisterCreatorDelegateFromMethod(createRange); + return creator; } public override Func CreatePropertyGetter(PropertyInfo propertyInfo) => diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs index d5afac2..9e73e49 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs @@ -42,7 +42,7 @@ namespace System.Text.Json return () => Activator.CreateInstance(type); } - public override object ImmutableCollectionCreateRange(Type constructingType, Type elementType) + public override ImmutableCollectionCreator ImmutableCollectionCreateRange(Type constructingType, Type collectionType, Type elementType) { MethodInfo createRange = ImmutableCollectionCreateRangeMethod(constructingType, elementType); @@ -51,12 +51,21 @@ namespace System.Text.Json return null; } - return createRange.CreateDelegate( - typeof(JsonSerializerOptions.ImmutableCreateRangeDelegate<>).MakeGenericType(elementType), null); + Type creatorType = typeof(ImmutableEnumerableCreator<,>).MakeGenericType(elementType, collectionType); + ConstructorInfo constructor = creatorType.GetConstructor( + BindingFlags.Public | + BindingFlags.NonPublic | + BindingFlags.Instance, binder: null, + Type.EmptyTypes, + modifiers: null); + + ImmutableCollectionCreator creator = (ImmutableCollectionCreator)constructor.Invoke(new object[] { }); + creator.RegisterCreatorDelegateFromMethod(createRange); + return creator; } - public override object ImmutableDictionaryCreateRange(Type constructingType, Type elementType) + public override ImmutableCollectionCreator ImmutableDictionaryCreateRange(Type constructingType, Type collectionType, Type elementType) { MethodInfo createRange = ImmutableDictionaryCreateRangeMethod(constructingType, elementType); @@ -65,8 +74,17 @@ namespace System.Text.Json return null; } - return createRange.CreateDelegate( - typeof(JsonSerializerOptions.ImmutableDictCreateRangeDelegate<,>).MakeGenericType(typeof(string), elementType), null); + Type creatorType = typeof(ImmutableDictionaryCreator<,>).MakeGenericType(elementType, collectionType); + ConstructorInfo constructor = creatorType.GetConstructor( + BindingFlags.Public | + BindingFlags.NonPublic | + BindingFlags.Instance, binder: null, + Type.EmptyTypes, + modifiers: null); + + ImmutableCollectionCreator creator = (ImmutableCollectionCreator)constructor.Invoke(new object[] { }); + creator.RegisterCreatorDelegateFromMethod(createRange); + return creator; } public override Func CreatePropertyGetter(PropertyInfo propertyInfo) diff --git a/src/libraries/System.Text.Json/tests/Serialization/PolymorphicTests.cs b/src/libraries/System.Text.Json/tests/Serialization/PolymorphicTests.cs index 6ef38d7..6266c72 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/PolymorphicTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/PolymorphicTests.cs @@ -191,6 +191,13 @@ namespace System.Text.Json.Serialization.Tests json = JsonSerializer.Serialize(linkedlist); Assert.Equal(ExpectedJson, json); + ImmutableArray immutablearray = ImmutableArray.CreateRange(new List { 1, true, address, null, "foo" }); + json = JsonSerializer.Serialize(immutablearray); + Assert.Equal(ExpectedJson, json); + + json = JsonSerializer.Serialize(immutablearray); + Assert.Equal(ExpectedJson, json); + IImmutableList iimmutablelist = ImmutableList.CreateRange(new List { 1, true, address, null, "foo" }); json = JsonSerializer.Serialize(iimmutablelist); Assert.Equal(ExpectedJson, json); @@ -306,6 +313,7 @@ namespace System.Text.Json.Serialization.Tests Assert.Contains(@"""HashSetT"":[""Hello"",""World""]", json); Assert.Contains(@"""LinkedListT"":[""Hello"",""World""]", json); Assert.Contains(@"""SortedSetT"":[""Hello"",""World""]", json); + Assert.Contains(@"""ImmutableArrayT"":[""Hello"",""World""]", json); Assert.Contains(@"""IImmutableListT"":[""Hello"",""World""]", json); Assert.Contains(@"""IImmutableStackT"":[""World"",""Hello""]", json); Assert.Contains(@"""IImmutableQueueT"":[""Hello"",""World""]", json); diff --git a/src/libraries/System.Text.Json/tests/Serialization/PropertyNameTests.cs b/src/libraries/System.Text.Json/tests/Serialization/PropertyNameTests.cs index 0afe52fb..539c162 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/PropertyNameTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/PropertyNameTests.cs @@ -40,6 +40,7 @@ namespace System.Text.Json.Serialization.Tests options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; SimpleTestClass obj = JsonSerializer.Deserialize(@"{}", options); + string json = JsonSerializer.Serialize(obj, options); Assert.Contains(@"""myInt16"":0", json); Assert.Contains(@"""myInt32"":0", json); diff --git a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.ImmutableCollections.cs b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.ImmutableCollections.cs index e9e2eda..95bc12b 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.ImmutableCollections.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.ImmutableCollections.cs @@ -10,6 +10,40 @@ using Xunit; namespace System.Text.Json.Serialization.Tests { + public class SimpleTestClassWithImmutableArray : ITestClass + { + public ImmutableArray MyStringImmutableArray { get; set; } + + public static readonly string s_json = @"{""MyStringImmutableArray"":[""Hello""]}"; + + public void Initialize() + { + MyStringImmutableArray = ImmutableArray.CreateRange(new List { "Hello" }); + } + + public void Verify() + { + Assert.Equal("Hello", MyStringImmutableArray[0]); + } + } + + public class SimpleTestClassWithObjectImmutableArray : ITestClass + { + public object MyStringImmutableArray { get; set; } + + public static readonly string s_json = @"{""MyStringImmutableArray"":[""Hello""]}"; + + public void Initialize() + { + MyStringImmutableArray = ImmutableArray.CreateRange(new List { "Hello" }); + } + + public void Verify() + { + Assert.Equal("Hello", ((ImmutableArray)MyStringImmutableArray)[0]); + } + } + public class SimpleTestClassWithIImmutableDictionaryWrapper { public StringToStringIImmutableDictionaryWrapper MyStringToStringImmutableDictionaryWrapper { get; set; } diff --git a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.Polymorphic.cs b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.Polymorphic.cs index 7ad0c5a..8e28a81 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.Polymorphic.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.Polymorphic.cs @@ -142,6 +142,7 @@ namespace System.Text.Json.Serialization.Tests public object /*HashSet*/ HashSetT { get; set; } public object /*LinkedList*/ LinkedListT { get; set; } public object /*SortedSet*/ SortedSetT { get; set; } + public object /*ImmutableArray*/ ImmutableArrayT { get; set; } public object /*IImmutableList*/ IImmutableListT { get; set; } public object /*IImmutableStack*/ IImmutableStackT { get; set; } public object /*IImmutableQueue*/ IImmutableQueueT { get; set; } @@ -176,6 +177,7 @@ namespace System.Text.Json.Serialization.Tests HashSetT = new HashSet(new List { "Hello", "World" }); LinkedListT = new LinkedList(new List { "Hello", "World" }); SortedSetT = new SortedSet(new List { "Hello", "World" }); + ImmutableArrayT = ImmutableArray.CreateRange(new List { "Hello", "World" }); IImmutableListT = ImmutableList.CreateRange(new List { "Hello", "World" }); IImmutableStackT = ImmutableStack.CreateRange(new List { "Hello", "World" }); IImmutableQueueT = ImmutableQueue.CreateRange(new List { "Hello", "World" }); diff --git a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithObject.cs b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithObject.cs index dc21f4f..32cc3ad 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithObject.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithObject.cs @@ -57,6 +57,7 @@ namespace System.Text.Json.Serialization.Tests public object MyStringIImmutableQueueT { get; set; } public object MyStringIImmutableSetT { get; set; } public object MyStringImmutableHashSetT { get; set; } + public object MyStringImmutableArray { get; set; } public object MyStringImmutableListT { get; set; } public object MyStringImmutableStackT { get; set; } public object MyStringImmutablQueueT { get; set; } diff --git a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.cs b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.cs index 34ba24c..41b5490 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/TestClasses.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/TestClasses.cs @@ -1339,6 +1339,7 @@ namespace System.Text.Json.Serialization.Tests public class TestClassWithObjectImmutableTypes : ITestClass { + public ImmutableArray MyImmutableArray { get; set; } public IImmutableList MyIImmutableList { get; set; } public IImmutableStack MyIImmutableStack { get; set; } public IImmutableQueue MyIImmutableQueue { get; set; } @@ -1350,6 +1351,10 @@ namespace System.Text.Json.Serialization.Tests public static readonly byte[] s_data = Encoding.UTF8.GetBytes( @"{" + + @"""MyImmutableArray"":[" + + SimpleTestClass.s_json + "," + + SimpleTestClass.s_json + + @"]," + @"""MyIImmutableList"":[" + SimpleTestClass.s_json + "," + SimpleTestClass.s_json + @@ -1393,6 +1398,15 @@ namespace System.Text.Json.Serialization.Tests SimpleTestClass obj2 = new SimpleTestClass(); obj2.Initialize(); + MyImmutableArray = ImmutableArray.CreateRange(new List { obj1, obj2 }); + } + { + SimpleTestClass obj1 = new SimpleTestClass(); + obj1.Initialize(); + + SimpleTestClass obj2 = new SimpleTestClass(); + obj2.Initialize(); + MyIImmutableList = ImmutableList.CreateRange(new List { obj1, obj2 }); } { @@ -1462,6 +1476,12 @@ namespace System.Text.Json.Serialization.Tests public void Verify() { + Assert.Equal(2, MyImmutableArray.Length); + foreach (SimpleTestClass data in MyImmutableArray) + { + data.Verify(); + } + Assert.Equal(2, MyIImmutableList.Count); foreach (SimpleTestClass data in MyIImmutableList) { diff --git a/src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.ImmutableCollections.cs b/src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.ImmutableCollections.cs index a6e1dd2..e16678d 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.ImmutableCollections.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.ImmutableCollections.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Text.Json.Tests; using Xunit; namespace System.Text.Json.Serialization.Tests @@ -12,6 +13,73 @@ namespace System.Text.Json.Serialization.Tests public static partial class ValueTests { [Fact] + public static void ReadImmutableArrayOfImmutableArray() + { + ImmutableArray> result = JsonSerializer.Deserialize>>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (ImmutableArray l in result) + { + foreach (int i in l) + { + Assert.Equal(expected++, i); + } + } + } + + [Fact] + public static void ReadImmutableArrayOfArray() + { + ImmutableArray result = JsonSerializer.Deserialize>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (int[] arr in result) + { + foreach (int i in arr) + { + Assert.Equal(expected++, i); + } + } + } + + [Fact] + public static void ReadArrayOfImmutableArray() + { + ImmutableArray[] result = JsonSerializer.Deserialize[]>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (ImmutableArray l in result) + { + foreach (int i in l) + { + Assert.Equal(expected++, i); + } + } + } + + [Fact] + public static void ReadSimpleImmutableArray() + { + ImmutableArray result = JsonSerializer.Deserialize>(Encoding.UTF8.GetBytes(@"[1,2]")); + int expected = 1; + + foreach (int i in result) + { + Assert.Equal(expected++, i); + } + + result = JsonSerializer.Deserialize>(Encoding.UTF8.GetBytes(@"[]")); + Assert.Equal(0, result.Count()); + } + + [Fact] + public static void ReadSimpleClassWithImmutableArray() + { + SimpleTestClassWithImmutableArray obj = JsonSerializer.Deserialize(SimpleTestClassWithImmutableArray.s_json); + obj.Verify(); + } + + [Fact] public static void ReadIImmutableListTOfIImmutableListT() { IImmutableList> result = JsonSerializer.Deserialize>>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); @@ -558,12 +626,6 @@ namespace System.Text.Json.Serialization.Tests } [Fact] - public static void ReadPrimitiveImmutableArrayThrows() - { - Assert.Throws(() => JsonSerializer.Deserialize>(Encoding.UTF8.GetBytes(@"[1,2]"))); - } - - [Fact] public static void ReadSimpleTestClass_ImmutableCollectionWrappers_Throws() { Assert.Throws(() => JsonSerializer.Deserialize(SimpleTestClassWithIImmutableDictionaryWrapper.s_json)); diff --git a/src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.ImmutableCollections.cs b/src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.ImmutableCollections.cs index cebb35b..ed02ffd 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.ImmutableCollections.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.ImmutableCollections.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.Immutable; +using System.Text.Json.Tests; using Xunit; namespace System.Text.Json.Serialization.Tests @@ -11,6 +12,51 @@ namespace System.Text.Json.Serialization.Tests public static partial class ValueTests { [Fact] + public static void WriteImmutableArrayOfImmutableArray() + { + ImmutableArray> input = ImmutableArray.CreateRange(new List>{ + ImmutableArray.CreateRange(new List() { 1, 2 }), + ImmutableArray.CreateRange(new List() { 3, 4 }) + }); + + string json = JsonSerializer.Serialize(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WriteImmutableArrayOfArray() + { + ImmutableArray input = ImmutableArray.CreateRange(new List + { + new int[] { 1, 2 }, + new int[] { 3, 4 } + }); + + string json = JsonSerializer.Serialize(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WriteArrayOfImmutableArray() + { + ImmutableArray[] input = new ImmutableArray[2]; + input[0] = ImmutableArray.CreateRange(new List() { 1, 2 }); + input[1] = ImmutableArray.CreateRange(new List() { 3, 4 }); + + string json = JsonSerializer.Serialize(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WriteSimpleImmutableArray() + { + ImmutableArray input = ImmutableArray.CreateRange(new List { 1, 2 }); + + string json = JsonSerializer.Serialize(input); + Assert.Equal("[1,2]", json); + } + + [Fact] public static void WriteIImmutableListTOfIImmutableListT() { IImmutableList> input = ImmutableList.CreateRange(new List>{ @@ -36,6 +82,24 @@ namespace System.Text.Json.Serialization.Tests } [Fact] + public static void WriteSimpleClassWithImmutableArray() + { + SimpleTestClassWithImmutableArray obj = new SimpleTestClassWithImmutableArray(); + obj.Initialize(); + + Assert.Equal(SimpleTestClassWithImmutableArray.s_json, JsonSerializer.Serialize(obj)); + } + + [Fact] + public static void WriteSimpleClassWithObjectImmutableArray() + { + SimpleTestClassWithObjectImmutableArray obj = new SimpleTestClassWithObjectImmutableArray(); + obj.Initialize(); + + Assert.Equal(SimpleTestClassWithObjectImmutableArray.s_json, JsonSerializer.Serialize(obj)); + } + + [Fact] public static void WriteArrayOfIImmutableListT() { IImmutableList[] input = new IImmutableList[2]; -- 2.7.4