graphql-platform icon indicating copy to clipboard operation
graphql-platform copied to clipboard

Resolver for objects derived from dictionaries/lists implicitly applies a list conversion

Open AlexanderInova opened this issue 1 year ago • 0 comments

Product

Hot Chocolate

Version

14.2.0

Link to minimal reproduction

https://github.com/AlexanderInova/HotChocolateDictIssue

Steps to reproduce

  1. Create a class that derives from Dictionary<string, object>:
    public class Metadata : Dictionary<string, object>
    {
      // ...
    }
    
  2. Create a GraphQL object type for that class and specify some fields:
    [ObjectType]
    public sealed class MetadataType : ObjectType<Metadata>
    {
      protected override void Configure(IObjectTypeDescriptor<Metadata> descriptor)
      {
        descriptor
          .BindFieldsExplicitly();
    
        descriptor.Field("all")
          .Type<NonNullType<ListType<NonNullType<MetadataEntryType>>>>()
          .Resolve(ctx => ctx.Parent<Metadata>().Select(e => new MetadataEntry(e.Key, e.Value)));
    
        descriptor.Field("value")
          .Argument("key", a => a.Type<NonNullType<StringType>>())
          .Type<JsonType>()
          .Resolve(ctx => ctx.Parent<Metadata>().TryGetValue(ctx.ArgumentValue<string>("key"), out var value) ? value : null);
      }
    }
    
    public record MetadataEntry(string Key, object? Value);
    
    [ObjectType]
    public sealed class MetadataEntryType : ObjectType<MetadataEntry>
    {
      protected override void Configure(IObjectTypeDescriptor<MetadataEntry> descriptor)
      {
        // ...
      }
    }
    
  3. Try to access the new field:
    query {
      metadata {
        all {
          key
          value
        }
    
        key1: value(key: "key1")
        key2: value(key: "key2")
        key3: value(key: "key3")
      }
    }
    

What is expected?

The specified resolver is called once per Metadata object (in this example) and successfully receives the actual object (as Metadata in this example) by calling ctx.Parent<Metadata>().

It is not automatically resolved as a list, which would violate the specified schema.

What is actually happening?

The specified resolver is invoked for each element of the enumerable object and receives System.Collections.Generic.KeyValuePair`2[System.String,System.Object] (in this example) by calling ctx.Parent<Metadata>()

Relevant log output

Exception has occurred: CLR/HotChocolate.GraphQLException
An exception of type 'HotChocolate.GraphQLException' occurred in HotChocolate.Execution.dll but was not handled in user code: 'The resolver parent type of field `Metadata.value` is `System.Collections.Generic.KeyValuePair`2[System.String,System.Object]` but the resolver requested the type `HotChocolateIssue.Metadata`. The resolver was unable to cast the parent type to the requested type.'
   at HotChocolate.Execution.Processing.MiddlewareContext.Parent[T]()
   at HotChocolateIssue.MetadataType.<>c.<Configure>b__0_2(IResolverContext ctx) in /workspaces/HotChocolateDictIssue/HotChocolateIssue/MetadataType.cs:line 24
   at HotChocolate.Types.ResolveObjectFieldDescriptorExtensions.<>c__DisplayClass0_0.<Resolve>b__0(IResolverContext ctx)
   at HotChocolate.Types.Helpers.FieldMiddlewareCompiler.<>c__DisplayClass9_0.<<CreateResolverMiddleware>b__0>d.MoveNext()

Additional context

No response

AlexanderInova avatar Jan 09 '25 09:01 AlexanderInova