Skip to content

Response records are nullable where the protocol never says they can be #22

Description

@Agash

The friction

Every read of a response goes through a ! or a ?.:

string[] names = (await client.Scenes.GetSceneListAsync(new(), ct)).Scenes!.Select(s => s.SceneName!).ToArray();
int count = (await client.Scenes.GetSceneListAsync(new(), ct)).Scenes?.Count ?? 0;

The example project alone carries 73 of these on response reads. The request itself is already non-nullable, since CallRequiredAsync throws rather than handing back null, so the remaining noise is entirely inside the records.

Where it comes from

All 152 response fields in protocol.json have valueOptional: null. The protocol never marks a response field optional; it only does that for request fields. So the generator has no signal, and its current rule keys off the C# type rather than the definition:

count share
response properties 152
generated nullable 88 57%
generated required 64 42%

Value types became required and non-nullable; reference types became nullable. That split follows from C#, not from OBS. GetSceneList always returns scenes, and GetVersion always returns obsVersion, but both are typed as though they might not.

What to consider

Making reference-typed response fields non-nullable and required would delete the ! from ordinary code. The cost is that required makes System.Text.Json throw when the field is absent, so a response OBS trims, or an older OBS that predates a field, would fail the whole read rather than yield a null.

That risk is not hypothetical in one direction: the 64 already-required value fields have exactly that behaviour today and have not caused trouble, which is some evidence the protocol's response shape is honoured in practice. It is also why the payload shape check was written as a key-overlap test rather than a required-field test.

Options, roughly in increasing boldness:

  1. Leave it and document the !.
  2. Make fields non-nullable only where the field is documented without an "optional" or "null if" phrase in valueDescription. Several say things like "null if not playing", which is exactly the set that must stay nullable. This is derivable from the definition rather than hand listed.
  3. Make every reference-typed response field non-nullable, and accept that a missing field throws.

Option 2 looks like the honest one: the prose is the only place the protocol records nullability for responses, and it is already the signal used to fix GetMediaInputStatus, which failed to deserialize whenever media was not playing because a field documented as nullable in prose had been generated non-nullable.

Scope note

This changes the shape of every response record, so it belongs in one release with the other breaking work rather than trickling out.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions