runtime: Fail null deserialization on non-nullable reference types
When deserializing “null” for a non-nullable value type System.Text.Json throws an Exception. However, for reference types this is not the case even if the project has the “Nullable” feature enabled.
Expectation: When nullable is enabled for the project deserializing null
into a non-nullable type should fail for both value and reference types.
Question: This could be worked around by writing a custom Converter that honors nullable during deserialization. Are there plans to expose a setting to control the deserialization behavior for non-nullable reference types?
Repro: Deserializing DateTime as null fails (by design per this issue 40922). Deserializing List<int> as null succeeds.
class Program
{
public class MyDataObject
{
public DateTime Dt { get; set; }
public List<int> MyNonNullableList { get; set; } = new List<int>();
}
static void Main(string[] args)
{
string input1 = "{\"Dt\":null, \"MyNonNullableList\":[42,32]}";
string invalidInput = "{\"Dt\":\"0001-01-01T00:00:00\",\"MyNonNullableList\":null}";
// Throws System.Text.Json.JsonException: 'The JSON value could not be converted to System.DateTime. Path: $.Dt | LineNumber: 0 | BytePositionInLine: 10.'
// var validConverted = JsonSerializer.Deserialize<MyDataObject>(input1);
// Does not throw Exception, but MyNonNullableList is null.
var invalidConverted = JsonSerializer.Deserialize<MyDataObject>(invalidInput);
Console.ReadKey();
}
}
About this issue
- Original URL
- State: open
- Created 4 years ago
- Reactions: 69
- Comments: 46 (22 by maintainers)
Any improvements with .NET 7 and required properties? I see the
JsonSerializer
is failing with aJsonException
when the JSON payload is missing a required property. However, it’s not failing when the property is there, explicitly set with anull
value.@Hottemax biggest problem is that (de)serializers in general have virtually infinite number of knobs and expectations how people want it to work. We have significant number of open issues and requests and we try to make sure we look at them holistically rather than per single request/issue. We cannot just remove APIs once added so we need to be conservative in that choice. This issue has accumulated enough upvotes for it to be considered for next release but note that it doesn’t guarantee we will do it. Soon we will look at all issues in 9.0 and Future milestone and make some initial choices but keep in mind that it’s overall almost 200 issues (12 being already considered for 9.0). There is a huge difference in how much time it takes to write simple prototype code like one above vs actual code which ships - latter requires much more time for testing and convincing people on the right design. We need to pick which of these issues are currently most important for product as a whole (i.e. our main scenario is seamless interaction with ASP.NET Core which might be invisible for most of the users) but upvoted issues like this one are also a big factor for us when picking. Certainly having someone from community doing some prototyping would help here. I.e. extending code above to figure out all corner cases, some test cases etc would decrease amount of work for us and make it more likely it would ship next release.
ASP.net Core can detect properties that are non-nullable reference types at runtime. It even rejects the request if one of the non-nullable property is null. I however don’t know if their code can detect non-nullable parameters as well.
For example, if I have this class:
and the JSON payload is empty
{}
ASP.net returns:
Note that only
Value
is required becauseOptionalValue
is a nullable reference type.The ASP.net code that detects non-nullable reference types is in
DataAnnotationsMetadataProvider
(link to the code) and it can be toggled on/off via MvcOptions.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes@ahsonkhan EF Core is actually doing this, when you have NRTs enabled, they’ll make columns
NULL
/NOT NULL
based on how your model class is annotated, see https://docs.microsoft.com/en-us/ef/core/miscellaneous/nullable-reference-typesAny chance a fix for this is coming with .net 5?
This is currently possible to achieve with contract customization except for top level (we don’t have info on nullability for top level - for top level you can do null check separately). Here is example implementation:
there is extra work needed to cover all corner cases (array values etc) but this should hopefully cover most of the scenarios
I understand, but at the same time a type system is not the same thing as a serialization contract. In automatic serializers we use convention to map type system features to serialization contracts in a way that feels intuitive to the user. One good example is the
required
keyword, which is mapped from a C# type system concept to a similar (but not identical) feature in the serialization contract, and despite the fact thatrequired
isn’t enforced at run time.Mapping non-nullable annotations to a “require non-null” deserialization contract seems useful to me for a couple of reasons:
I should clarify that the scope of this feature is limited to member and constructor parameter annotations. We can’t add this to generic parameters/collection elements due to constraints in their runtime representation, at least in the reflection serializer.
That’s a lotta code when your model has dozens or hundreds of properties that shouldn’t be null.
This ticket would be opt-in, no doubt. And I think it’s still a great idea if it’s implementable.
Thanks. If it’s opt-in, my primary concerns are alleviated.
Those APIs are just using reflection to get at the relevant information. If those APIs aren’t exposed publicly downlevel, S.T.J could still achieve the same thing using reflection, even by building in a copy of those implementations as internal (and with whatever tweaks might be needed to work downlevel).
They also affect how Entity Framework creates columns - an NRT causes a column to be nullable.
This “key aspect” is so bad that not even Microsoft’s products stick to it. Kotlin handles the “Billion Dollar Mistake” way better, despite interoperability with the Java ecosystem.
Would this help? “Expose top-level nullability information from reflection” (#29723) is slated for .NET 6 Preview 7.
So @ahsonkhan if I understand correctly you are suggesting a property attribute could be used to ensure NULL is ignored when de-serialising a non-nullable reference type. But don’t we want a way to trigger an exception to be equivalent to the non-nullable value type case?
And if we have an attribute or way to trigger an exception with NULL would that not also solve the compiler warning issue mentioned by @safern in that the compiler flow analysis can see that an incoming NULL will lead to an exception and hence the conversion to non-nullable is safe?
I think it would be really great to have a solution that is compatible with the compile-time checker if possible. 😀
I think it is different because one of the most useful purposes of both reflection and source generators is to save the developer from writing a whole bunch of ‘handwritten’ code. It is from this perspective this feature request is thought about I believe.
Taking your example above, your point is (I think) that the compiler warning helps the author remember to insert a null check. In code generation, the ‘author’ is runtime code using reflection or a compile-time source generator. This automated author should have the benefit of the same information that a human author would. How else can it produce code of similar value to ‘handwritten’ code? The
<nullable>enabled</nullable>
is therefore analogous to a serializer flag.Here’s a prototype showing how NRT metadata can be extracted in both reflection and source gen:
https://github.com/eiriktsarpalis/typeshape-csharp/commit/e96ef202263922c0d380554c02d929b16b2e999e
One thing to note is that this will most likely require an opt-in switch – it would otherwise be a breaking change:
Thanks for the detailed provided context @krwq ! 👍 Replies like these are so valuable, so us library users know what is going on behind the scenes.
This has been open for years, and many related requests are also closed as duplicates, pointing to this issue.
We are also facing this issue. What is the actual problem here to provide a solution for this?
If nullable annottations are turned on, fail/warn (potentially based on a JsonSerializer setting/annotation), when null is received as the value of the attribute in Json.
Currently it only fails when the attribute is not present at all (when I mark the property as required). Funnily enough, the deprecated IgnoreNullValues will work also for deserialization (as opposed to JsonIgnoreCondition.WhenWritingNull, which only handles the serialization direction).
It does, assuming you’re using controllers not minimal API.
Add this controller in an ASP.NET Core app
Posting
{ "nonNullableProp": null }
to/nullability
will get you a 400 response.I found a temporary solution as follows. I don’t add the ThrowIfNull extension if it can be null.
From @Eneuman in https://github.com/dotnet/runtime/issues/46431: