Phase 5: JSON - Nullable & Empty - Context

Gathered: 2026-02-06 Status: Ready for planning

## Phase Boundary

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.

## Implementation Decisions

Nullable semantics#

  • Nullable applies to all primitive types equally (string, int32, int64, float, double, bool, bytes)
  • Nullable annotation only valid on proto3 optional fields — 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 | null for nullable types (sebuf generates ts-client directly)
  • OpenAPI 3.1: {"type": ["string", "null"]} array syntax (proper JSON Schema style, not deprecated nullable: 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
## Specific Ideas
  • "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
## Deferred Ideas

None — discussion stayed within phase scope


Phase: 05-json-nullable-empty Context gathered: 2026-02-06