Add support for immutable array (dotnet/corefx#39493)
authorLayomi Akinrinade <laakinri@microsoft.com>
Tue, 16 Jul 2019 12:53:06 +0000 (08:53 -0400)
committerGitHub <noreply@github.com>
Tue, 16 Jul 2019 12:53:06 +0000 (08:53 -0400)
* 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

19 files changed:
src/libraries/System.Text.Json/src/System.Text.Json.csproj
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableDictionaryConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultImmutableEnumerableConverter.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ImmutableCollectionCreator.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/MemberAccessor.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionEmitMemberAccessor.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReflectionMemberAccessor.cs
src/libraries/System.Text.Json/tests/Serialization/PolymorphicTests.cs
src/libraries/System.Text.Json/tests/Serialization/PropertyNameTests.cs
src/libraries/System.Text.Json/tests/Serialization/TestClasses.ImmutableCollections.cs
src/libraries/System.Text.Json/tests/Serialization/TestClasses.Polymorphic.cs
src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithObject.cs
src/libraries/System.Text.Json/tests/Serialization/TestClasses.cs
src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.ImmutableCollections.cs
src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.ImmutableCollections.cs

index b874c93..8d2f4dd 100644 (file)
@@ -84,6 +84,7 @@
     <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterUInt64.cs" />
     <Compile Include="System\Text\Json\Serialization\Converters\JsonValueConverterUri.cs" />
     <Compile Include="System\Text\Json\Serialization\ExtensionDataWriteStatus.cs" />
+    <Compile Include="System\Text\Json\Serialization\ImmutableCollectionCreator.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonAttribute.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonCamelCaseNamingPolicy.cs" />
     <Compile Include="System\Text\Json\Serialization\JsonClassInfo.cs" />
index 68644de..b6faa4f 100644 (file)
@@ -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);
index 869b4dd..a8c5210 100644 (file)
@@ -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 (file)
index 0000000..efdfb18
--- /dev/null
@@ -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<TElement, TCollection> : ImmutableCollectionCreator
+        where TCollection : IEnumerable<TElement>
+    {
+        private Func<IEnumerable<TElement>, TCollection> _creatorDelegate;
+
+        public override void RegisterCreatorDelegateFromMethod(MethodInfo creator)
+        {
+            Debug.Assert(_creatorDelegate == null);
+            _creatorDelegate = (Func<IEnumerable<TElement>, TCollection>)creator.CreateDelegate(typeof(Func<IEnumerable<TElement>, 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<TElement> CreateGenericTElementIEnumerable(IList sourceList)
+        {
+            foreach (object item in sourceList)
+            {
+                yield return (TElement)item;
+            }
+        }
+    }
+
+    internal sealed class ImmutableDictionaryCreator<TElement, TCollection> : ImmutableCollectionCreator
+        where TCollection : IReadOnlyDictionary<string, TElement>
+    {
+        private Func<IEnumerable<KeyValuePair<string, TElement>>, TCollection> _creatorDelegate;
+
+        public override void RegisterCreatorDelegateFromMethod(MethodInfo creator)
+        {
+            Debug.Assert(_creatorDelegate == null);
+            _creatorDelegate = (Func<IEnumerable<KeyValuePair<string, TElement>>, TCollection>)creator.CreateDelegate(
+                typeof(Func<IEnumerable<KeyValuePair<string, TElement>>, 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<KeyValuePair<string, TElement>> CreateGenericTElementIDictionary(IDictionary sourceDictionary)
+        {
+            foreach (DictionaryEntry item in sourceDictionary)
+            {
+                yield return new KeyValuePair<string, TElement>((string)item.Key, (TElement)item.Value);
+            }
+        }
+    }
+}
index 56d7bd7..22bda60 100644 (file)
@@ -250,15 +250,15 @@ namespace System.Text.Json
         // CreateRange<TRuntimePropertyType> 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<TRuntimeProperty> createRangeDelegate = (
-                (JsonSerializerOptions.ImmutableCreateRangeDelegate<TRuntimeProperty>)createRangeDelegateObj);
-
-            return (IEnumerable)createRangeDelegate.Invoke(CreateGenericTRuntimePropertyIEnumerable(sourceList));
+            return collection;
         }
 
         // Creates an IEnumerable<TRuntimePropertyType> and populates it with the items in the
@@ -266,15 +266,15 @@ namespace System.Text.Json
         // CreateRange<TRuntimePropertyType> 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<string, TRuntimeProperty> createRangeDelegate = (
-                (JsonSerializerOptions.ImmutableDictCreateRangeDelegate<string, TRuntimeProperty>)createRangeDelegateObj);
-
-            return (IDictionary)createRangeDelegate.Invoke(CreateGenericIEnumerableFromDictionary(sourceDictionary));
+            return collection;
         }
 
         private IEnumerable<TRuntimeProperty> CreateGenericTRuntimePropertyIEnumerable(IList sourceList)
index 7c5d335..148c5ec 100644 (file)
@@ -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<T> 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);
                     }
index c3e3a74..fa679aa 100644 (file)
@@ -20,7 +20,7 @@ namespace System.Text.Json
 
         private readonly ConcurrentDictionary<Type, JsonClassInfo> _classes = new ConcurrentDictionary<Type, JsonClassInfo>();
         private readonly ConcurrentDictionary<Type, JsonPropertyInfo> _objectJsonProperties = new ConcurrentDictionary<Type, JsonPropertyInfo>();
-        private static ConcurrentDictionary<string, object> s_createRangeDelegates = new ConcurrentDictionary<string, object>();
+        private static ConcurrentDictionary<string, ImmutableCollectionCreator> s_createRangeDelegates = new ConcurrentDictionary<string, ImmutableCollectionCreator>();
         private MemberAccessor _memberAccessorStrategy;
         private JsonNamingPolicy _dictionayKeyPolicy;
         private JsonNamingPolicy _jsonPropertyNamingPolicy;
@@ -331,9 +331,6 @@ namespace System.Text.Json
             };
         }
 
-        internal delegate object ImmutableCreateRangeDelegate<T>(IEnumerable<T> items);
-        internal delegate object ImmutableDictCreateRangeDelegate<TKey, TValue>(IEnumerable<KeyValuePair<TKey, TValue>> 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);
         }
index 166713e..bb513ac 100644 (file)
@@ -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)
         {
index f795e62..885dbec 100644 (file)
@@ -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);
                 }
index f41f7cb..27e9812 100644 (file)
@@ -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<object, TProperty> CreatePropertyGetter<TClass, TProperty>(PropertyInfo propertyInfo) =>
index d5afac2..9e73e49 100644 (file)
@@ -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<object, TProperty> CreatePropertyGetter<TClass, TProperty>(PropertyInfo propertyInfo)
index 6ef38d7..6266c72 100644 (file)
@@ -191,6 +191,13 @@ namespace System.Text.Json.Serialization.Tests
             json = JsonSerializer.Serialize<object>(linkedlist);
             Assert.Equal(ExpectedJson, json);
 
