Skip to content

Migrate core deserialization and default JSON library to System.Text.Json#3327

Merged
jar-stripe merged 5 commits intomasterfrom
jar/stj-migration-phase1
Mar 19, 2026
Merged

Migrate core deserialization and default JSON library to System.Text.Json#3327
jar-stripe merged 5 commits intomasterfrom
jar/stj-migration-phase1

Conversation

@jar-stripe
Copy link
Copy Markdown
Contributor

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

Why?

stripe-dotnet currently uses Newtonsoft.Json as its primary JSON serializer but has carried System.Text.Json (STJ) attributes in parallel since 2024. This PR promotes STJ becomes the primary serializer for all request serialization, response deserialization, and public ToJson() output, and is implemented so that STJ will serialize and deserialize correctly without relying on custom options.

Newtonsoft.Json remains available for backward compatibility (ToJson() previously used Newtonsoft; RawJObject is still populated).

What?

  • Remove #if NET6_0_OR_GREATER conditional compilation gates from STJ attributes and adds backfill for STJ on .NET 4.6.2 and .net 5.
  • Swap the primary serialization engine from Newtonsoft to STJ in StripeClient, ApiRequestor, and EventUtility
  • Create public STJStripeEntityConverter and STJStripeOptionsConverter as JsonConverterFactory classes that every entity/options class references via [JsonConverter]
  • Refactor STJDefaultConverter<T> to be the shared read/write implementation used by both factory converters
  • Add [JsonExtensionData] support to STJDefaultConverter for ExtraParams on BaseOptions
  • Add DecimalStringConverter (Newtonsoft) for decimal_string fields — writes decimals as JSON strings to match STJ's [JsonNumberHandling] behavior
  • Add Int64StringConverter (Newtonsoft) for int64_string fields — same pattern as above (cherry-picked from PR Add runtime support for V2 int64 string-encoded fields #3321)
  • Add [JsonConverter(typeof(STJEventConverter))] to Event class for pre-2017 event compatibility
  • Update ToJson() to use STJ instead of Newtonsoft
  • Remove STJMemberSerializationOptIn and related infrastructure (no longer needed with per-type converters)
  • Add [JsonConverter(typeof(STJStripeEntityConverter))] to all entity classes
  • Add [JsonConverter(typeof(STJStripeOptionsConverter))] to all options classes

This PR also fixes an issue with serializing Stripe's decimal strings

  • Add [JsonConverter(typeof(DecimalStringConverter))] on decimal_string Newtonsoft properties

Tests:

  • Update tests to primarily use STJ serialization
  • Wholesome test NewtonsoftAndSystemTextJsonOutputTheSameObject validates parity between serializers

See Also

Changelog

  • ⚠️ System.Text.Json replaces Newtonsoft Json.NET as the default JSON library used in serialization and deserialization of Stripe.net objects. This is most likely non-breaking for most users.
  • ⚠️ Serializing Stripe objects using either System.Text.Json or Newtonsoft Json.NET now represents decimal-format strings as JSON string values to match the Stripe API format.

@jar-stripe jar-stripe changed the title STJ migration phase 1: engine swap to System.Text.Json Migrate core deserialization and default JSON library to System.Text.Json Mar 18, 2026
@jar-stripe jar-stripe marked this pull request as ready for review March 18, 2026 23:21
@jar-stripe jar-stripe requested a review from a team as a code owner March 18, 2026 23:21
@jar-stripe jar-stripe requested review from mbroshi-stripe and removed request for a team March 18, 2026 23:21
@jar-stripe jar-stripe enabled auto-merge (squash) March 18, 2026 23:40
@jar-stripe
Copy link
Copy Markdown
Contributor Author

@mbroshi-stripe I'm working to rebuild the commit history so this is less insane to review

jar-stripe and others added 2 commits March 18, 2026 18:15
…hanges

System.Text.Json migration for stripe-dotnet. This commit contains all
hand-written (non-generated) changes:

- Add STJ package references and multi-TFM support
- STJ converter implementations (AnyOf, Expandable, DateTime, Enum, etc.)
- Engine swap: deserialize/serialize paths now use System.Text.Json
- Base entity/service/options classes updated with STJ attributes
- Hand-written entity and service classes updated with STJ attributes
- Request serialization migrated to STJ
- All tests updated for STJ as primary deserializer
- Wholesome tests for STJ attribute coverage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
Machine-generated changes from OpenAPI spec codegen. All files in this
commit contain the "File generated from our OpenAPI spec" header.

Adds System.Text.Json attributes ([JsonPropertyName], [JsonConverter],
etc.) to all generated entity and service option classes.

This commit can be skipped during review — the hand-written infrastructure
that drives these attributes is in the previous commit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
@jar-stripe jar-stripe force-pushed the jar/stj-migration-phase1 branch from bc5bab4 to 2cbf0c6 Compare March 19, 2026 01:29
…hase1

Committed-By-Agent: claude

# Conflicts:
#	src/Stripe.net/Entities/Charges/ChargePaymentMethodDetailsCardPresent.cs
#	src/Stripe.net/Entities/Charges/ChargePaymentMethodDetailsInteracPresent.cs
#	src/Stripe.net/Entities/ConfirmationTokens/ConfirmationTokenPaymentMethodPreviewCardGeneratedFromPaymentMethodDetailsCardPresent.cs
#	src/Stripe.net/Entities/Mandates/MandatePaymentMethodDetailsBacsDebit.cs
#	src/Stripe.net/Entities/PaymentAttemptRecords/PaymentAttemptRecordPaymentMethodDetailsCardPresent.cs
#	src/Stripe.net/Entities/PaymentAttemptRecords/PaymentAttemptRecordPaymentMethodDetailsInteracPresent.cs
#	src/Stripe.net/Entities/PaymentIntents/PaymentIntentPaymentMethodOptionsUsBankAccount.cs
#	src/Stripe.net/Entities/PaymentMethods/PaymentMethodCardGeneratedFromPaymentMethodDetailsCardPresent.cs
#	src/Stripe.net/Entities/PaymentRecords/PaymentRecordPaymentMethodDetailsCardPresent.cs
#	src/Stripe.net/Entities/PaymentRecords/PaymentRecordPaymentMethodDetailsInteracPresent.cs
#	src/Stripe.net/Entities/Reserve/Holds/Hold.cs
#	src/Stripe.net/Entities/Reserve/Holds/HoldReleaseSchedule.cs
#	src/Stripe.net/Entities/Reserve/Plans/Plan.cs
#	src/Stripe.net/Entities/Reserve/Plans/PlanFixedRelease.cs
#	src/Stripe.net/Entities/Reserve/Plans/PlanRollingRelease.cs
#	src/Stripe.net/Entities/Reserve/Releases/Release.cs
#	src/Stripe.net/Entities/Reserve/Releases/ReleaseSourceTransaction.cs
#	src/Stripe.net/Entities/Tax/Registrations/RegistrationCountryOptions.cs
#	src/Stripe.net/Entities/Tax/Registrations/RegistrationCountryOptionsLk.cs
#	src/Stripe.net/Entities/Terminal/Configurations/Configuration.cs
#	src/Stripe.net/Entities/Terminal/Configurations/ConfigurationCellular.cs
#	src/Stripe.net/Entities/Terminal/Configurations/ConfigurationStripeS710.cs
#	src/Stripe.net/Entities/V2/Billing/MeterEventAdjustments/MeterEventAdjustment.cs
#	src/Stripe.net/Entities/V2/Billing/MeterEventSessions/MeterEventSession.cs
#	src/Stripe.net/Services/PaymentIntents/PaymentIntentPaymentMethodOptionsUsBankAccountOptions.cs
#	src/Stripe.net/Services/PaymentLinks/PaymentLinkUpdateOptions.cs
#	src/Stripe.net/Services/SubscriptionItems/SubscriptionItemDeleteOptions.cs
#	src/Stripe.net/Services/Tax/Registrations/RegistrationCountryOptionsLkOptions.cs
#	src/Stripe.net/Services/Tax/Registrations/RegistrationCountryOptionsOptions.cs
#	src/Stripe.net/Services/Terminal/Configurations/ConfigurationCellularOptions.cs
#	src/Stripe.net/Services/Terminal/Configurations/ConfigurationCreateOptions.cs
#	src/Stripe.net/Services/Terminal/Configurations/ConfigurationStripeS710Options.cs
#	src/Stripe.net/Services/Terminal/Configurations/ConfigurationUpdateOptions.cs
@jar-stripe
Copy link
Copy Markdown
Contributor Author

Ok this should be good to review; the first commit is all the manual changes, the second commit is generated code, and then there is some clean up to fix tests after that.

…d-trip test

Delete NewtonsoftAndSystemTextJsonDeserializeTheSameObject which iterated
over ~4700 entity types with reflection-heavy object population, causing
CI timeouts (7+ minutes, killed by SIGTERM).

Replace with a focused test in DeserializationTest that exercises every
property type pattern (string, long, bool, decimal, decimal_string,
DateTime, nested object, list, expandable field, map, StripeList) through
both STJ and Newtonsoft deserialization and verifies equivalence. Add
DecimalStringConverter attributes to close the one coverage gap.

Other wholesome tests already verify all types have correct STJ attributes
(PropertiesHaveAllNecessaryJsonAttributes, CorrectSystemTextJsonConvertersForTypes)
and that serialization output matches (OutputTheSameObject).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Committed-By-Agent: claude
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.

2 participants