Go: keep empty lists as [] through decode, re-marshal and requests
What
- Generated Go types: the Go overlay (
go/oapi-overlay.yaml) tags every list propertyx-omitempty: false+x-omitzero: true, so list fields inpkg/generatedare taggedomitzeroinstead ofomitempty. Types regenerated withgo generate; no hand edits. Client.GetAll/GetAllWithLimit: start from an empty slice instead of nil.- Tests (
go/pkg/fizzy/json_shape_test.go):- round-trip sweep over every generated type with a list field:
[]decodes non-nil and re-marshals as[],{}stays absent. A coverage check against the generated source fails if a new list type isn't in the sweep. UpdateBoardwithUserIds: []string{}sends"user_ids": [].GetAllreturns[]for a server-sent[].- go/ast check: every exported field of every exported struct (generated and hand-written) has a snake_case json tag. Non-data types are allowlisted with a reason;
Content_Type/Content_MD5are kept because that's how Fizzy sends them.
- round-trip sweep over every generated type with a list field:
Why
prefer-skip-optional-pointer turns optional lists into plain slices tagged omitempty, and omitempty drops an empty non-nil slice. So:
- a
Cardthe server sent with"tags": [](alsoassignees,steps,Board.user_ids, …) lost the key when re-marshaled, which reads as "unknown" rather than "none"; - a caller couldn't clear a list.
UpdateBoardRequest.UserIds,Create/UpdateWebhookRequest.SubscribedActionsandBulkReadNotificationsRequest.NotificationIdsset to[]never reached the server.
GetAll built its result by appending to a nil slice, so an empty list came back nil and marshaled as null.
Evidence
Before (tests run against the old types and client): 15 round-trip assertion failures across the 12 list-bearing types, body user_ids = "", want [], and GetAll returned nil for a server-sent []. After: all pass, and Go conformance passes 134/134.
Compat
Field types don't change. The only output change: an empty list now marshals as [] where it used to be omitted. A nil list is still omitted. Required lists (BoardAccesses.users, Identity.accounts, Webhook.subscribed_actions) also pick up omitzero, because the overlay filter can't see the parent's required set. A nil one now marshals as absent instead of null. The server always sends these lists, so a decoded value is never nil.
This needs encoding/json omitzero, which arrived in Go 1.24. The module already requires a newer Go than that.
Other languages: surveyed, not changed
- TypeScript and Ruby pass the server's JSON through as-is, so
[]and the keys survive. - Rust wraps optional lists in
Option<Vec<_>>withskip_serializing_if = "Option::is_none", soSome([])serializes as[], andserde(rename)is emitted wherever the field name differs from the wire key. - Swift uses
encodeIfPresent, which keeps[];BaseService.encoderwrites snake_case keys. - Kotlin: request bodies send
[], and responses decode[]as an empty list. Every response field the model writes has a snake_case@SerialName.
Conformance: four new request-shapes cases send an empty list on UpdateBoard (user_ids), CreateWebhook and UpdateWebhook (subscribed_actions) and BulkReadNotifications (notification_ids), and assert the key reaches the wire. The Go runner used to send every fixture body as a raw map. For these four operations it now decodes the body into the generated request type and sends that, so the cases go through the json tags. Against the old omitempty types, all four fail. The TypeScript runner maps these fixture bodies to the SDK's camelCase fields, and the Kotlin runner passes user_ids through.