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:
- Leave it and document the
!.
- 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.
- 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.
The friction
Every read of a response goes through a
!or a?.:The example project alone carries 73 of these on response reads. The request itself is already non-nullable, since
CallRequiredAsyncthrows rather than handing back null, so the remaining noise is entirely inside the records.Where it comes from
All 152 response fields in
protocol.jsonhavevalueOptional: 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:requiredValue types became
requiredand non-nullable; reference types became nullable. That split follows from C#, not from OBS.GetSceneListalways returnsscenes, andGetVersionalways returnsobsVersion, but both are typed as though they might not.What to consider
Making reference-typed response fields non-nullable and
requiredwould delete the!from ordinary code. The cost is thatrequiredmakes 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-
requiredvalue 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:
!.valueDescription. Several say things like "nullif not playing", which is exactly the set that must stay nullable. This is derivable from the definition rather than hand listed.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.