Nullable Float/Int fields

In GraphQL all scalar types are nullable by default. In GraphQL for Delphi, when using a schema type with a nullable Int or Float field, there seems to be no built-in way to make the resolver function return null.

I modified the TSchemaFloatType.Serialize function to explicitly check for a null variant type. The updated code snippet is provided below:

function TSchemaFloatType.Serialize(const Value: TValue): TValue;
  if Value.IsEmpty or
     ((Value.Kind = tkVariant) and (Value.AsVariant = Null)) then

  if not Value.TryCast(TypeInfo(Extended), Result) then
    raise EGraphQLCoercingSerialize.Create(Self.DisplayName, Value);

With this change, a variant set to null in the resolver will be correctly serialized as null in the GraphQL response.

Is this functionality missing from the library, or am I misunderstanding something?

Hello @Richard, welcome to TMS Support Center.

To return null values, you should just return TValue.Empty.
Can you please provide more details and exact steps to reproduce the issue you are claiming?

I modified your bookshelf demo by adding an int field to the Author schema type.

type Author {         
  id: ID!             
  name: String!       
  createdOn: DateTime!
  someInt: Int
  books: [Book!]!     

Then I implemented that in TAuthor like so

  TAuthor = class
    FId: Integer;
    FName: string;
    FCreatedOn: TDateTime;
    function GetBooks: TArray<TBook>;
    function GetSomeInt: TValue;
    constructor Create(AName: string);
    property Books: TArray<TBook> read GetBooks;

    property Id: Integer read FId;
    property Name: string read FName;
    property CreatedOn: TDateTime read FCreatedOn;
    property SomeInt: TValue read GetSomeInt;

function TAuthor.GetSomeInt: TValue;
  Result := TValue.Empty;

This raises an exception in TSchemaIntType.Serialize because at that point the value isn't empty anymore and the function tries to cast it to an Integer. I stopped debugging at TRttiPropResolver.Resolve in GraphQL.Schema.Resolvers where the result is not resolved to empty.

Can you please provide the full project and steps to reproduce it?
I can't reproduce it here.

I just changed the hello demo to show the issue that I'm having.

hello (92.9 KB)

To reproduce just run the application and run the query shown in the form. Also good to mention: I'm using version 1.3 of your library in Delphi 11.1. We did not get around to upgrading yet.

The type of the field is Integer, so you cannot use TValue as the value of the Delphi class property type itself.
Since Delphi has no nullable-types, you have to provide a custom field resolver for such field. The following code solves your issue:

procedure TDataModule1.GraphQLSchema1InitSchema(Sender: TObject; Schema: TSchemaDocument);
  Schema.Bind('Query', TQuery);
  Schema.Bind('Example', TExample);
  Schema.SetResolver('Example', 'someInt',
    function(Args: TFieldResolverArgs): TValue
      Result := TValue.Empty;

To avoid writing separate resolvers for nullable integer and float fields, I modified the Float and Int serializers (see snippet above) to use the Variant return type, which allows for null values. Do you see any drawbacks to this approach?

Hi, @wlandgraf

You say "Since Delphi has no nullable-types...", but that is clearly not the case: Delphi supports the variant type, which is nullable. That is precisely what allows my colleague @Richard to test for null in his modified Serialize-method.
You yourself suggested using a TValue, which is also nullable(emptyable) and that would be another option.

And in fact both types work in the TSchemaIntType and TSchemFloatType-resolvers when a non null integer or float is assigned. The existing framework converts these without a problem to the correct output. The only problem is when a null or empty value is returned. I think we can agree that it would be of tremendous value if your framework would also convert nulls correctly in these kind of resolvers, since these object-type resolvers are way easier to read and manipulate than separate field-resolvers. This would greatly benefit the usability of your framework.

(Note also that for input types it is possible to use variants and TValues and when a field is missing in the input this is correctly set to null)

BTW, I traced through the code using a TValue in the resolver and, when that is set to null, when arriving in the Serialize-method, it is not longer an emtpy TValue, but actually a TValue that contains an empty TValue, which is not recognized itself as empty. So maybe code could also be added to recognize these as empty or convert them in an earlier stage to empty TValue

Hi @Kees-Willem, those are valid suggestions.

I was just providing a solution that could be used with the current version. But yes, in a future release with can provide a check for NULL Variant and also nested TValue.

We just have to pay attention to performance, those are very low level methods that might be called thousands of times when serializing the response, so any extra check can cause an overhead.

I will create a feature request from this post.