Refactor null handling and add tests (dotnet/corefx#37300)
authorSteve Harter <steveharter@users.noreply.github.com>
Tue, 7 May 2019 15:53:25 +0000 (08:53 -0700)
committerGitHub <noreply@github.com>
Tue, 7 May 2019 15:53:25 +0000 (08:53 -0700)
Commit migrated from https://github.com/dotnet/corefx/commit/f5ed5a82d8ae626bd756c225a25bf432b92ec472

23 files changed:
src/libraries/System.Text.Json/src/Resources/Strings.resx
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.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/JsonSerializer.Read.HandleNull.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs
src/libraries/System.Text.Json/tests/Serialization/Array.ReadTests.cs
src/libraries/System.Text.Json/tests/Serialization/DictionaryTests.cs
src/libraries/System.Text.Json/tests/Serialization/Null.ReadTests.cs
src/libraries/System.Text.Json/tests/Serialization/Null.WriteTests.cs
src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClass.cs
src/libraries/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithNullables.cs
src/libraries/System.Text.Json/tests/Serialization/TestClasses.cs
src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.cs
src/libraries/System.Text.Json/tests/Serialization/Value.WriteTests.cs

index c2a1da63c591d0347a9eca5e324702efeefd38e0..f2f72cbc78fa98a7584a9633967d68fef0a67cc6 100644 (file)
     <value>Expected depth to be zero at the end of the JSON payload. There is an open JSON object or array that should be closed.</value>
   </data>
   <data name="DeserializeCannotBeNull" xml:space="preserve">
-    <value>The JSON value from {0} cannot be null.</value>
+    <value>The JSON value cannot be null.</value>
   </data>
   <data name="DeserializeDataRemaining" xml:space="preserve">
     <value>The provided data of length {0} has remaining bytes {1}.</value>
index 08e1d9edc16ac069f4acbf88b18d561f5e909079..8eaebc9f33d3edd83489bf3c17a3e827d5306800 100644 (file)
@@ -28,10 +28,14 @@ namespace System.Text.Json.Serialization
             // Convert interfaces to concrete types.
             if (propertyType.IsInterface && jsonInfo.ClassType == ClassType.Dictionary)
             {
-                Type newPropertyType = jsonInfo.ElementClassInfo.GetPolicyProperty().GetConcreteType(propertyType);
-                if (propertyType != newPropertyType)
+                // If a polymorphic case, we have to wait until run-time values are processed.
+                if (jsonInfo.ElementClassInfo.ClassType != ClassType.Unknown)
                 {
-                    jsonInfo = CreateProperty(propertyType, newPropertyType, propertyInfo, classType, options);
+                    Type newPropertyType = jsonInfo.ElementClassInfo.GetPolicyProperty().GetDictionaryConcreteType();
+                    if (propertyType != newPropertyType)
+                    {
+                        jsonInfo = CreateProperty(propertyType, newPropertyType, propertyInfo, classType, options);
+                    }
                 }
             }
 
index 5bcb5cc2ada0db854a091d1ca906e06532cd71df..769e5e220d0da4b99a835ddfe3f5f98668b0e4be 100644 (file)
@@ -324,9 +324,8 @@ namespace System.Text.Json.Serialization
                         {
                             elementType = args[1];
                         }
-                        else if (args.Length >= 1) // It is >= 1 in case there is an IEnumerable<T, TSomeExtension>.
+                        else if (GetClassType(propertyType) == ClassType.Enumerable && args.Length >= 1) // It is >= 1 in case there is an IEnumerable<T, TSomeExtension>.
                         {
-                            Debug.Assert(GetClassType(propertyType) == ClassType.Enumerable);
                             elementType = args[0];
                         }
                     }
index 3849ba413ded9b9bd58d2fb4cb7872ce2a54bbf1..3702b018dca525060111498d6430a4e6f468e0d9 100644 (file)
@@ -259,11 +259,9 @@ namespace System.Text.Json.Serialization
             return (TAttribute)PropertyInfo?.GetCustomAttribute(typeof(TAttribute), inherit: false);
         }
 
-        public abstract void ApplyNullValue(JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader);
-
         public abstract IList CreateConverterList();
 
-        public abstract Type GetConcreteType(Type interfaceType);
+        public abstract Type GetDictionaryConcreteType();
 
         public abstract void Read(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader);
         public abstract void ReadEnumerable(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader);
