Skip to content

Add a STJ feature flag recognizing nullable reference type annotations on properties #100144

Closed
@eiriktsarpalis

Description

@eiriktsarpalis

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.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions