Skip to content

Preserve null values in v2 JSON request bodies#2042

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

Preserve null values in v2 JSON request bodies#2042
jar-stripe merged 8 commits intomasterfrom
jar/explicit-null-v2

Conversation

@jar-stripe
Copy link
Copy Markdown
Contributor

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

Why?

v2 APIs use JSON-encoded POST bodies where null has semantic meaning — it signals the intent to clear/unset a field. Two issues prevented this from working:

  1. AbstractService::formatParams() converted all null values to empty strings, which is correct for v1 but strips null semantics for v2
  2. Util::objectsToIds() stripped null values from params entirely before they could reach the JSON encoder

This was identified as part of an audit across all 7 Stripe language SDKs (DEVSDK-2926) to verify explicit null handling. PHP was one of four languages where v2 null serialization was not working correctly.

What?

  • Modified AbstractService::formatParams() to accept a required $apiMode parameter — for v2, null values are preserved; for v1, nulls are converted to empty strings for form encoding
  • Modified Util::objectsToIds() to accept a required $serializeEmpty parameter — when true (v2 POST), null values in associative arrays are preserved instead of stripped
  • CurlClient::constructUrlAndBody() passes $serializeEmpty = true for v2 POST requests
  • Fixed edge case where an associative array with all values stripped would json_encode as [] instead of {} — cast to stdClass to preserve the object type
  • Updated all callers to derive API mode from the request path and pass it explicitly
  • Added 14 new tests covering null preservation, null stripping, nested nulls, metadata key deletion, and end-to-end request integration for both API modes

Changelog

  • The SDK now preserves and sends null when set in V2 API metadata and params, enabling you to clear metadata entries and some unsettable properties for V2 APIs.
  • ⚠️ The Util::objectsToIds() method now has a required $serializeNull parameter to indicate if null values set in the object should be output in the resulting hash. This is relevant for V2 POST APIs to let callers clear emptyable values.

See Also

  • DEVSDK-2926: Cross-SDK emptyable field support audit

For v2 APIs (JSON-encoded POST), null has semantic meaning — it clears
a field. Previously, formatParams() converted all null values to empty
strings regardless of API version, making it impossible to send explicit
null in v2 requests.

formatParams() now accepts an $apiMode parameter. For v2, null values
are preserved so json_encode produces "field": null. For v1, the
existing null-to-empty-string behavior is unchanged.

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 Copilot and mbroshi-stripe and removed request for a team March 20, 2026 17:44
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

Updates Stripe PHP’s service-layer param formatting so null values keep their semantic meaning for v2 JSON request bodies (clear/unset), while preserving existing v1 form-encoding behavior.

Changes:

  • Extend AbstractService::formatParams() with an apiMode argument to preserve null for v2 and convert null -> '' for v1.
  • Update service request helpers (request, requestStream, requestCollection, requestSearchResult) to derive API mode from the path and pass it through.
  • Add tests validating v1/v2 null handling, including nested nulls and request integration behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
lib/Service/AbstractService.php Adds API-mode-aware param formatting and threads mode through request helper methods.
tests/Stripe/Service/AbstractServiceTest.php Adds coverage for v1 null stringification vs v2 null preservation and request-level assertions.

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

Comment thread tests/Stripe/Service/AbstractServiceTest.php
Comment thread tests/Stripe/Service/AbstractServiceTest.php Outdated
Comment thread tests/Stripe/Service/AbstractServiceTest.php
jar-stripe and others added 4 commits March 23, 2026 19:48
objectsToIds() was stripping null values from params before they could
reach the JSON encoder, defeating v2 explicit null handling. Add a
$serializeEmpty parameter that preserves nulls in associative arrays
when true (used for v2 POST bodies).

Also fixes an edge case where an associative array with all values
stripped would json_encode as [] instead of {} — cast to stdClass to
preserve the object type.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
Make callers explicitly specify v1/v2 API mode and null handling
behavior instead of silently defaulting. This is a breaking change
for external callers of Util::objectsToIds() (public method) — the
$serializeEmpty parameter no longer defaults to false.

Also fix docstring: V2 POST/PUT/PATCH → V2 POST (we don't support
PUT or PATCH).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
More accurately describes what the parameter controls — whether null
values are preserved in the output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@jar-stripe jar-stripe requested review from prathmesh-stripe and removed request for mbroshi-stripe March 24, 2026 04:49
The default was removed from formatParams() to require callers to
specify v1 or v2. The original test was missing the apiMode argument.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
@jar-stripe jar-stripe enabled auto-merge (squash) March 24, 2026 21:05
@jar-stripe jar-stripe disabled auto-merge March 24, 2026 21:07
@jar-stripe jar-stripe enabled auto-merge (squash) March 24, 2026 21:07
assertArrayHasKey must come before assertNull to avoid a confusing
'undefined index' notice if the key is absent. Addresses PR review comment.

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