@@ -271,7 +269,7 @@ namespace System.Text.Json.Serialization
 
         public abstract void Write(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer);
 
-        public abstract void WriteDictionary(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer);
+        public virtual void WriteDictionary(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer) { }
         public abstract void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer);
     }
 }
index 12d1e3aa8c18e7173e309fd50ade64cfc4c34667..50f22bd92e68b5d9f6fb959ce8b9268d5626044b 100644 (file)
@@ -92,16 +92,9 @@ namespace System.Text.Json.Serialization
             return new List<TDeclaredProperty>();
         }
 
-        // Map interfaces to a well-known implementation.
-        public override Type GetConcreteType(Type interfaceType)
+        public override Type GetDictionaryConcreteType()
         {
-            if (interfaceType.IsAssignableFrom(typeof(IDictionary<string, TRuntimeProperty>)) ||
-                interfaceType.IsAssignableFrom(typeof(IReadOnlyDictionary<string, TRuntimeProperty>)))
-            {
-                return typeof(Dictionary<string, TRuntimeProperty>);
-            }
-
-            return interfaceType;
+            return typeof(Dictionary<string, TRuntimeProperty>);
         }
     }
 }
index 9c092163759f16a97eee1265d224148ca0ddba05..9c95b56bb06003f6d087db14919db437a018f33f 100644 (file)
@@ -75,22 +75,11 @@ namespace System.Text.Json.Serialization
             JsonSerializer.ApplyValueToEnumerable(ref value, options, ref state, ref reader);
         }
 
