Phase 5: JSON - Nullable & Empty - Context
Gathered: 2026-02-06 Status: Ready for planning
Developers can express null vs absent vs default semantics for primitive fields and control empty object serialization behavior across all 4 generators (go-http, go-client, ts-client, openapiv3). This phase adds nullable annotation for primitives and empty_behavior annotation for message fields.
Nullable semantics#
- Nullable applies to all primitive types equally (string, int32, int64, float, double, bool, bytes)
- Nullable annotation only valid on proto3
optionalfields — aligns with protobuf ecosystem conventions - Nullable for primitives only — message fields already have null representation
- Absent = key omitted from JSON (standard proto3 behavior); Claude can decide whether to also support explicit default value presence
Empty behavior modes#
- Default empty_behavior when no annotation: PRESERVE (empty messages serialize as
{}) - Annotation applies at field level only — annotate specific message fields, not message types
- "Empty" defined as all fields at proto default — simple, predictable, not recursive
- Empty behavior only valid on singular message fields — not repeated fields or maps (collections already have
[]/{})
Type generation#
- Go: sebuf controls JSON serialization behavior; proto message types generated by protoc-gen-go unchanged
- TypeScript:
T | nullfor nullable types (sebuf generates ts-client directly) - OpenAPI 3.1:
{"type": ["string", "null"]}array syntax (proper JSON Schema style, not deprecatednullable: true)
Backward compatibility#
- Existing optional fields WITHOUT nullable annotation: current protojson behavior (omit when not set, include when set)
- Existing message fields WITHOUT empty_behavior annotation: current protojson behavior (empty =
{}, matches PRESERVE) - Zero breaking changes for existing protos
Validation#
- Nullable on non-optional field: compile-time error (fail protoc generation)
- Empty_behavior on primitive field: compile-time error (only valid for message fields)
Claude's Discretion#
- Whether "absent" can also support explicit default value presence (both representations)
- Implementation details for detecting empty messages
- Exact error message wording for invalid annotation usage
- "Feel at home with protobuf/buf ecosystem" — nullable semantics should align with how proto3 optional works
- Follow established patterns from Phase 4 (int64_encoding, enum_encoding) for annotation structure
None — discussion stayed within phase scope
Phase: 05-json-nullable-empty Context gathered: 2026-02-06
