Search code examples
asp.netgraphqlhotchocolate

Serialize an IDictionary<string, object> with GraphQL and Hot Choclate to a dynamic JSON


I'm adding Hot Chocolate(GraphQL) to an existing ASP.Net Core project with a Web API and reusing the models that are used by the Web API. One of the models has an IDictionary<string, object> property that is serialized into a dynamic JSON with Web API.

The Model

public class Theme
{
    ...
    public IDictionary<string, object> Styles { get; set; }
    ...
}

that with the Web API can be serialized to

{
  ...
  styles: {
    header: {
      color: "#fff", 
      background: "rgba(255, 255, 255, 0)", 
      boxShadow: "", position: "fixed"},
      ...
    },
    borderRadius: "4px",
    ...
  }
  ...
}

With Hot Chocolate, I'm reusing the model and adds GraphQL in startup with

services.AddGraphQL(sp => SchemaBuilder.New()
    .AddServices(sp)
    .AddQueryType(d => d.Name("Query"))
    ...
    .AddType<Theme>()
    .Create());

The schema that is generated becomes

type Theme {
  ...
  styles: [KeyValuePairOfStringAndObject!]
  ...
}

and only key can be retrieved

{
  themes(...) {
    edges {
      node {
        name
        styles{
          key
        }
      }
    }
  }
}

with the following response

{
  "themes": {
    "edges": [
      {
        "node": {
          ...
          "styles": [
            {
              "key": "header"
            },
            {
              "key": "borderRadius"
            },
            ...
          ]
        }
      }
    ]
  }
}

I would like to set up Hot Chocolate to make it possible to write a GraphQL query that gives the same dynamic result as the Web API.

UPDATE

The source is a JSON string that is deserialized to a Dictionary<string, object> with

var jsonString = "{\"header\":{\"color\":\"#fff\"},\"borderRadius\":\"4px\"}";

theme.Styles = JsonConvert.DeserializeObject<Dictionary<string, object>>(jsonString);

The problem is that the resulting dictionary isn't recursively serialized to a Dictionary<string, object>.


Solution

  • Hot Chocolate has an AnyType for dynamic data structures. But the AnyType by default is not implicitly inferred from types.

    You basically have two options here. Either declare the type on your type with an attribute (or do the same with the fluent API).

    Pure Code-First:

    public class Theme
    {
        ...
        [GraphQLType(typeof(AnyType))]
        public IDictionary<string, object> Styles { get; set; }
        ...
    }
    

    Code-First with Schema Types:

    public class ThemeType : ObjectType<Theme> 
    {
        protected override void Configure(IObjectTypeDescriptor<Theme> descriptor)
        {
            descriptor.Field(t => t.Styles).Type<AnyType>();
        }
    }
    

    Alternatively, you can say that in your schema all IDictionary<string, object> are any types in which case you do not need to explicitly define the type:

    services.AddGraphQL(sp => SchemaBuilder.New()
        .AddServices(sp)
        .AddQueryType(d => d.Name("Query"))
        ...
        .AddType<Theme>()
        .BindClrType<IDictionary<string, object>, AnyType>()
        .Create());
    

    I hope this helps.