-        public override void ApplyNullValue(JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader)
-        {
-            Debug.Assert(state.Current.JsonPropertyInfo != null);
-            state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, value : null);
-        }
-
-        // todo: have the caller check if current.Enumerator != null and call WriteEnumerable of the underlying property directly to avoid an extra virtual call.
         public override void Write(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer)
         {
-            if (current.Enumerator != null)
-            {
-                // Forward the setter to the value-based JsonPropertyInfo.
-                JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
-                propertyInfo.WriteEnumerable(options, ref current, writer);
-            }
-            else if (ShouldSerialize)
+            Debug.Assert(current.Enumerator == null);
+
+            if (ShouldSerialize)
             {
                 TRuntimeProperty value;
                 if (_isPropertyPolicy)
@@ -104,11 +93,9 @@ namespace System.Text.Json.Serialization
 
                 if (value == null)
                 {
-                    if (_escapedName == null)
-                    {
-                        writer.WriteNullValue();
-                    }
-                    else if (!IgnoreNullValues)
+                    Debug.Assert(_escapedName != null);
+
+                    if (!IgnoreNullValues)
                     {
                         writer.WriteNull(_escapedName);
                     }
@@ -132,7 +119,6 @@ namespace System.Text.Json.Serialization
             JsonSerializer.WriteDictionary(ValueConverter, options, ref current, writer);
         }
 
-
         public override void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer)
         {
             if (ValueConverter != null)
index 5a3ce959a90cf389c2291769258567fbb7ba16eb..e276e2942d6d15953f1aef043e5a41bef3acb047 100644 (file)
@@ -31,13 +31,9 @@ namespace System.Text.Json.Serialization
 
         public override void Read(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader)
         {
-            if (ElementClassInfo != null)
-            {
-                // Forward the setter to the value-based JsonPropertyInfo.
-                JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty();
-                propertyInfo.ReadEnumerable(tokenType, options, ref state, ref reader);
-            }
-            else if (ShouldDeserialize)
+            Debug.Assert(ElementClassInfo == null);
+
+            if (ShouldDeserialize)
             {
                 if (ValueConverter != null)
                 {
@@ -68,18 +64,10 @@ namespace System.Text.Json.Serialization
                 return;
             }
 
-            // Converting to TProperty? here lets us share a common ApplyValue() with ApplyNullValue().
             TProperty? nullableValue = new TProperty?(value);
             JsonSerializer.ApplyValueToEnumerable(ref nullableValue, options, ref state, ref reader);
         }
 
-        public override void ApplyNullValue(JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader)
-        {
-            TProperty? nullableValue = null;
-            JsonSerializer.ApplyValueToEnumerable(ref nullableValue, options, ref state, ref reader);
-        }
-
-        // todo: have the caller check if current.Enumerator != null and call WriteEnumerable of the underlying property directly to avoid an extra virtual call.
         public override void Write(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer)
         {
             if (current.Enumerator != null)
@@ -102,11 +90,9 @@ namespace System.Text.Json.Serialization
 
                 if (value == null)
                 {
-                    if (_escapedName == null)
-                    {
-                        writer.WriteNullValue();
-                    }
-                    else if (!IgnoreNullValues)
+                    Debug.Assert(_escapedName != null);
+
+                    if (!IgnoreNullValues)
                     {
                         writer.WriteNull(_escapedName);
                     }
@@ -125,11 +111,6 @@ namespace System.Text.Json.Serialization
             }
         }
 
-        public override void WriteDictionary(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer)
-        {
-            JsonSerializer.WriteDictionary(ValueConverter, options, ref current, writer);
-        }
-
         public override void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer)
         {
             if (ValueConverter != null)
index d80b17330fafd1f0bb2dcd1a48ae713c51cca5dc..a19e02f4f2b88886c5ddb1899a3669defccffb4a 100644 (file)
@@ -98,14 +98,9 @@ namespace System.Text.Json.Serialization
             }
 
             IEnumerable value = ReadStackFrame.GetEnumerableValue(state.Current);
-            if (value == null)
-            {
-                // We added the items to the list property already.
-                state.Current.ResetProperty();
-                return false;
-            }
-
             bool setPropertyDirectly;
+            bool popStackOnEnd = state.Current.PopStackOnEnd;
+
             if (state.Current.TempEnumerableValues != null)
             {
                 JsonEnumerableConverter converter = state.Current.JsonPropertyInfo.EnumerableConverter;
@@ -115,13 +110,20 @@ namespace System.Text.Json.Serialization
                 value = converter.CreateFromList(elementType, (IList)value);
                 setPropertyDirectly = true;
             }
+            else if (!popStackOnEnd)
+            {
+                Debug.Assert(state.Current.IsPropertyEnumerable);
+
+                // We added the items to the list property already.
+                state.Current.ResetProperty();
+                return false;
+            }
             else
             {
                 setPropertyDirectly = false;
             }
 
-            bool valueReturning = state.Current.PopStackOnEnd;
-            if (state.Current.PopStackOnEnd)
+            if (popStackOnEnd)
             {
                 state.Pop();
             }
@@ -145,8 +147,9 @@ namespace System.Text.Json.Serialization
 
             ApplyObjectToEnumerable(value, options, ref state, ref reader, setPropertyDirectly: setPropertyDirectly);
 
-            if (!valueReturning)
+            if (!popStackOnEnd)
             {
+                Debug.Assert(state.Current.IsPropertyEnumerable);
                 state.Current.ResetProperty();
             }
 
@@ -182,7 +185,9 @@ namespace System.Text.Json.Serialization
                 }
                 else
                 {
-                    ((IList)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue)).Add(value);
+                    IList list = (IList)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
+                    Debug.Assert(list != null);
+                    list.Add(value);
                 }
             }
             else if (state.Current.IsDictionary)
@@ -235,7 +240,9 @@ namespace System.Text.Json.Serialization
                 }
                 else
                 {
-                    ((IList<TProperty>)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue)).Add(value);
+                    IList<TProperty> list = (IList<TProperty>)state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue);
+                    Debug.Assert(list != null);
+                    list.Add(value);
                 }
             }
             else if (state.Current.IsDictionary)
index 028c634242b97152c9bc21a2cd27cc96ddc2be38..80abfc4a631c8039f78df2e15b6ef6b0a77a953e 100644 (file)
@@ -38,7 +38,8 @@ namespace System.Text.Json.Serialization
 
             if (state.Current.IsPropertyEnumerable)
             {
-                state.Current.JsonPropertyInfo.ApplyNullValue(options, ref state, ref reader);
+                bool setPropertyToNull = !state.Current.EnumerableCreated;
+                ApplyObjectToEnumerable(null, options, ref state, ref reader, setPropertyDirectly: setPropertyToNull);
                 return false;
             }
 
index cd7e02dd6575c86846318e3d0c62c5815e46cea7..425c4d78f65dfaaf3d599adb9551566da4144d83 100644 (file)
@@ -28,14 +28,12 @@ namespace System.Text.Json.Serialization
                 if (state.Current.IsDictionary)
                 {
                     // Verify that the Dictionary can be deserialized by having <string> as first generic argument.
-                    Debug.Assert(state.Current.JsonClassInfo.Type.GetGenericArguments().Length >= 1);
-                    if (state.Current.JsonClassInfo.Type.GetGenericArguments()[0].UnderlyingSystemType != typeof(string))
+                    Type[] args = state.Current.JsonClassInfo.Type.GetGenericArguments();
+                    if (args.Length == 0 || args[0].UnderlyingSystemType != typeof(string))
                     {
                         ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(state.Current.JsonClassInfo.Type, reader, state.PropertyPath);
                     }
 
-                    ClassType classType = state.Current.JsonClassInfo.ElementClassInfo.ClassType;
-
                     if (state.Current.ReturnValue == null)
                     {
                         // The Dictionary created below will be returned to corresponding Parse() etc method.
@@ -44,9 +42,15 @@ namespace System.Text.Json.Serialization
                     }
                     else
                     {
-                        Debug.Assert(classType == ClassType.Object || classType == ClassType.Dictionary);
+                        ClassType classType = state.Current.JsonClassInfo.ElementClassInfo.ClassType;
+
+                        // Verify that the second parameter is not a value.
+                        if (state.Current.JsonClassInfo.ElementClassInfo.ClassType == ClassType.Value)
+                        {
+                            ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(state.Current.JsonClassInfo.Type, reader, state.PropertyPath);
+                        }
 
-                        // A nested object or dictionary.
+                        // A nested object, dictionary or enumerable.
                         JsonClassInfo classInfoTemp = state.Current.JsonClassInfo;
                         state.Push();
                         state.Current.JsonClassInfo = classInfoTemp.ElementClassInfo;
index bbba76969f0c8cb463bdc33e94880f4eb170f5e5..b816cae4fb6e58341272e9a83849711b8f8ee4df 100644 (file)
@@ -27,8 +27,14 @@ namespace System.Text.Json.Serialization
 
             if (state.Current.Enumerator == null)
             {
-                IEnumerable enumerable = (IEnumerable)jsonPropertyInfo.GetValueAsObject(state.Current.CurrentValue);
+                // Verify that the Dictionary can be serialized by having <string> as first generic argument.
+                Type[] args = jsonPropertyInfo.RuntimePropertyType.GetGenericArguments();
+                if (args.Length == 0 || args[0].UnderlyingSystemType != typeof(string))
+                {
+                    ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(state.Current.JsonClassInfo.Type, state.PropertyPath);
+                }
 
+                IEnumerable enumerable = (IEnumerable)jsonPropertyInfo.GetValueAsObject(state.Current.CurrentValue);
                 if (enumerable == null)
                 {
                     // Write a null object or enumerable.
index 74d0ca5bd97454ddd5804cff6c8138c37fa1f3bb..6f5109780f8cb8c186acfa1c212ca14cb7ef80d0 100644 (file)
@@ -30,8 +30,12 @@ namespace System.Text.Json.Serialization
 
                 if (enumerable == null)
                 {
-                    // Write a null object or enumerable.
-                    state.Current.WriteObjectOrArrayStart(ClassType.Enumerable, writer, writeNull: true);
+                    if (!state.Current.JsonPropertyInfo.IgnoreNullValues)
+                    {
+                        // Write a null object or enumerable.
+                        state.Current.WriteObjectOrArrayStart(ClassType.Enumerable, writer, writeNull: true);
+                    }
+
                     return true;
                 }
 
index 5cc3453eeec0102bccf62d579755291dab5e42ff..9f29f9700e5911b6fb46c82fc95258affb8d97c2 100644 (file)
@@ -63,5 +63,42 @@ namespace System.Text.Json.Serialization
             Debug.Assert(_index > 0);
             Current = _previous[--_index];
         }
+
+        // Return a property path in the form of: [FullNameOfType].FirstProperty.SecondProperty.LastProperty
+        public string PropertyPath
+        {
+            get
+            {
+                StringBuilder path = new StringBuilder();
+
+                if (_previous == null || _index == 0)
+                {
+                    path.Append($"[{Current.JsonClassInfo.Type.FullName}]");
+                }
+                else
+                {
+                    path.Append($"[{_previous[0].JsonClassInfo.Type.FullName}]");
+
+                    for (int i = 0; i < _index; i++)
+                    {
+                        path.Append(GetPropertyName(_previous[i]));
+                    }
+                }
+
+                path.Append(GetPropertyName(Current));
+
+                return path.ToString();
+            }
+        }
+
+        private string GetPropertyName(in WriteStackFrame frame)
+        {
+            if (frame.JsonPropertyInfo != null && frame.JsonClassInfo.ClassType == ClassType.Object)
+            {
+                return $".{frame.JsonPropertyInfo.PropertyInfo.Name}";
+            }
+
+            return string.Empty;
+        }
     }
 }
index c46ebbdfaf18b4d3b8213b7bc064c37e890ed464..6bf5b59ab68e8c04c3633725a14810042879fe65 100644 (file)
@@ -3,7 +3,6 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Diagnostics;
-using System.Reflection;
 using System.Runtime.CompilerServices;
 using System.Text.Json.Serialization;
 
@@ -24,15 +23,16 @@ namespace System.Text.Json
         }
 
         [MethodImpl(MethodImplOptions.NoInlining)]