+            ImmutableArray<object> immutablearray = ImmutableArray.CreateRange(new List<object> { 1, true, address, null, "foo" });
+            json = JsonSerializer.Serialize(immutablearray);
+            Assert.Equal(ExpectedJson, json);
+
+            json = JsonSerializer.Serialize<object>(immutablearray);
+            Assert.Equal(ExpectedJson, json);
+
             IImmutableList<object> iimmutablelist = ImmutableList.CreateRange(new List<object> { 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);
index 0afe52f..539c162 100644 (file)
@@ -40,6 +40,7 @@ namespace System.Text.Json.Serialization.Tests
             options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
 
             SimpleTestClass obj = JsonSerializer.Deserialize<SimpleTestClass>(@"{}", options);
+
             string json = JsonSerializer.Serialize(obj, options);
             Assert.Contains(@"""myInt16"":0", json);
             Assert.Contains(@"""myInt32"":0", json);
index e9e2eda..95bc12b 100644 (file)
@@ -10,6 +10,40 @@ using Xunit;
 
 namespace System.Text.Json.Serialization.Tests
 {
+    public class SimpleTestClassWithImmutableArray : ITestClass
+    {
+        public ImmutableArray<string> MyStringImmutableArray { get; set; }
+
+        public static readonly string s_json = @"{""MyStringImmutableArray"":[""Hello""]}";
+
+        public void Initialize()
+        {
+            MyStringImmutableArray = ImmutableArray.CreateRange(new List<string> { "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<string> { "Hello" });
+        }
+
+        public void Verify()
+        {
+            Assert.Equal("Hello", ((ImmutableArray<string>)MyStringImmutableArray)[0]);
+        }
+    }
+
     public class SimpleTestClassWithIImmutableDictionaryWrapper
     {
         public StringToStringIImmutableDictionaryWrapper MyStringToStringImmutableDictionaryWrapper { get; set; }
index 7ad0c5a..8e28a81 100644 (file)
@@ -142,6 +142,7 @@ namespace System.Text.Json.Serialization.Tests
         public object /*HashSet<string>*/ HashSetT { get; set; }
         public object /*LinkedList<string>*/ LinkedListT { get; set; }
         public object /*SortedSet<string>*/ SortedSetT { get; set; }
+        public object /*ImmutableArray<string>*/ ImmutableArrayT { get; set; }
         public object /*IImmutableList<string>*/ IImmutableListT { get; set; }
         public object /*IImmutableStack<string>*/ IImmutableStackT { get; set; }
         public object /*IImmutableQueue<string>*/ IImmutableQueueT { get; set; }
@@ -176,6 +177,7 @@ namespace System.Text.Json.Serialization.Tests
             HashSetT = new HashSet<string>(new List<string> { "Hello", "World" });
             LinkedListT = new LinkedList<string>(new List<string> { "Hello", "World" });
             SortedSetT = new SortedSet<string>(new List<string> { "Hello", "World" });
+            ImmutableArrayT = ImmutableArray.CreateRange(new List<string> { "Hello", "World" });
             IImmutableListT = ImmutableList.CreateRange(new List<string> { "Hello", "World" });
             IImmutableStackT = ImmutableStack.CreateRange(new List<string> { "Hello", "World" });
             IImmutableQueueT = ImmutableQueue.CreateRange(new List<string> { "Hello", "World" });
index dc21f4f..32cc3ad 100644 (file)
@@ -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; }
index 34ba24c..41b5490 100644 (file)
@@ -1339,6 +1339,7 @@ namespace System.Text.Json.Serialization.Tests
 
     public class TestClassWithObjectImmutableTypes : ITestClass
     {
+        public ImmutableArray<SimpleTestClass> MyImmutableArray { get; set; }
         public IImmutableList<SimpleTestClass> MyIImmutableList { get; set; }
         public IImmutableStack<SimpleTestClass> MyIImmutableStack { get; set; }
         public IImmutableQueue<SimpleTestClass> 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<SimpleTestClass> { obj1, obj2 });
+            }
+            {
+                SimpleTestClass obj1 = new SimpleTestClass();
+                obj1.Initialize();
+
+                SimpleTestClass obj2 = new SimpleTestClass();
+                obj2.Initialize();
+
                 MyIImmutableList = ImmutableList.CreateRange(new List<SimpleTestClass> { 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)
             {
index a6e1dd2..e16678d 100644 (file)
@@ -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<ImmutableArray<int>> result = JsonSerializer.Deserialize<ImmutableArray<ImmutableArray<int>>>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+            int expected = 1;
+
+            foreach (ImmutableArray<int> l in result)
+            {
+                foreach (int i in l)
+                {
+                    Assert.Equal(expected++, i);
+                }
+            }
+        }
+
+        [Fact]
+        public static void ReadImmutableArrayOfArray()
+        {
+            ImmutableArray<int[]> result = JsonSerializer.Deserialize<ImmutableArray<int[]>>(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<int>[] result = JsonSerializer.Deserialize<ImmutableArray<int>[]>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
+            int expected = 1;
+
+            foreach (ImmutableArray<int> l in result)
+            {
+                foreach (int i in l)
+                {
+                    Assert.Equal(expected++, i);
+                }
+            }
+        }
+
+        [Fact]
+        public static void ReadSimpleImmutableArray()
+        {
+            ImmutableArray<int> result = JsonSerializer.Deserialize<ImmutableArray<int>>(Encoding.UTF8.GetBytes(@"[1,2]"));
+            int expected = 1;
+
+            foreach (int i in result)
+            {
+                Assert.Equal(expected++, i);
+            }
+
+            result = JsonSerializer.Deserialize<ImmutableArray<int>>(Encoding.UTF8.GetBytes(@"[]"));
+            Assert.Equal(0, result.Count());
+        }
+
+        [Fact]
+        public static void ReadSimpleClassWithImmutableArray()
+        {
+            SimpleTestClassWithImmutableArray obj = JsonSerializer.Deserialize<SimpleTestClassWithImmutableArray>(SimpleTestClassWithImmutableArray.s_json);
+            obj.Verify();
+        }
+
+        [Fact]
         public static void ReadIImmutableListTOfIImmutableListT()
         {
             IImmutableList<IImmutableList<int>> result = JsonSerializer.Deserialize<IImmutableList<IImmutableList<int>>>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]"));
@@ -558,12 +626,6 @@ namespace System.Text.Json.Serialization.Tests
         }
 
         [Fact]
-        public static void ReadPrimitiveImmutableArrayThrows()
-        {
-            Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<ImmutableArray<int>>(Encoding.UTF8.GetBytes(@"[1,2]")));
-        }
-
-        [Fact]
         public static void ReadSimpleTestClass_ImmutableCollectionWrappers_Throws()
         {
             Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<SimpleTestClassWithIImmutableDictionaryWrapper>(SimpleTestClassWithIImmutableDictionaryWrapper.s_json));
index cebb35b..ed02ffd 100644 (file)
@@ -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<ImmutableArray<int>> input = ImmutableArray.CreateRange(new List<ImmutableArray<int>>{
+                ImmutableArray.CreateRange(new List<int>() { 1, 2 }),
+                ImmutableArray.CreateRange(new List<int>() { 3, 4 })
+            });
+
+            string json = JsonSerializer.Serialize(input);
+            Assert.Equal("[[1,2],[3,4]]", json);
+        }
+
+        [Fact]
+        public static void WriteImmutableArrayOfArray()
+        {
+            ImmutableArray<int[]> input = ImmutableArray.CreateRange(new List<int[]>
+            {
+                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<int>[] input = new ImmutableArray<int>[2];
+            input[0] = ImmutableArray.CreateRange(new List<int>() { 1, 2 });
+            input[1] = ImmutableArray.CreateRange(new List<int>() { 3, 4 });
+
+            string json = JsonSerializer.Serialize(input);
+            Assert.Equal("[[1,2],[3,4]]", json);
+        }
+
+        [Fact]
+        public static void WriteSimpleImmutableArray()
+        {
+            ImmutableArray<int> input = ImmutableArray.CreateRange(new List<int> { 1, 2 });
+
+            string json = JsonSerializer.Serialize(input);
+            Assert.Equal("[1,2]", json);
+        }
+
+        [Fact]
         public static void WriteIImmutableListTOfIImmutableListT()
         {
             IImmutableList<IImmutableList<int>> input = ImmutableList.CreateRange(new List<IImmutableList<int>>{
@@ -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<int>[] input = new IImmutableList<int>[2];