Skip to content

fix(sdk-go): keep empty objects the API sent on a Source - #356

Open
u7k4rs6 wants to merge 1 commit into
stripe:mainfrom
u7k4rs6:fix/source-drops-empty-known-objects
Open

u7k4rs6 wants to merge 1 commit into
stripe:mainfrom
u7k4rs6:fix/source-drops-empty-known-objects

Conversation

@u7k4rs6

@u7k4rs6 u7k4rs6 commented Sep 19, 2026

Copy link
Copy Markdown

Motivation

omitempty drops a non-nil but empty map or slice. Source.MarshalJSON already works around that for granted_actions, but capabilities, external_connection, bank_account and card carry the same tag and were missed, so an empty object the API sent disappears on re-encode.

Unknown properties survive through AdditionalFields, so the fields the SDK models are currently the ones that get lost:

in  {"id":"src_1","capabilities":{}}          out {"id":"src_1"}
in  {"id":"src_1","unknown_future_field":{}}  out {"id":"src_1","unknown_future_field":{}}

This also erases the difference between a source having no capabilities and the API not reporting them.

Summary

  • Re-add the four map fields when non-nil, matching the existing granted_actions handling
  • Add a round-trip test covering all five modelled fields plus an unknown one

Key design considerations

  • Balance and the page types are unaffected; their optional fields are pointers, where omitempty only drops nil
  • Go-only, so no changeset — packages/sdk-go has no package.json. CHANGELOG entry follows Surface SpendRequest idempotency key #317

`Source.MarshalJSON` re-adds `granted_actions` to the encoded body when it
is non-nil, because `omitempty` would otherwise drop a non-nil but empty
slice. The four `map[string]any` fields beside it — `capabilities`,
`external_connection`, `bank_account` and `card` — carry the same
`omitempty` tag and got no such treatment, so an empty object the API sent
disappears on re-encode.

The effect is backwards from what the type documents. `AdditionalFields`
preserves unknown properties verbatim, so an empty object Link adds in
future survives a round trip, while an empty object for a field the SDK
already models does not:

    in  {"id":"src_1","capabilities":{}}            out {"id":"src_1"}
    in  {"id":"src_1","unknown_future_field":{}}    out {"id":"src_1","unknown_future_field":{}}

That also erases the distinction between "the source has no capabilities"
and "the API did not report capabilities", which is the distinction the
explicit-empty handling exists to keep.

Extend the same non-nil check to the four map fields. `Balance` and the
page types are unaffected: their remaining optional fields are pointers,
where `omitempty` already only drops nil.

Testing

`TestSourcePreservesExplicitEmptyResponseObjects` fails on the parent
commit, reporting all four fields dropped, and passes with this change.
`granted_actions` and an unknown field pass either way, so the test does
not simply restate the fix.

- `go test ./...`
- `go vet ./...`
- `gofmt -l .`
@u7k4rs6
u7k4rs6 requested a review from a team as a code owner September 19, 2026 20:56
@cla-assistant

cla-assistant Bot commented Sep 19, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@cla-assistant

cla-assistant Bot commented Sep 19, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant