Description
Background and motivation
STJ should be able to recognize non-nullable reference types in property and constructor parameter annotations and fail serialization or deserialization if the run-time values are found to violate nullability. Recognizing annotations is valuable from the perspective of JSON schema extraction, since the current nullable-oblivious semantics result in more permissive schemas than what users might desire.
Enabling this would be a breaking change, so it needs to be turned on via an opt-in flag. The proposal intentionally makes this a property rather than a process-wide feature switch so that individual components can use the semantics appropriate to them.
API Proposal
namespace System.Text.Json;
public partial class JsonSerializerOptions
{
public bool ResolveNullableReferenceTypes { get; set; }
}
public partial class JsonSourceGenerationOptionsAttribute
{
public bool ResolveNullableReferenceTypes { get; set; }
}
namespace System.Text.Json.Serialization.Metadata;
public partial class JsonPropertyInfo
{
// Allow `null` values that might be returned by the getter.
// Defaults to false if the global flag has been enabled
// and the property is annotated as non-nullable
// (e.g. via type annotation or `NotNull` or `MaybeNull` attributes)
public bool AllowNullWrites { get; set; }
// Allow `null` values to be passed to the setter
// Defaults to false if the global flag has been enabled
// and the property is annotated as non-nullable
// (e.g. via type annotation or `DisallowNull` or `AllowNull` attributes)
// either on the property itself or the associated constructor parameter.
public bool AllowNullReads { get; set; }
}
API usage
// Default semantics
JsonSerializer.Deserialize<Person>("""{"Name":null, "Address":null}"""); // Success
// Nullable resolution enabled
var options = new JsonSerializerOptions { ResolveNullableReferenceTypes = true };
JsonSerializer.Deserialize<Person>("""{"Name":"John", "Address": null}"""); // Success
JsonSerializer.Deserialize<Person>("""{"Name":null, "Address": "22 Acacia Avenue"}"""); // JsonException
record Person(string Name, string? Address);
Alternative designs
This proposal is very closely related to #100075. We should consider a design that unifies both behaviors under a single feature switch.
Risks
Because of restrictions on how NRT's are implemented and the architecture used by JsonSerializer (contracts keyed on System.Type
which is nullable-agnostic), it is currently not possible to determine nullability annotations for root-level types, generic properties or collection elements. For example, it is not possible to distinguish between Person
and Person?
or List<Person>
and List<Person?>
as root-level values. This could lead to user confusion so we should try to document this concern as clearly as possible.