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

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

Closed
mike-a-stephens opened this issue Aug 12, 2024 · 9 comments

Comments

@mike-a-stephens
Copy link

mike-a-stephens commented Aug 12, 2024

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.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.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.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.g__ConfigureSynchronized|172_0()
--- End of stack trace from previous location ---
at System.Text.Json.Serialization.Metadata.JsonTypeInfo.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 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<>() ?

@clement911
Copy link
Collaborator

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

@mike-a-stephens
Copy link
Author

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

mike-a-stephens commented Aug 14, 2024

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.

@nozzlegear
Copy link
Owner

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.

@RobJDavey
Copy link

RobJDavey commented Oct 2, 2024

@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?

@nozzlegear
Copy link
Owner

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.

@clement911
Copy link
Collaborator

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

Fixed by #1101

@clement911
Copy link
Collaborator

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

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

No branches or pull requests

4 participants