gql-dart / ferry

Stream-based strongly typed GraphQL client for Dart
https://ferrygraphql.com/
MIT License
602 stars 116 forks source link

How can I send request with null field? #473

Closed bizk-sato closed 11 months ago

bizk-sato commented 1 year ago

I want to update a field with null.

So I did something like below.

final req = GUpsertModelReq((b) {
  b.vars.input.field_name = null;

 return b;
}
await client.request(req)

field_name is nullable field in graphql schema.

when I submit the request, it doesn't send the field_name field. How can I send field with null value?

knaeckeKami commented 1 year ago

This is currently not supported. It was a limitation of built_value when this library was written originally.

However, built_value supports this now, so we could add an option to ferry_generator to add @BuiltValueSerializer(serializeNulls: true) to serializers of input fields. ( https://github.com/google/built_value.dart/issues/604 )

This would, however, lead to all fields being included and being sent as null even if they were never set, so it's also not a really good solution.

A complete fix would probably require wrapping all nullable values in something like this.

At the moment, I can only offer you a workaround: Write your own class that implements the G...Var() type and implement your own toJson() where you decide what's included or omitted.

bizk-sato commented 1 year ago

@knaeckeKami Ok, thanks.

Can you provide sample code or how to implements the G...Var() type and toJson() method? I mean at the moment toJson() in G..Var() class called, the field_name propeerty doesn't exist in this argument in the method. So I don't know how to decide which value is absent or set to null.

knaeckeKami commented 1 year ago

You could for example add an additional bool field explictlySetToNull for every variable that gets set to true to decide wether to include it in the toJson() or not.

zombie6888 commented 1 year ago

I was faced with this issue and ended up with custom serializer:

class PassportSerializer extends JsonSerializer<GupdatePassportVars>
    implements StructuredSerializer<GupdatePassportVars> {
  @override
  GupdatePassportVars fromJson(Map<String, dynamic> json) {
    throw UnimplementedError();
  }

  @override
  Map<String, dynamic> toJson(GupdatePassportVars object) {
    final passport = object.passport;   
    final json = object.passport.toJson();
    if (passport.series == null) {
      json['series'] = null;
    }
    if (passport.number == null) {
      json['number'] = null;
    }

    return {
      "passportId": object.passportId?.value,
      "passport": json
    };    
  }
}

Hope it will be fixed soon. Thx for your job!

knaeckeKami commented 1 year ago

Thanks for sharing a workaround!

I have a draft solution here: https://github.com/gql-dart/gql/pull/381

I am not sure when I can make it production-ready (especially tests, make this an opt-in feature to avoid breaking changes, and code cleanup).

mattbajorek commented 1 year ago

Extending on what @zombie6888 has posted. Here is a full example:

import 'package:built_value/serializer.dart';
// ignore: implementation_imports
import 'package:gql_code_builder/src/serializers/json_serializer.dart';

import '../../utils/date_utils.dart';
// IMPORT GQueryVars

class QueryVarsSerializer extends JsonSerializer<GQueryVars>
    implements StructuredSerializer<GQueryVars> {
  @override
  GQueryVars fromJson(Map<String, dynamic> json) {
    final builder = GQueryVarsBuilder()
      ..id = json['id']
      ..nullableField = dateTimeFromJsonValue(json['nullableField']);
    return builder.build();
  }

  @override
  Map<String, dynamic> toJson(GQueryVars object) =>
      {'id': object.id, 'nullableField': object.nullableField};

  // Required override method needed to serialize a null value
  // Needs to return a list of all of the key value pairs
  @override
  Iterable<Object?> serialize(
    Serializers serializers,
    GQueryVars object, {
    FullType specifiedType = FullType.unspecified,
  }) =>
      ['id', object.id, 'nullableField', object.nullableField];
}

Then in build.yaml:

targets:
  $default:
    builders:
      ferry_generator|serializer_builder:
        enabled: true
        options:
          schema: app|lib/graphql/schema.graphql
          custom_serializers:
            - import: "IMPORT LOCATION"
              name: QueryVarsSerializer
knaeckeKami commented 11 months ago

Fixed by

https://github.com/gql-dart/ferry/pull/549 and https://github.com/gql-dart/gql/pull/381