ShopifySharp icon indicating copy to clipboard operation
ShopifySharp copied to clipboard

GraphQL Mutation Result Deserialization Issues (When Using System.Text.Json.JsonSerializer)

Open mike-a-stephens opened this issue 1 year ago • 4 comments

When deserializing the response from a GraphQL mutation, System.Text.Json.JsonSerializer is throwing an exception, whereas NetwtonSoft can deserialize the response successfully.

For example, using the following code:

public async Task<ProductCreatePayload> UploadProduct(Article article)
{
    var service = GetGraphService();

    var mutation = @"mutation productCreate($title: String!, $descriptionHtml: String) {
    productCreate(input: {
            title: $title
            descriptionHtml: $descriptionHtml
        }) {
            product {
                id
            }
        }
    }";

    var variables = new
    {
        title = article.Name.Value,
        description = article.FullDescription.Value
    };

    var response = await service.SendAsync(new GraphRequest { query = mutation, variables = variables });
    var ptyElt = response.EnumerateObject().Single().Value;

    var result = JsonConvert.DeserializeObject<ProductCreatePayload>(ptyElt.GetRawText());
    var test = System.Text.Json.JsonSerializer.Deserialize<ProductCreatePayload>(ptyElt.GetRawText());

    return result;
}

The NewtonSoft deserializer succeeds and returns the deserialized object, however System.Text.Json deserializer fails with the following exception:

System.InvalidOperationException HResult=0x80131509 Message=The polymorphic type 'ShopifySharp.GraphQL.INode' has already specified derived type 'ShopifySharp.GraphQL.ReturnLineItem'. Source=System.Text.Json StackTrace: at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_DerivedTypeIsAlreadySpecified(Type baseType, Type derivedType) at System.Text.Json.Serialization.Metadata.PolymorphicTypeResolver..ctor(JsonSerializerOptions options, JsonPolymorphismOptions polymorphismOptions, Type baseType, Boolean converterCanHaveMetadata) at System.Text.Json.Serialization.Metadata.JsonTypeInfo.Configure() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.<EnsureConfigured>g__ConfigureSynchronized|172_0() at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type type, Boolean ensureConfigured, Nullable1 ensureNotNull, Boolean resolveIfMutable, Boolean fallBackToNearestAncestorType) at System.Text.Json.Serialization.Metadata.JsonPropertyInfo.Configure() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.ConfigureProperties() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.Configure() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.<EnsureConfigured>g__ConfigureSynchronized|172_0() at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type type, Boolean ensureConfigured, Nullable1 ensureNotNull, Boolean resolveIfMutable, Boolean fallBackToNearestAncestorType) at System.Text.Json.Serialization.Metadata.JsonPropertyInfo.Configure() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.ConfigureProperties() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.Configure() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.<EnsureConfigured>g__ConfigureSynchronized|172_0() at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type type, Boolean ensureConfigured, Nullable1 ensureNotNull, Boolean resolveIfMutable, Boolean fallBackToNearestAncestorType) at System.Text.Json.Serialization.Metadata.JsonTypeInfo.Configure() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.<EnsureConfigured>g__ConfigureSynchronized|172_0() at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type type, Boolean ensureConfigured, Nullable1 ensureNotNull, Boolean resolveIfMutable, Boolean fallBackToNearestAncestorType) at System.Text.Json.Serialization.Metadata.JsonPropertyInfo.Configure() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.ConfigureProperties() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.Configure() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.<EnsureConfigured>g__ConfigureSynchronized|172_0() at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type type, Boolean ensureConfigured, Nullable1 ensureNotNull, Boolean resolveIfMutable, Boolean fallBackToNearestAncestorType) at System.Text.Json.Serialization.Metadata.JsonPropertyInfo.Configure() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.ConfigureProperties() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.Configure() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.<EnsureConfigured>g__ConfigureSynchronized|172_0() at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type type, Boolean ensureConfigured, Nullable1 ensureNotNull, Boolean resolveIfMutable, Boolean fallBackToNearestAncestorType) at System.Text.Json.Serialization.Metadata.JsonPropertyInfo.Configure() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.ConfigureProperties() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.Configure() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.<EnsureConfigured>g__ConfigureSynchronized|172_0() --- End of stack trace from previous location --- at System.Text.Json.Serialization.Metadata.JsonTypeInfo.<EnsureConfigured>g__ConfigureSynchronized|172_0() at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type type, Boolean ensureConfigured, Nullable`1 ensureNotNull, Boolean resolveIfMutable, Boolean fallBackToNearestAncestorType) at System.Text.Json.JsonSerializerOptions.GetTypeInfoForRootType(Type type, Boolean fallBackToNearestAncestorType) at System.Text.Json.JsonSerializer.GetTypeInfo[T](JsonSerializerOptions options) at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options) : : : : : : : : : : : : : : : : : : : : : : : : : : : :

This appears to be causing the generic ShopifySharp SendAsync<T> methods to fail when processing the response of a mutation.

For example, in my original attempt at implementing the productCreate call, I used code similar to the following:

        return await service.SendAsync<ProductCreatePayload>(new GraphRequest { query = mutation, variables = variables });

but was getting the exception thrown above, which lead me to try deserializing manually with NewtonSoft.

Am I doing something wrong, or is there indeed an issue when using System.Text.Json.JsonSerializer.Deserialize<>() ?

mike-a-stephens avatar Aug 12 '24 00:08 mike-a-stephens

To deserialize automatically, try using the generic overload SendAsync<ProductCreatePayload>.

clement911 avatar Aug 13 '24 00:08 clement911

Referring to my original report (near the bottom), you will see that I originally tried the generic method you mentioned. It was the exception being thrown from this generic method which lead lead me to investigating the issue further. As mentioned, out of the box using the automatic deserialization fails with the exception I posted.

mike-a-stephens avatar Aug 13 '24 05:08 mike-a-stephens

Further to this, it appears that I am only having issues with SendAsync<ProductCreatePayload>(). I just created another API call using SendAsync<ProductVariantsBulkCreatePayload>() and that deserializes correctly and without an exception being thrown.

mike-a-stephens avatar Aug 14 '24 04:08 mike-a-stephens

Thanks for the detailed investigation so far @mike-a-stephens. I'm going to be working on the GraphService in the next day or so, especially picking up an old PR I never finished in #1051. I will be sure to get this fixed and add test cases for this deserialization issue.

nozzlegear avatar Aug 14 '24 05:08 nozzlegear

@nozzlegear The problem is caused by the schema generated file GraphQLSchema.generated.cs.

// ...
[JsonDerivedType(typeof(ReturnLineItem), typeDiscriminator: "ReturnLineItem")]
[JsonDerivedType(typeof(ReturnLineItem), typeDiscriminator: "ReturnLineItem")]
// ...
public interface INode : IGraphQLObject
// ...

The ReturnLineItem has been registered twice as a derived type, which is what causes the error to occur. This means anything that needs to deserialise an INode throws this exception.

I've been unable to upgrade beyond 6.17.0 due to this issue meaning we're stuck on 2024-04 for now. Is it possible to get a point upgrade with this minor change in it?

RobJDavey avatar Oct 02 '24 12:10 RobJDavey

Interesting, thanks for digging in and finding that @RobJDavey. I'm working on the GraphQL service right now and will be regenerating the schema file for 2024-10 as well. I'll double check for that issue and if it's still in there I'll just manually edit it out before releasing, then I'll have to dig in and find out why it's doing that.

nozzlegear avatar Oct 02 '24 17:10 nozzlegear

Good catch. ShopifySharp relies on a separate library to generate GraphQLSchema.generated.cs I have now fixed the issue in that separate library and the issue should get resolved once ShopifySharp upgrades to the latest nuget version of that lib and regenerates the file.

clement911 avatar Oct 04 '24 01:10 clement911

Fixed by https://github.com/nozzlegear/ShopifySharp/pull/1101

clement911 avatar Oct 04 '24 01:10 clement911

This should be fixed in the new pre-release nuget package and will be fixed in the next release nuget.

clement911 avatar Oct 04 '24 02:10 clement911