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

The collection type 'Newtonsoft.Json.Linq.JObject' on 'Stripe.Checkout.Session.RawJObject' is not supported. #1979

Open sstalder opened 4 years ago

sstalder commented 4 years ago

Stripe v35.11.0

1970

This breaks my dotnet core 3 project not using Newtonsoft.Json as the serializer, and I would assume anyone using System.Text.Json.

This is due to the JsonIgnore attribute from Newtonsoft not being used by System.Text.Json as it has it's own attribute.

There is some more information under this section on the migration guide: https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to#conditionally-ignore-a-property

An unhandled exception has occurred while executing the request. System.NotSupportedException: The collection type 'Newtonsoft.Json.Linq.JObject' on 'Stripe.Checkout.Session.RawJObject' is not supported. at System.Text.Json.JsonPropertyInfoNotNullable`4.GetDictionaryKeyAndValueFromGenericDictionary(WriteStackFrame& writeStackFrame, String& key, Object& value) at System.Text.Json.JsonPropertyInfo.GetDictionaryKeyAndValue(WriteStackFrame& writeStackFrame, String& key, Object& value) at System.Text.Json.JsonSerializer.HandleDictionary(JsonClassInfo elementClassInfo, JsonSerializerOptions options, Utf8JsonWriter writer, WriteStack& state) at System.Text.Json.JsonSerializer.Write(Utf8JsonWriter writer, Int32 originalWriterDepth, Int32 flushThreshold, JsonSerializerOptions options, WriteStack& state) at System.Text.Json.JsonSerializer.WriteAsyncCore(Stream utf8Json, Object value, Type inputType, JsonSerializerOptions options, CancellationToken cancellationToken) at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.gAwaited|29_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters() --- End of stack trace from previous location where exception was thrown --- at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.gAwaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker) at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) at IdentityServer4.Hosting.IdentityServerMiddleware.Invoke(HttpContext context, IEndpointRouter router, IUserSession session, IEventService events) at IdentityServer4.Hosting.MutualTlsTokenEndpointMiddleware.Invoke(HttpContext context, IAuthenticationSchemeProvider schemes) at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) at IdentityServer4.Hosting.BaseUrlMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.MigrationsEndPointMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext) at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

remi-stripe commented 4 years ago

@sstalder Thanks for the report! ~We shipped a fix in 35.11.1 for that error so if you upgrade the error should go away!~

EDIT: Sorry it looks like your issue is different and we're looking into it.

sstalder commented 4 years ago

Sorry I should of also gave an example. This happens when simply trying to serialize a Session object from a controller.

return Json(session);

ob-stripe commented 4 years ago

Thanks for the report @sstalder, and sorry for the trouble.

My take on the issue is that it's expected that StripeEntity objects cannot be reliably serialized with different JSON serialization libraries than the one used by the Stripe.net library itself (i.e. Newtonsoft.Json). We do provide clean abstractions to serialize and deserialize objects without having to worry about the underlying JSON library:

var e = // some StripeEntity object
var json = e.ToJson();
var deserialized = StripeEntity.FromJson(json);

That said, System.Text.Json is not just any "random" JSON library -- it looks like it's in a good place to become the new standard JSON library for modern .NET apps. We aren't quite ready to make the switch from Newtonsoft.Json to System.Text.Json in Stripe.net itself, though we may do so in the future.

In the meantime, I think you'd have to write a custom converter to be able to serialize StripeEntity objects using System.Text.Json. Something along these lines:

namespace YourApp
{
    public class StripeEntityConverter : JsonConverter<StripeEntity>
    {
        public override StripeEntity Read(
            ref Utf8JsonReader reader,
            Type typeToConvert,
            JsonSerializerOptions options) =>
                StripeEntity.FromJson(reader.GetString());

        public override void Write(
            Utf8JsonWriter writer,
            StripeEntity stripeEntity,
            JsonSerializerOptions options) =>
                writer.WriteString(stripeEntity.ToJson());
    }
}

Would that work for you?

sstalder commented 4 years ago

I wasn't aware of the json methods you had, thank you for that. I think the converter is a good alternative in the mean time. I completely understand not 100% supporting a somewhat new json library. Thanks for the quick assistance!

ob-stripe commented 4 years ago

Glad I could help. I'm going to keep this issue open for the time being, and we'll spend some time investigating other possible solutions -- at the very least, we can document this somewhere so users of System.Text.Json know what to expect.

If you could report here to confirm whether the custom converter solution works for you or not, I'd really appreciate it too.

sstalder commented 4 years ago

It looks like the custom converter does work, if anyone needs to go that route in the meantime.

ghost commented 4 years ago

I've just run into this as well. Any update here?

kaby2201 commented 4 years ago

Stripe API's return is a JSON object with sake_case properties.

services.AddControllersWithViews().AddNewtonsoftJson(options =>
{
  options.SerializerSettings.ContractResolver = new DefaultContractResolver 
   {
         NamingStrategy = new SnakeCaseNamingStrategy()
    };
 });
shawn-msft commented 3 years ago

@ob-stripe Thanks a lot for mentioning this! We just encountered the same problem in serialization and will try that custom json converter

clement911 commented 3 years ago

Wouldn't it be a better idea to return the following from the controller action?

return Content(stripeEntity.ToJson(), "application/json");

This way it would keep working even if/when Stripe.net migrates to System.Text.Json

joffnerd commented 3 years ago

Hi, I think I am having a similar issue. Is there any updates on this? I have a net core 5 site using Microsoft.AspNetCore.Mvc.NewtonsoftJson

Trying to read the json posted via webhook which used to work on core 3.1

var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync(); Event stripeEvent = EventUtility.ParseEvent(json, true);

I get: Object reference not set to an instance of an object.

This exception was originally thrown at this call stack: Stripe.Infrastructure.EventConverter.ReadJson(Newtonsoft.Json.JsonReader, System.Type, object, Newtonsoft.Json.JsonSerializer) Newtonsoft.Json.Serialization.JsonSerializerInternalReader.DeserializeConvertable(Newtonsoft.Json.JsonConverter, Newtonsoft.Json.JsonReader, System.Type, object) Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(Newtonsoft.Json.JsonReader, System.Type, bool)

umaknow-louis commented 3 years ago

@joffnerd , The json you are trying to parse probably not doesn't have "object": "event", as a property. That was my proble and using the right Json fixed my issue. The id should begin with "evt_".

jaybo commented 2 years ago

Looks like this topic has been kicking around for two years now, with no clear guidance on using Stripe, System.Text.Json along with Newtonsoft.Json in a webapp. I'm just beginning to integrate stripe in an existing dotnetcore v6 website and have thus far avoided using Newtonsoft.Json. What I assumed would be a simple integration has begun to look a bit, er, daunting.

Q1. Snake case naming

services.AddControllersWithViews().AddNewtonsoftJson(options =>
{
  options.SerializerSettings.ContractResolver = new DefaultContractResolver 
   {
         NamingStrategy = new SnakeCaseNamingStrategy()
    };
 });

Won't this globally mess up my existing controllers which don't use snake case naming? Or is there an alternative way to link Newtonsoft.Json to just the payment controller? This link implies that it's not possible: https://github.com/dotnet/aspnetcore/issues/20630

Q2. This post explains how to use Newtonsoft.Json and System.Text.Json side-by-side but the implementation doesn't look trivial: https://blogs.taiga.nl/martijn/2020/05/28/system-text-json-and-newtonsoft-json-side-by-side-in-asp-net-core/

Do I really need to jump through this hoop?

Much Later... Here's a relatively untested attribute based approach, will report back if it works in all cases: https://gist.github.com/jaybo/e17bea7e9ec7d252dc000412bdb2ddaf