Skip to content

Add UnsetFields for clearing field values in v1 and v2 API requests#2322

Merged
jar-stripe merged 12 commits intomasterfrom
jar/explicit-null-v2
Mar 24, 2026
Merged

Add UnsetFields for clearing field values in v1 and v2 API requests#2322
jar-stripe merged 12 commits intomasterfrom
jar/explicit-null-v2

Conversation

@jar-stripe
Copy link
Copy Markdown
Contributor

@jar-stripe jar-stripe commented Mar 20, 2026

Why?

Go's pointer types with omitempty cannot distinguish "user never set this field" (nil pointer) from "user explicitly wants to clear this field." This affects both API versions:

  • v1 form: Clearing a field requires sending field= (empty string), but the form encoder skips nil pointers
  • v2 JSON: Clearing a field requires sending "field": null, but json.Marshal omits nil pointers entirely

This was identified as part of DEVSDK-2926 (cross-SDK emptyable field audit). Go was one of three languages needing runtime changes.

What?

In params.go + stripe.go:

  • Added UnsetFields []string field to Params — a list of API field names that should be explicitly cleared in requests
  • Added AddUnsetField(field string) convenience method on Params
  • For v1 POST (form): Call() adds field= entries for all listed fields
  • For v2 POST (JSON): marshalV2JSON() marshals params normally, then patches in null entries for any fields listed in UnsetFields

In stripe.go:

  • Added collectAllUnsetFields() which recursively walks params structs via reflection to find UnsetFields entries at all nesting levels
  • For v1 form: nested fields use bracket notation (e.g. cancellation_details[comment]=)
  • For v2 JSON: nested nulls are injected at the correct depth (e.g. {"cancellation_details": {"comment": null}})
  • Nested params structs carry their own UnsetFields []string field (generated by codegen)

Tests: 11 tests covering root and nested UnsetFields for both v1 form and v2 JSON encoding, including deep nesting.

See Also

Changelog

  • Added UnsetFields field and AddUnsetField method to Params for explicitly clearing field values in API requests. For v2 JSON requests, listed fields are sent as "field": null. For v1 form requests, listed fields are sent as field= (empty string).
  • Nested params structs with emptyable fields carry their own UnsetFields slice, enabling clearing of nested fields (e.g. params.CancellationDetails.AddUnsetField(...)).
  • Generated UnsetField string enum types provide type-safe constants for each clearable field (e.g. SubscriptionUpdateParamsUnsetFieldDescription).

For v2 APIs (JSON-encoded POST), null has semantic meaning — it clears
a field. Previously, nil pointer fields with omitempty were always
omitted from JSON, making it impossible to send explicit null.

Adds NullFields []string to the Params struct and a marshalV2JSON
helper that patches explicit null entries into the marshaled JSON for
any field names listed in NullFields. This is fully additive — existing
code is unaffected, and v1 requests ignore NullFields entirely.

Usage:
  params := &stripe.V2SomeUpdateParams{Name: stripe.String("new")}
  params.AddNullField("description")

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
@jar-stripe jar-stripe requested a review from a team as a code owner March 20, 2026 17:44
@jar-stripe jar-stripe requested review from prathmesh-stripe and removed request for a team March 20, 2026 17:44
jar-stripe and others added 2 commits March 23, 2026 19:05
EmptyFields works across both v1 and v2 APIs:
- v2 JSON: sends "field": null to clear a field
- v1 form: sends field= (empty string) to clear a field

Renamed from NullFields since the semantic is "empty/clear this field"
regardless of API version, and the wire encoding differs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
Add collectAllEmptyFields() which walks params structs via reflection
to find EmptyFields entries at all nesting levels. Update marshalV2JSON
and v1 form encoding to use recursive collection, so nested params
structs can declare emptyable fields independently.

For v2 JSON: nested nulls are injected at the correct depth
(e.g. {"details": {"comment": null}}).

For v1 form: nested empty fields use bracket notation
(e.g. details[comment]=).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
@jar-stripe jar-stripe changed the title Add NullFields for explicit null in v2 JSON requests Add EmptyFields for clearing field values in v1 and v2 API requests Mar 24, 2026
jar-stripe and others added 2 commits March 23, 2026 20:40
Rename the field clearing mechanism from EmptyFields/AddEmptyField to
UnsetFields/AddUnsetField for clarity. Regenerate all codegen output
with the updated naming: per-struct UnsetField enum types,
AddUnsetField methods, and UnsetFields []string on nested params.

Skip EmptyableField generation for list/search params structs since
they embed ListParams (not Params) and clearing fields on GET
requests is not meaningful.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
@jar-stripe jar-stripe changed the title Add EmptyFields for clearing field values in v1 and v2 API requests Add UnsetFields for clearing field values in v1 and v2 API requests Mar 24, 2026
@mbroshi-stripe mbroshi-stripe requested a review from Copilot March 24, 2026 13:07
Comment thread account.go Outdated
Comment thread stripe.go
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an UnsetFields mechanism to Stripe Go params to let callers explicitly clear fields in API requests (v1 form: field=, v2 JSON: "field": null), including support for nested params structs, and updates generated params types plus tests accordingly.

Changes:

  • Added Params.UnsetFields []string and Params.AddUnsetField(string) for explicit field clearing.
  • Updated request encoding in stripe.go to emit empty form fields for v1 and inject JSON nulls for v2, collecting nested UnsetFields via reflection.
  • Regenerated many params structs to include type-safe UnsetField enums + AddUnsetField helpers; added tests for root and nested behavior.

Reviewed changes

Copilot reviewed 62 out of 63 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
webhookendpoint.go Adds type-safe unset-field enums + AddUnsetField helpers for webhook endpoint params.
treasury_outboundtransfer.go Adds UnsetFields support to nested destination payment method options params.
treasury_outboundpayment.go Adds UnsetFields support to billing details/options nested params.
treasury_financialaccount.go Adds type-safe unset-field enums + AddUnsetField helpers for financial account params.
transferreversal.go Adds type-safe unset-field enums + AddUnsetField helpers for transfer reversal params.
transfer.go Adds type-safe unset-field enums + AddUnsetField helpers for transfer params.
topup.go Adds type-safe unset-field enums + AddUnsetField helpers for topup params.
testhelpers_confirmationtoken.go Adds UnsetFields + enums/helpers to testhelpers confirmation token nested params.
terminal_reader.go Adds type-safe unset-field enums + AddUnsetField helpers for terminal reader params.
terminal_location.go Adds type-safe unset-field enums + AddUnsetField helpers for terminal location params.
terminal_configuration.go Adds UnsetFields across multiple nested terminal configuration structs + enums/helpers.
taxrate.go Adds type-safe unset-field enums + AddUnsetField helpers for tax rate params.
tax_registration.go Adds type-safe unset-field enums + AddUnsetField helpers for tax registration params.
subscriptionschedule.go Adds UnsetFields support across many nested subscription schedule structs + enums/helpers.
subscriptionitem.go Adds UnsetFields support across subscription item params + enums/helpers.
stripe_test.go Adds tests covering v1/v2 root and nested UnsetFields behavior.
stripe.go Implements recursive collection of UnsetFields + v1/v2 encoding behavior (marshalV2JSON).
source.go Adds UnsetFields support and enums/helpers for source and mandate params.
shippingrate.go Adds type-safe unset-field enums + AddUnsetField helpers for shipping rate params.
refund.go Adds type-safe unset-field enums + AddUnsetField helpers for refund params.
quote.go Adds UnsetFields support across quote + line item/subscription nested params + enums/helpers.
promotioncode.go Adds type-safe unset-field enums + AddUnsetField helpers for promotion code params.
product.go Adds type-safe unset-field enums + AddUnsetField helpers for product params.
price.go Adds type-safe unset-field enums + AddUnsetField helpers for price params.
plan.go Adds type-safe unset-field enums + AddUnsetField helpers for plan params.
person.go Adds UnsetFields support across person nested params + enums/helpers.
payout.go Adds type-safe unset-field enums + AddUnsetField helpers for payout params.
paymentsource.go Adds type-safe unset-field enums + AddUnsetField helpers for payment source params.
paymentrecord.go Adds type-safe unset-field enums + AddUnsetField helpers for payment record report params.
paymentmethod.go Adds UnsetFields support across payment method billing details/network nested params + enums/helpers.
paymentlink.go Adds UnsetFields support across payment link nested params + enums/helpers.
params.go Adds Params.UnsetFields and Params.AddUnsetField.
issuing_transaction.go Adds type-safe unset-field enums + AddUnsetField helpers for issuing transaction params.
issuing_personalizationdesign.go Adds UnsetFields support across issuing personalization design nested params + enums/helpers.
issuing_cardholder.go Adds UnsetFields support for issuing cardholder terms acceptance nested params + enums/helpers.
issuing_card.go Adds type-safe unset-field enums + AddUnsetField helpers for issuing card params.
issuing_authorization.go Adds type-safe unset-field enums + AddUnsetField helpers for issuing authorization params.
invoicelineitem.go Adds UnsetFields support for invoice line item params + enums/helpers.
invoiceitem.go Adds UnsetFields support for invoice item params + enums/helpers.
identity_verificationsession.go Adds UnsetFields support for identity verification session options nested params + enums/helpers.
filelink.go Adds type-safe unset-field enums + AddUnsetField helpers for file link params.
file.go Adds UnsetFields support for file link data nested params + enums/helpers.
feerefund.go Adds type-safe unset-field enums + AddUnsetField helpers for fee refund params.
entitlements_feature.go Adds type-safe unset-field enums + AddUnsetField helpers for entitlements feature params.
dispute.go Adds UnsetFields support across dispute evidence nested params + enums/helpers.
customerbalancetransaction.go Adds type-safe unset-field enums + AddUnsetField helpers for customer balance transaction params.
customer.go Adds UnsetFields support across customer nested params + enums/helpers.
creditnote.go Adds UnsetFields support for credit note line params + enums/helpers.
coupon.go Adds type-safe unset-field enums + AddUnsetField helpers for coupon params.
climate_order.go Adds UnsetFields support for climate order beneficiary nested params + enums/helpers.
charge.go Adds UnsetFields support for charge fraud details nested params + enums/helpers.
card.go Adds type-safe unset-field enums + AddUnsetField helpers for card params.
billingportal_configuration.go Adds UnsetFields support across billing portal configuration nested params + enums/helpers.
billing_creditgrant.go Adds type-safe unset-field enums + AddUnsetField helpers for billing credit grant params.
bankaccount.go Adds type-safe unset-field enums + AddUnsetField helpers for bank account params.
balancesettings.go Adds UnsetFields support in balance settings nested params; also changes map value type for MinimumBalanceByCurrency.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread stripe.go Outdated
Comment thread balancesettings.go
Comment thread stripe_test.go
Comment thread stripe_test.go Outdated
jar-stripe and others added 2 commits March 24, 2026 13:05
Address review feedback:
- Nested params structs now use typed UnsetFields slices
  (e.g. []AccountBusinessProfileParamsUnsetField) instead of []string,
  so callers don't need to cast. Root structs still use []string via
  the embedded Params struct.
- Add BenchmarkCollectAllUnsetFields (~880ns/op, 3 allocs on M3 Pro).
- Fix misleading test name: UnsetFields overrides set values, not
  "does not override."

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
Add a deeply_nested sub-benchmark that populates PaymentMethodOptions,
PaymentMethodData, and Shipping with multiple levels of nesting to
stress-test the recursive reflection walk.

Results on M3 Pro:
  minimal:       ~900ns/op,  64 B/op,  3 allocs
  deeply_nested: ~4.5µs/op, 224 B/op, 13 allocs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
Copy link
Copy Markdown
Contributor

@mbroshi-stripe mbroshi-stripe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Thanks for taking this on! Please link to #1556 also, which I believe this resolves.

jar-stripe and others added 4 commits March 24, 2026 13:34
Remove UnsetFields []string and AddUnsetField from the base Params
struct. Every params struct (root and nested) now has its own typed
UnsetFields field and AddUnsetField method, generated by codegen. This
eliminates the untyped []string on Params and makes the API consistent
across root and nested structs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
Extend collectAllUnsetFields to recurse into slice/array elements,
using numeric indices as path segments. Update setNestedNull to handle
array indices when patching nulls into v2 JSON output. For v1 form,
form.FormatKey already handles numeric path segments with bracket
notation (e.g. line_items[1][tax_rates]=).

Add tests for slice element UnsetFields:
- Unit test (marshalV2JSON with slice element unset)
- v1 form integration test (bracket notation encoding)
- v2 JSON integration test (null in array element)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
@jar-stripe jar-stripe merged commit 91aa7f7 into master Mar 24, 2026
12 checks passed
@jar-stripe jar-stripe deleted the jar/explicit-null-v2 branch March 24, 2026 21:22
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.

3 participants