-        public static void ThrowJsonException_DeserializeCannotBeNull(in Utf8JsonReader reader, string path)
+        public static void ThrowJsonException_DeserializeUnableToConvertValue(Type propertyType, string path)
         {
-            ThowJsonException(SR.DeserializeCannotBeNull, in reader, path);
+            string message = SR.Format(SR.DeserializeUnableToConvertValue, propertyType.FullName) + $" Path: {path}.";
+            throw new JsonException(message, path, null, null);
         }
 
         [MethodImpl(MethodImplOptions.NoInlining)]
-        public static void ThrowObjectDisposedException(string name)
+        public static void ThrowJsonException_DeserializeCannotBeNull(in Utf8JsonReader reader, string path)
         {
-            throw new ObjectDisposedException(name);
+            ThowJsonException(SR.DeserializeCannotBeNull, in reader, path);
         }
 
         [MethodImpl(MethodImplOptions.NoInlining)]
index b60697653b4d1f63517ce49901f52a2d4e226ae6..85fb64722025ec065354d682fbee0ec822519d8c 100644 (file)
@@ -19,6 +19,51 @@ namespace System.Text.Json.Serialization.Tests
             Assert.Equal(0, list.Count);
         }
 
+
+        public static IEnumerable<object[]> ReadNullJson
+        {
+            get
+            {
+                yield return new object[] { $"[null, null, null]", true, true, true };
+                yield return new object[] { $"[null, null, {SimpleTestClass.s_json}]", true, true, false };
+                yield return new object[] { $"[null, {SimpleTestClass.s_json}, null]", true, false, true };
+                yield return new object[] { $"[null, {SimpleTestClass.s_json}, {SimpleTestClass.s_json}]", true, false, false };
+                yield return new object[] { $"[{SimpleTestClass.s_json}, {SimpleTestClass.s_json}, {SimpleTestClass.s_json}]", false, false, false };
+                yield return new object[] { $"[{SimpleTestClass.s_json}, {SimpleTestClass.s_json}, null]", false, false, true };
+                yield return new object[] { $"[{SimpleTestClass.s_json}, null, {SimpleTestClass.s_json}]", false, true, false };
+                yield return new object[] { $"[{SimpleTestClass.s_json}, null, null]", false, true, true };
+            }
+        }
+
+        private static void VerifyReadNull(SimpleTestClass obj, bool isNull)
+        {
+            if (isNull)
+            {
+                Assert.Null(obj);
+            }
+            else
+            {
+                obj.Verify();
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(ReadNullJson))]
+        public static void ReadNull(string json, bool element0Null, bool element1Null, bool element2Null)
+        {
+            SimpleTestClass[] arr = JsonSerializer.Parse<SimpleTestClass[]>(json);
+            Assert.Equal(3, arr.Length);
+            VerifyReadNull(arr[0], element0Null);
+            VerifyReadNull(arr[1], element1Null);
+            VerifyReadNull(arr[2], element2Null);
+
+            List<SimpleTestClass> list = JsonSerializer.Parse<List<SimpleTestClass>>(json);
+            Assert.Equal(3, list.Count);
+            VerifyReadNull(list[0], element0Null);
+            VerifyReadNull(list[1], element1Null);
+            VerifyReadNull(list[2], element2Null);
+        }
+
         [Fact]
         public static void ReadClassWithStringArray()
         {
index eb232cba779a5ffd98d94a17658c71fb0ed89644..b0386a2c3911c66d29e6db96ce98df256b5f0f19 100644 (file)
@@ -2,6 +2,7 @@
 // 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 Xunit;
 
@@ -266,5 +267,45 @@ namespace System.Text.Json.Serialization.Tests
                 Assert.Equal(1, obj[longPropertyName]);
             }
         }
