question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Execution result is unable to convert values to Scalar Types

See original GitHub issue

Summary

We’ve been using custom resolvers to resolve types/fields via http requests. Things have been working well but I encountered an error when trying to resolve Arrays of any Scalar Types during execution.

Here are some of the error messages I am seeing:

  • “Unable to convert ‘1’ to the scalar type ‘Int’”
  • “Unable to convert ‘Anna’ to the scalar type ‘String’”
  • “Unable to convert ‘1.23’ to the scalar type ‘Float’”
  • “Unable to convert ‘True’ to the scalar type ‘Boolean’”

Is there a configuration I missed or an extension I can write to create a generic way of converting values to their desired scalar types?

Just to note: The resolver is able to resolve arrays of object types without issue (I’ll provide more relevant info below), we just seem to be running into an issue with an array of a scalar type.

Relevant information

Sample Schema:

type Query {
  users: [User]
}

type User {
  id: String!
  NAME: String!
  favorite_numbers: [Int] # unable to convert
  favorite_names: [String!]! # unable to convert
  random_flags: [Boolean]! # unable to convert
  previous_transactions: [Float] # unable to convert
  relatives: [User] # converts successfully
}

Sample Query:

query {
	users {
		favorite_names
		favorite_numbers
		previous_transactions
		random_flags
	}
}

We register a visitor in the schema for each type/field in the schema with something like:

//Sample RegisterVistor impl is below
schema.RegisterVisitor(this.customResolverVisitor);

Create and execute Executor

var executor = new DocumentExecuter();
var executionResults = await executor.ExecuteAsync(_ =>
{
 _.Schema = schema;
 _.Query = query;
 _.Variables = inputs;
 _.CancellationToken = cancellation;
});

custom resolver visitor:

//  
public override void VisitObjectFieldDefinition(FieldType field, IObjectGraphType type, ISchema _)
{
    this.AttachResolver(type, field, field.ResolvedType);
}

public void AttachResolver(...)
{
    if (fieldType is NonNullGraphType nonNullType)
    {
        this.AttachResolver(type, field, nonNullType.ResolvedType);
    }
    else if (fieldType is ListGraphType listGraphType)
    {
        field.Resolver = this.GraphQLResolver<JArray>(type, field);
    }
    else if (fieldType is ObjectGraphType)
    {
        field.Resolver = this.GraphQLResolver<JObject>(type, field);
    }
// ... remaining scalar default scalar types like StringGraphType, IntGraphType, FloatGraphType, etc
}

T ChangeToType<T>(string content)
{
    if (content == null || content.Length == 0)
    {
        return default(T);
    }

    T result;
    if (typeof(T) == typeof(JObject))
    {
        result = (T)Convert.ChangeType(JObject.Parse(content), typeof(T));
    }
    else if (typeof(T) == typeof(JArray))
    {
        result = (T)Convert.ChangeType(JArray.Parse(content), typeof(T));
    }
    else
    {
        result = (T)Convert.ChangeType(content, typeof(T));
    }

    return result;
}

FuncFieldResolver<T> GraphQLResolver<T>(IObjectGraphType type, FieldType field)
{
    return new FuncFieldResolver<T>(async context =>
    {
        // we have a flag to determine if an http call should be made to resolve this type/field
        if (shouldUseHttpResolverConfigured) {
          var jsonResponse = await someHttpGetCall();
          return this.ChangeToType<T>(await jsonResponse.GetStringContent());
        } 
        // otherwise we check to see if this is a nested resolver call for a field of a parent type
        // and if the parent exists and has the field we are looking for already in its payload, we parse it and return it
        else if (context.Source != null && context.Source is JObject obj) {
          var token = obj.SelectToken(field.Name);
          if (token != null) {
            return this.ChangeToType<T>(token.ToString());
          }
        }
        else { throwSomeError() }
    }
}

Sample JSON Response from someHttpGetCall():

[
  {
    "id": "testId",
    "NAME": "testName",
    "favorite_numbers": [
      1,
      2,
      3
    ],
    "favorite_names": [
      "Anna",
      "Bob",
      "Carl"
    ],
    "random_flags": [
      true,
      true,
      false
    ],
    "previous_transactions": [
      1.23,
      4.56,
      7.89
    ]
  }
]

I am checking the code on myside to see if it’s something to do with the way we are parsing everything but thought I’d ask to see if perhaps there is something else we needed to do to configure the executor or extend anything to resolve the errors.

Thanks!

Environment (if relevant)

Versions:

GraphQL 5.0.0 GraphQL.NewtonSoftJson 5.0.0 GraphQL-Parser 8.0.0

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:9 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
Shane32commented, Jul 19, 2022

Ok so the issue is that the resolver for favoriteNumbers (which has a graph type of ListGraphType<IntGraphType>) is returning a JArray which inheirts IList<JToken> . GraphQL.NET understands this as an enumerable list, and iterates the list, finding JToken instances, which the scalar doesn’t understand. At no point does the underlying JToken elements get parsed to integers.

So for example, if you change like this:

//broken
    else if (fieldType is ListGraphType listGraphType)
    {
        field.Resolver = this.GraphQLResolver<JArray>(type, field);
    }

//fixed
    else if (fieldType is ListGraphType<IntGraphType> listGraphType)
    {
        field.Resolver = this.GraphQLResolver<IList<int>>(type, field);
    }

Then it would work, because the resolver will return a list of integers.

There are probably many ways to solve this issue. One way would be to overwrite the built-in GraphQL scalars, so they also understand JToken objects. Then you can remove all your scalar-specific code from your sample above.

Another way would be something like this:

if (fieldType is ListGraphType listGraphType)
{
    this.AttachResolver(type, field, listGraphType.ResolvedType, true); //add argument to indicate it's a list
}

// change this
FuncFieldResolver<T> GraphQLResolver<T>(IObjectGraphType type, FieldType field, bool isArray)

// change all the calls to GraphQLResolver to pass along isArray

// and change T to object:
// return new FuncFieldResolver<object>(async context =>

// and change this
return isArray ? token.ToObject<IList<T>>() : token.ToObject<T>();
0reactions
Shane32commented, Jul 19, 2022

Either way is fine with me. These errors are “server errors” and should not normally returned to the end user, but rather used for debugging and logging. So extra information here is valuable.

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to Handle Unsupported Scalar Types · Issue #458
The problem is that AstFromValue will throw ExecutionError($"Cannot convert value to AST: {serialized}") when it is called.
Read more >
Conversion failed when converting the varchar value ...
I have tried similar solution like this but I was not succesful. In this case it says The data types ntext and varchar...
Read more >
Solved: Type mismatch: Unable to convert a value to the da...
Solved: we have NPI column has contains below like data number's and names. i fixed the npi column data type as a text....
Read more >
Custom scalars - Apollo GraphQL Docs
When an incoming query string includes the scalar as a hard-coded argument value, that value is part of the query document's abstract syntax...
Read more >
Error : Cannot convert value " of type Text to type Integer
Error : Cannot convert value " of type Text to type Integer · Select the column with a left click · In the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found