Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
sstalder opened this issue Apr 3, 2020 · 13 comments
Assignees
Labels

Comments

@sstalder
Copy link

sstalder commented Apr 3, 2020

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.g__Awaited|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.g__Awaited|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
Copy link
Contributor

remi-stripe commented Apr 3, 2020

@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
Copy link
Author

sstalder commented Apr 3, 2020

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
Copy link
Contributor

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
Copy link
Author

sstalder commented Apr 3, 2020

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
Copy link
Contributor

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
Copy link
Author

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

@ghost
Copy link

ghost commented Jul 24, 2020

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

@kaby2201
Copy link

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
Copy link

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

@clement911
Copy link
Contributor

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
Copy link

joffnerd commented Feb 5, 2021

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
Copy link

@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
Copy link

jaybo commented May 5, 2022

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

9 participants