+
+        [Fact]
+        public static void ObjectToStringFail()
+        {
+            string json = @"{""MyDictionary"":{""Key"":""Value""}}";
+            Assert.Throws<JsonException>(() => JsonSerializer.Parse<Dictionary<string, string>>(json));
+        }
+
+        [Fact]
+        public static void HashtableFail()
+        {
+            {
+                string json = @"{""Key"":""Value""}";
+
+                // Verify we can deserialize into Dictionary<,>
+                JsonSerializer.Parse<Dictionary<string, string>>(json);
+
+                // We don't support non-generic IDictionary
+                Assert.Throws<JsonException>(() => JsonSerializer.Parse<Hashtable>(json));
+            }
+
+            {
+                Hashtable ht = new Hashtable();
+                ht.Add("Key", "Value");
+                Assert.Throws<JsonException>(() => JsonSerializer.ToString(ht));
+            }
+
+            {
+                string json = @"{""Key"":""Value""}";
+
+                // We don't support non-generic IDictionary
+                Assert.Throws<JsonException>(() => JsonSerializer.Parse<IDictionary>(json));
+            }
+
+            {
+                IDictionary ht = new Hashtable();
+                ht.Add("Key", "Value");
+                Assert.Throws<JsonException>(() => JsonSerializer.ToString(ht));
+            }
+        }
     }
 }
index 13bb1d5335aada9d662281b9b077c8ce6b56d604..c61dab68fb67c4f3da512a4c6cdb3b1a78157603 100644 (file)
@@ -35,6 +35,8 @@ namespace System.Text.Json.Serialization.Tests
             TestClassWithInitializedProperties obj = JsonSerializer.Parse<TestClassWithInitializedProperties>(TestClassWithInitializedProperties.s_null_json);
             Assert.Equal(null, obj.MyString);
             Assert.Equal(null, obj.MyInt);
+            Assert.Equal(null, obj.MyIntArray);
+            Assert.Equal(null, obj.MyIntList);
         }
 
         [Fact]
@@ -46,6 +48,8 @@ namespace System.Text.Json.Serialization.Tests
             TestClassWithInitializedProperties obj = JsonSerializer.Parse<TestClassWithInitializedProperties>(TestClassWithInitializedProperties.s_null_json, options);
             Assert.Equal("Hello", obj.MyString);
             Assert.Equal(1, obj.MyInt);
+            Assert.Equal(1, obj.MyIntArray[0]);
+            Assert.Equal(1, obj.MyIntList[0]);
         }
 
         [Fact]
index 8e72f1e5c6a858e5db69692db0088860c28b12fe..20b1925e9986ad27803a67397a29dd7ab838548d 100644 (file)
@@ -14,10 +14,14 @@ namespace System.Text.Json.Serialization.Tests
             var obj = new TestClassWithInitializedProperties();
             obj.MyString = null;
             obj.MyInt = null;
+            obj.MyIntArray = null;
+            obj.MyIntList = null;
 
             string json = JsonSerializer.ToString(obj);
             Assert.Contains(@"""MyString"":null", json);
             Assert.Contains(@"""MyInt"":null", json);
+            Assert.Contains(@"""MyIntArray"":null", json);
+            Assert.Contains(@"""MyIntList"":null", json);
         }
 
         [Fact]
@@ -29,6 +33,8 @@ namespace System.Text.Json.Serialization.Tests
             var obj = new TestClassWithInitializedProperties();
             obj.MyString = null;
             obj.MyInt = null;
+            obj.MyIntArray = null;
+            obj.MyIntList = null;
 
             string json = JsonSerializer.ToString(obj, options);
             Assert.Equal(@"{}", json);
index d5ed0cb6bfd77ff03b26dd299556175970a64bc6..d56864440eb3874b298e5d5d1d7ccefbf8399af3 100644 (file)
@@ -55,6 +55,7 @@ namespace System.Text.Json.Serialization.Tests
         public Dictionary<string, string> MyStringToStringDict { get; set; }
         public IDictionary<string, string> MyStringToStringIDict { get; set; }
         public IReadOnlyDictionary<string, string> MyStringToStringIReadOnlyDict { get; set; }
+        public List<string> MyListOfNullString { get; set; }
 
         public static readonly string s_json = $"{{{s_partialJsonProperties},{s_partialJsonArrays}}}";
         public static readonly string s_json_flipped = $"{{{s_partialJsonArrays},{s_partialJsonProperties}}}";
@@ -106,7 +107,8 @@ namespace System.Text.Json.Serialization.Tests
                 @"""MyStringIListT"" : [""Hello""]," +
                 @"""MyStringICollectionT"" : [""Hello""]," +
                 @"""MyStringIReadOnlyCollectionT"" : [""Hello""]," +
-                @"""MyStringIReadOnlyListT"" : [""Hello""]";
+                @"""MyStringIReadOnlyListT"" : [""Hello""]," +
+                @"""MyListOfNullString"" : [null]";
 
         public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
 
@@ -160,6 +162,7 @@ namespace System.Text.Json.Serialization.Tests
             MyStringToStringDict = new Dictionary<string, string> { { "key", "value" } };
             MyStringToStringIDict = new Dictionary<string, string> { { "key", "value" } };
             MyStringToStringIReadOnlyDict = new Dictionary<string, string> { { "key", "value" } };
+            MyListOfNullString = new List<string> { null };
         }
 
         public void Verify()
@@ -212,6 +215,7 @@ namespace System.Text.Json.Serialization.Tests
             Assert.Equal("value", MyStringToStringDict["key"]);
             Assert.Equal("value", MyStringToStringIDict["key"]);
             Assert.Equal("value", MyStringToStringIReadOnlyDict["key"]);
+            Assert.Null(MyListOfNullString[0]);
         }
     }
 }
index 698c0e6458d4d07a601edb1a021e051f6f047f9d..c61e99f41e0c8e2d85aec13f491b99353585717d 100644 (file)
@@ -44,6 +44,7 @@ namespace System.Text.Json.Serialization.Tests
         public DateTimeOffset?[] MyDateTimeOffsetArray { get; set; }
         public SampleEnum?[] MyEnumArray { get; set; }
         public Dictionary<string, string> MyStringToStringDict { get; set; }
+        public List<int?> MyListOfNullInt { get; set; }
     }
 
     public class SimpleTestClassWithNulls : SimpleBaseClassWithNullables, ITestClass
@@ -90,6 +91,7 @@ namespace System.Text.Json.Serialization.Tests
             Assert.Null(MyDateTimeOffsetArray);
             Assert.Null(MyEnumArray);
             Assert.Null(MyStringToStringDict);
+            Assert.Null(MyListOfNullInt);
         }
         public static readonly string s_json =
                 @"{" +
@@ -127,7 +129,8 @@ namespace System.Text.Json.Serialization.Tests
                 @"""MyDateTimeArray"" : null," +
                 @"""MyDateTimeOffsetArray"" : null," +
                 @"""MyEnumArray"" : null," +
-                @"""MyStringToStringDict"" : null" +
+                @"""MyStringToStringDict"" : null," +
+                @"""MyListOfNullInt"" : null" +
                 @"}";
 
         public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
@@ -171,7 +174,8 @@ namespace System.Text.Json.Serialization.Tests
                 @"""MyDateTimeArray"" : [""2019-01-30T12:01:02.0000000Z""]," +
                 @"""MyDateTimeOffsetArray"" : [""2019-01-30T12:01:02.0000000+01:00""]," +
                 @"""MyEnumArray"" : [2]," +
-                @"""MyStringToStringDict"" : {""key"" : ""value""}" +
+                @"""MyStringToStringDict"" : {""key"" : ""value""}," +
+                @"""MyListOfNullInt"" : [null]" +
                 @"}";
 
         public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
@@ -214,6 +218,7 @@ namespace System.Text.Json.Serialization.Tests
             MyDateTimeOffsetArray = new DateTimeOffset?[] { new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0)) };
             MyEnumArray = new SampleEnum?[] { SampleEnum.Two };
             MyStringToStringDict = new Dictionary<string, string> { { "key", "value" } };
+            MyListOfNullInt = new List<int?> { null };
         }
 
         public void Verify()
@@ -254,6 +259,7 @@ namespace System.Text.Json.Serialization.Tests
             Assert.Equal(new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0)), MyDateTimeOffsetArray[0]);
             Assert.Equal(SampleEnum.Two, MyEnumArray[0]);
             Assert.Equal("value", MyStringToStringDict["key"]);
+            Assert.Null(MyListOfNullInt[0]);
         }
     }
 }
index 919f18bd79123d05468b9113006fa95e8925b6fd..cb7c4e203b1cd1f42950299d294405cf35a8ffaa 100644 (file)
@@ -30,11 +30,6 @@ namespace System.Text.Json.Serialization.Tests
 
         public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json);
 
-        public void Initialize()
-        {
-            MyString = null;
-        }
-
         public void Verify()
         {
             Assert.Equal(MyString, null);
@@ -45,10 +40,14 @@ namespace System.Text.Json.Serialization.Tests
     {
         public string MyString { get; set; } = "Hello";
         public int? MyInt { get; set; } = 1;
+        public int[] MyIntArray { get; set; } = new int[] { 1 };
+        public List<int> MyIntList { get; set; } = new List<int> { 1 };
         public static readonly string s_null_json =
                 @"{" +
                 @"""MyString"" : null," +
-                @"""MyInt"" : null" +
+                @"""MyInt"" : null," +
+                @"""MyIntArray"" : null," +
+                @"""MyIntList"" : null" +
                 @"}";
 
         public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_null_json);
@@ -108,6 +107,7 @@ namespace System.Text.Json.Serialization.Tests
             @"{" +
                 @"""MyData"":[" +
                     SimpleTestClass.s_json + "," +
+                    "null," +
                     SimpleTestClass.s_json +
                 @"]" +
             @"}");
@@ -122,6 +122,8 @@ namespace System.Text.Json.Serialization.Tests
                 MyData.Add(obj);
             }
 
+            MyData.Add(null);
+
             {
                 SimpleTestClass obj = new SimpleTestClass();
                 obj.Initialize();
@@ -131,9 +133,10 @@ namespace System.Text.Json.Serialization.Tests
 
         public void Verify()
         {
-            Assert.Equal(2, MyData.Count);
+            Assert.Equal(3, MyData.Count);
             MyData[0].Verify();
-            MyData[1].Verify();
+            Assert.Null(MyData[1]);
+            MyData[2].Verify();
         }
     }
 
index 70722386de26850774af76f01ba2078ba6f43213..1fdd0b05977fd9bc2523aa485ddc7b96dcf4942d 100644 (file)
@@ -72,6 +72,9 @@ namespace System.Text.Json.Serialization.Tests
             // Invalid data
             Assert.Throws<JsonException>(() => JsonSerializer.Parse<int[]>(Encoding.UTF8.GetBytes(@"[1,""a""]")));
 
+            // Invalid data
+            Assert.Throws<JsonException>(() => JsonSerializer.Parse<List<int?>>(Encoding.UTF8.GetBytes(@"[1,""a""]")));
+
             // Multidimensional arrays currently not supported
             Assert.Throws<JsonException>(() => JsonSerializer.Parse<int[,]>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")));
         }
index 0ff8dbe4443eb2d0fd25ddaa784d69d2388abf40..8a6c960002063396b34d0e2e56863e6b82db7e09 100644 (file)
@@ -17,6 +17,18 @@ namespace System.Text.Json.Serialization.Tests
                 Assert.Equal("1", json);
             }
 
+            {
+                int? value = 1;
+                string json = JsonSerializer.ToString(value);
+                Assert.Equal("1", json);
+            }
+
+            {
+                int? value = null;
+                string json = JsonSerializer.ToString(value);
+                Assert.Equal("null", json);
+            }
+
             {
                 Span<byte> json = JsonSerializer.ToBytes(1);
                 Assert.Equal(Encoding.UTF8.GetBytes("1"), json.ToArray());