Add logic to throw when converter doesn't handle null and null can't be assigned...
authorLayomi Akinrinade <laakinri@microsoft.com>
Thu, 7 May 2020 17:40:04 +0000 (10:40 -0700)
committerLayomi Akinrinade <laakinri@microsoft.com>
Thu, 7 May 2020 17:40:04 +0000 (10:40 -0700)
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonParameterInfoOfT.cs
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoOfT.cs
src/libraries/System.Text.Json/tests/Serialization/CustomConverterTests/CustomConverterTests.HandleNull.cs

index e18a31a0c7088758589bd7d4698ebb623a662be2..058e7dc395bd8a3646ab3f3e89f1c0b50aab43c2 100644 (file)
@@ -23,6 +23,7 @@ namespace System.Text.Json.Serialization
             CanBePolymorphic = TypeToConvert == typeof(object);
             IsValueType = TypeToConvert.IsValueType;
             HandleNull = IsValueType;
+            CanBeNull = !IsValueType || Nullable.GetUnderlyingType(TypeToConvert) != null;
             IsInternalConverter = GetType().Assembly == typeof(JsonConverter).Assembly;
             CanUseDirectReadOrWrite = !CanBePolymorphic && IsInternalConverter && ClassType == ClassType.Value;
         }
@@ -63,6 +64,11 @@ namespace System.Text.Json.Serialization
         /// </remarks>
         public virtual bool HandleNull { get; }
 
+        /// <summary>
+        /// Can <see langword="null"/> be assigned to <see cref="TypeToConvert"/>?
+        /// </summary>
+        internal bool CanBeNull { get; }
+
         /// <summary>
         /// Is the converter built-in.
         /// </summary>
@@ -112,6 +118,11 @@ namespace System.Text.Json.Serialization
                 // For perf and converter simplicity, handle null here instead of forwarding to the converter.
                 if (reader.TokenType == JsonTokenType.Null && !HandleNull)
                 {
+                    if (!CanBeNull)
+                    {
+                        ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
+                    }
+
                     value = default;
                     return true;
                 }
@@ -154,6 +165,11 @@ namespace System.Text.Json.Serialization
             {
                 if (reader.TokenType == JsonTokenType.Null && !HandleNull && !wasContinuation)
                 {
+                    if (!CanBeNull)
+                    {
+                        ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
+                    }
+
                     // For perf and converter simplicity, handle null here instead of forwarding to the converter.
                     value = default;
                     success = true;
@@ -171,6 +187,11 @@ namespace System.Text.Json.Serialization
                     // For perf and converter simplicity, handle null here instead of forwarding to the converter.
                     if (reader.TokenType == JsonTokenType.Null && !HandleNull)
                     {
+                        if (!CanBeNull)
+                        {
+                            ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
+                        }
+
                         value = default;
                         state.Pop(true);
                         return true;
index a2b20294510ee5e8038e5c4826ee254345d80b7b..a03982f4e40d8fbe0aa957d54847724cfb8c84c4 100644 (file)
@@ -56,6 +56,11 @@ namespace System.Text.Json
 
             if (isNullToken && !_converter.HandleNull && !state.IsContinuation)
             {
+                if (!_converter.CanBeNull)
+                {
+                    ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(_converter.TypeToConvert);
+                }
+
                 // Don't have to check for IgnoreNullValue option here because we set the default value regardless.
                 value = DefaultValue;
                 return true;
@@ -85,6 +90,11 @@ namespace System.Text.Json
 
             if (isNullToken && !_converter.HandleNull && !state.IsContinuation)
             {
+                if (!_converter.CanBeNull)
+                {
+                    ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(_converter.TypeToConvert);
+                }
+
                 // Don't have to check for IgnoreNullValue option here because we set the default value regardless.
                 value = TypedDefaultValue;
                 return true;
index fe1e77e701678f43b828d636d02354380a2cad68..357a247185a9226ed64bba89f73c9c1a2b62d8e2 100644 (file)
@@ -164,6 +164,11 @@ namespace System.Text.Json
             {
                 if (!IgnoreNullValues)
                 {
+                    if (!Converter.CanBeNull)
+                    {
+                        ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Converter.TypeToConvert);
+                    }
+
                     T value = default;
                     Set!(obj, value!);
                 }
@@ -206,6 +211,11 @@ namespace System.Text.Json
             bool isNullToken = reader.TokenType == JsonTokenType.Null;
             if (isNullToken && !Converter.HandleNull && !state.IsContinuation)
             {
+                if (!Converter.CanBeNull)
+                {
+                    ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(Converter.TypeToConvert);
+                }
+
                 value = default(T)!;
                 success = true;
             }
index ac7165b15bb3c2ec1e8033710a02c0e310a2f69f..30ee15ea9980d6c271b64c33636cc5cb6a392575 100644 (file)
@@ -49,8 +49,8 @@ namespace System.Text.Json.Serialization.Tests
             var options = new JsonSerializerOptions();
             options.Converters.Add(new Int32NullConverter_OptOut());
 
-            // Serializer sets default value (doesn't fallback to built-in converter).
-            Assert.Equal(0, JsonSerializer.Deserialize<int>("null", options));
+            // Serializer throws JsonException if null is assigned to value that can't be null.
+            Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<int>("null", options));
         }
 
         private class Int32NullConverter_OptOut : Int32NullConverter_SpecialCaseNull
@@ -113,9 +113,7 @@ namespace System.Text.Json.Serialization.Tests
             var options = new JsonSerializerOptions();
             options.Converters.Add(new PointStructConverter_OptOut());
 
-            var obj = JsonSerializer.Deserialize<Point_2D_Struct>("null", options);
-            Assert.Equal(0, obj.X);
-            Assert.Equal(0, obj.Y);
+            Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<Point_2D_Struct>("null", options));
         }
 
         private class PointStructConverter_OptOut : PointStructConverter_SpecialCaseNull