Debug.Assert(converter != null);
}
- // User indicated that non-nullable-struct-handling converter should handle a nullable struct type.
- // The serializer would have picked that converter up by default and wrapped it in NullableConverter<T>;
- // throw so that user can modify or remove their unnecessary CanConvert method override.
+ // User has indicated that either:
+ // a) a non-nullable-struct handling converter should handle a nullable struct type or
+ // b) a nullable-struct handling converter should handle a non-nullable struct type.
+ // User should implement a custom converter for the underlying struct and remove the unnecessary CanConvert method override.
+ // The serializer will automatically wrap the custom converter with NullableConverter<T>.
//
// We also throw to avoid passing an invalid argument to setters for nullable struct properties,
// which would cause an InvalidProgramException when the generated IL is invoked.
- // This is not an issue of the converter is wrapped in NullableConverter<T>.
- if (runtimePropertyType.CanBeNull() && !converter.TypeToConvert.CanBeNull())
+ if (runtimePropertyType.IsValueType && converter.IsValueType &&
+ (runtimePropertyType.IsNullableOfT() ^ converter.TypeToConvert.IsNullableOfT()))
{
- ThrowHelper.ThrowInvalidOperationException_ConverterCanConvertNullableRedundant(runtimePropertyType, converter);
+ ThrowHelper.ThrowInvalidOperationException_ConverterCanConvertMultipleTypes(runtimePropertyType, converter);
}
return converter;
throw new NotImplementedException();
}
}
+
+
+ [Fact]
+ public static void BadDoubleConverter_Serialization()
+ {
+ var options = new JsonSerializerOptions
+ {
+ Converters = { new BadDoubleConverter() }
+ };
+
+ Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize<double?>(3.14, options));
+ Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(new { value = (double?)3.14 }, options));
+ Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(new double?[] { 3.14 }, options));
+ Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(new Dictionary<string, double?> { ["key"] = 3.14 }, options));
+ }
+
+ [Fact]
+ public static void BadDoubleConverter_Deserialization()
+ {
+ var options = new JsonSerializerOptions
+ {
+ Converters = { new BadDoubleConverter() }
+ };
+
+ Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<double?>("3.14", options));
+ Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<PocoWithGenericProperty<double?>>(@"{""Property"":3.14}", options));
+ Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<double?[]>("[3.14]", options));
+ Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<Dictionary<string, double?>>(@"{""key"":3.14}", options));
+ }
+
+
+ [Fact]
+ public static void BadNullableDoubleConverter_Serialization()
+ {
+ var options = new JsonSerializerOptions
+ {
+ Converters = { new BadNullableDoubleConverter() }
+ };
+
+ Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(3.14, options));
+ Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(new { value = 3.14 }, options));
+ Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(new[] { 3.14 }, options));
+ Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(new Dictionary<string, double> { ["key"] = 3.14 }, options));
+ }
+
+ [Fact]
+ public static void BadNullableDoubleConverter_Deserialization()
+ {
+ var options = new JsonSerializerOptions
+ {
+ Converters = { new BadNullableDoubleConverter() }
+ };
+
+ Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<double>("3.14", options));
+ Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<PocoWithGenericProperty<double>>(@"{""Property"":3.14}", options));
+ Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<double[]>("[3.14]", options));
+ Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<Dictionary<string, double>>(@"{""key"":3.14}", options));
+ }
+
+ private class PocoWithGenericProperty<T>
+ {
+ public T Property { get; set; }
+ }
+
+ /// <summary>
+ /// A double converter that incorrectly claims to support double? types.
+ /// </summary>
+ private class BadDoubleConverter : JsonConverter<double>
+ {
+ public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(double) || typeToConvert == typeof(double?);
+ public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => reader.GetDouble();
+ public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options) => writer.WriteNumberValue(value);
+ }
+
+ /// <summary>
+ /// A double? converter that incorrectly claims to support double types.
+ /// </summary>
+ private class BadNullableDoubleConverter : JsonConverter<double?>
+ {
+ public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(double) || typeToConvert == typeof(double?);
+ public override double? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => reader.GetDouble();
+ public override void Write(Utf8JsonWriter writer, double? value, JsonSerializerOptions options) => writer.WriteNumberValue(value.Value);
+ }
}
}