stripe / stripe-dotnet

Stripe.net is a sync/async .NET 4.6.1+ client, and a portable class library for stripe.com.
Apache License 2.0
1.36k stars 571 forks source link

Support JSON serialization with System.Text.Json #2495

Open cecilphillip-stripe opened 2 years ago

cecilphillip-stripe commented 2 years ago

Is your feature request related to a problem? Please describe.

Stripe.NET takes a hard dependency on the Netwonsoft.Json serializer which is fine for some cases. Starting with ASP.NET Core 3.0, the default JSON serializer has changed to System.Text.JSON.

For ASP.NET Core applications running on v3.0+ that use our Stripe.NET nuget package, developers have to be concerned with the inconsistencies between the serializers when working with Stripe Objects like Products, Customers, etc.

Serialized single Stripe Product w/ Newtonsoft

{
  "object": "product",
  "active": true,
  "attributes": [],
  "caption": null,
  "created": 1650997792,
  "deactivate_on": null,
  "default_price": null,
  "description": "This is such an awesome product made out of Metal",
  "images": [
    "https://picsum.photos/640/480/?image=279"
  ],
  "livemode": false,
  "metadata": {
    "code": "XXFYF3V"
  },
  "name": "Gorgeous Plastic Pants",
  "package_dimensions": null,
  "shippable": true,
  "statement_descriptor": null,
  "tax_code": null,
  "type": "service",
  "unit_label": null,
  "updated": 1650997792,
  "url": null
}

Serialized single Stripe Product w/ System.Text.Json

{
  "Object": "product",
  "Active": true,
  "Attributes": [],
  "Caption": null,
  "Created": "2022-04-26T18:29:52Z",
  "DeactivateOn": null,
  "DefaultPriceId": null,
  "DefaultPrice": null,
  "Deleted": null,
  "Description": "This is such an awesome product made out of Metal",
  "Images": [
    "https://picsum.photos/640/480/?image=279"
  ],
  "Livemode": false,
  "Metadata": {
    "code": "XXFYF3V"
  },
  "Name": "Gorgeous Plastic Pants",
  "PackageDimensions": null,
  "Shippable": true,
  "StatementDescriptor": null,
  "TaxCodeId": null,
  "TaxCode": null,
  "Type": "service",
  "UnitLabel": null,
  "Updated": "2022-04-26T18:29:52Z",
  "Url": null,
  "RawJObject": null,
  "StripeResponse": null
}

You'll notice that the expected casing is different as well as the number of returned properties. Below are examples of serializing a StripeList

Serialized StripeList of Product w/ Newtonsoft.Json

{
  "object": "list",
  "data": [
    {
      "id": "prod_La3FD0M3LXeUPE",
      "object": "product",
      "active": true,
      "attributes": [],
      "caption": null,
      "created": 1650997792,
      "deactivate_on": null,
      "default_price": null,
      "description": "This is such an awesome product made out of Metal",
      "images": [
        "https://picsum.photos/640/480/?image=279"
      ],
      "livemode": false,
      "metadata": {
        "code": "XXFYF3V"
      },
      "name": "Gorgeous Plastic Pants",
      "package_dimensions": null,
      "shippable": true,
      "statement_descriptor": null,
...
  ],
  "has_more": true,
  "url": "/v1/products"
}

Serialized StripeList of Product w/ System.text.json

[
  {
    "Id": "prod_La3FD0M3LXeUPE",
    "Object": "product",
    "Active": true,
    "Attributes": [],
    "Caption": null,
    "Created": "2022-04-26T18:29:52Z",
    "DeactivateOn": null,
    "DefaultPriceId": null,
    "DefaultPrice": null,
    "Deleted": null,
    "Description": "This is such an awesome product made out of Metal",
    "Images": [
      "https://picsum.photos/640/480/?image=279"
    ],
    "Livemode": false,
    "Metadata": {
      "code": "XXFYF3V"
    },
    "Name": "Gorgeous Plastic Pants",
    "PackageDimensions": null,
    "Shippable": true,
    "StatementDescriptor": null,
...
    "RawJObject": null,
    "StripeResponse": null
  }
]

With lists you can see the data nesting between the serializers are also different.

Describe the solution you'd like

Since System.Text.JSON is the default serializer for asp.net core web applications, It would be great if there could be some consistency with the expected responses when serializing/deserializing Stripe.NET object.

Can we have Stripe.NET use System.Text.JSON if available and fallback to Newtonsoft.JSON? Otherwise, I've seen other package authors create separate packages for each serializer but I'm not sure if we'd want to do that here.

Describe alternatives you've considered

This can be mitigated by using the setting Newtonsoft.Json as the default serializer by using the Microsoft.AspNetCore.Mvc.NewtonsoftJson package.

Another option would be to return a custom response type instead of the plain Stripe object. For example, see here.

Additional context

No response

clement911 commented 2 years ago

I'm not sure if you're aware but every stripe entity in the Stripe.Net lib inherits from the StripeEntity base class, which has a ToJson and FromJson methods. This way if tomorrow Stripe.Net starts using System.Text.Json, these methods should keep working as they do now.

cecilphillip-stripe commented 2 years ago

@clement911 all the Stripe objects use attributes from Newtonsoft.json => example.

This would have to be updated if another serializer is adopted so that the shape of the payload is consistent.

emorell96 commented 2 years ago

I've been porting this library to System.Text.Json these last few days, it has quite its challenges, specially how much polymorphism is used which is always a weak point for STJ but I have been able to get most of it working on my branch:

https://github.com/StockDrops/stripe-dotnet/tree/without-stripe-entity

Would you all be interested in a PR for a future version or at least some discussion on how to better port it? Specially if you ever decide to do the switch, it might come in handy to have most of the converters out of the way.

richardm-stripe commented 1 year ago

Hi @emorell96, sorry for the delayed response. Thank you for preparing that pull request!

cc @cecilphillip-stripe, I think this is worth discussing. I want to specifically talk about whether we could do this non-breakingly? If not; how disruptive will this change be to users who are upgrading? Is it possible that changing default serialization libraries would break them in subtle ways?

cecilphillip-stripe commented 1 year ago

@richardm-stripe started a conversation with some folks on this. There's also a migration document we can reference.

To not break existing users, we'd have to find a way to support both and gradually migrate away.

AraHaan commented 1 year ago

I think a better option would be the following:

For used Newtonsoft.Json attributes:

Alternatively, one could look into making ASP.NET Framework use System.Text.Json instead as well so then it can be unified for both cases too.

iamcarbon commented 1 year ago

To facilitate a migration, would it be possible to double up attributes / serialization converters and begin using System.Text.Json for all internal serialization methods?

AraHaan commented 1 year ago

I guess another option would be to define an interface in stripe-dotnet and then have separate json "plugins" that implement said interface for newtonsoft and system.text.json that then get loaded at runtime depending on what the user determine that they want to use (it gets loaded into something similar to DependencyInjection but in a way usable in all of the supported frameworks of .NET)

ismkdc commented 7 months ago

We definitely need system.text.json version of stripe.net

btecu commented 7 months ago

Any news? Would rather use System.Text.Json than Newtonsoft.Json, which has not seen a release in almost a year.

ramya-stripe commented 4 months ago

Update: We will be revisiting this in August.