Custom Scalars

If our GraphQL schema includes custom scalar types, we'll need to configure Ferry's code generator to properly handle them.

Configuring our build.yaml file

Let's say our GraphQL schema includes a Date scalar that we want to map to Dart's DateTime type.

Configure Type Overrides

Specifying a Type Overrides tells the generator what Dart types to use when generating a type from our GraphQL schema (in this case our custom scalar type).

gql_build|schema_builder:
enabled: true
options:
type_overrides:
Date:
name: DateTime

The key in the yaml Map should be the name of our custom scalar in our schema (i.e. "Date"), and name should be the name of the Dart type.

important

We've included only the schema_builder above for brevity, but we will need to include this same type_overrides map for data_builder, var_builder, and req_builder as well. See the complete build.yaml example for more details.

If our Dart type is not part of the dart:core library, we'd also need to import the file that contains the Dart type. DateTime is a part of dart:core, but if it weren't, we'd import it like so:

gql_build|schema_builder:
enabled: true
options:
type_overrides:
Date:
name: DateTime
import: 'package:my_date_library/date_time.dart'

Configure Custom Serializer

If our Dart type is not a Dart Data Type, we also need to define a custom serializer so that the generated classes can be correctly serialized & deserialized the data.

Assuming we've created a DateSerializer for our custom Date scalar, we will need to include it in our serializer_builder.

gql_build|serializer_builder:
enabled: true
options:
schema: my_project|lib/graphql/schema.graphql
custom_serializers:
- import: 'package:path/to/date_serializer.dart'
name: DateSerializer

Complete build.yaml Example

Here's an example of what our final build.yaml might look like.

targets:
$default:
builders:
gql_build|ast_builder:
enabled: true
gql_build|schema_builder:
enabled: true
options:
schema: my_project|lib/schema.graphql
type_overrides:
Date:
name: DateTime
gql_build|data_builder:
enabled: true
options:
schema: my_project|lib/schema.graphql
type_overrides:
Date:
name: DateTime
gql_build|var_builder:
enabled: true
options:
schema: my_project|lib/schema.graphql
type_overrides:
Date:
name: DateTime
gql_build|serializer_builder:
enabled: true
options:
schema: my_project|lib/schema.graphql
custom_serializers:
- import: 'package:path/to/date_serializer.dart'
name: DateSerializer
ferry_generator|req_builder:
enabled: true
options:
schema: my_project|lib/schema.graphql
type_overrides:
Date:
name: DateTime

Create a Custom Serializer

In order for us to instantiate our generated Dart Classes with data from our GraphQL server, we will need to create a custom built_value serializer for our custom scalar type.

In this case, let's assume that our GraphQL server will return our Date scalar as an int timestamp.

import 'package:built_value/serializer.dart';
class DateSerializer implements PrimitiveSerializer<DateTime> {
@override
DateTime deserialize(
Serializers serializers,
Object serialized, {
FullType specifiedType = FullType.unspecified,
}) {
assert(serialized is int,
"DateSerializer expected 'int' but got ${serialized.runtimeType}");
return DateTime.fromMillisecondsSinceEpoch(serialized);
}
@override
Object serialize(
Serializers serializers,
DateTime date, {
FullType specifiedType = FullType.unspecified,
}) =>
date.millisecondsSinceEpoch;
@override
Iterable<Type> get types => [DateTime];
@override
String get wireName => "Date";
}

And that's it! Now when we run our pub run build_runner build command, ferry's generators will automatically override our GraphQL Date scalar with the Dart DateTime type and use our DateSerializer to serialize & deserialize data.

Using StructuredSerializers

We've implemented the above DateSerializer using the PrimitiveSerializer from built_value. However, if we were building a serializer for a non-primitive Dart type, we'd probably want to use the StructuredSerializer instead.

Since built_value doesn't use standard Json by default, rather than implementing StructuredSerializer directly, we may prefer to extend the JsonSerializer from gql_code_builder. For example, here's a serializer for the Operation type from gql_exec that ferry's generaters use internally.

import "package:built_value/serializer.dart";
import "package:gql/language.dart";
import "package:gql_exec/gql_exec.dart";
import "package:gql_code_builder/src/serializers/json_serializer.dart";
class OperationSerializer extends JsonSerializer<Operation> {
@override
Operation fromJson(Map<String, dynamic> json) => Operation(
document: parseString(json["document"] as String),
operationName: json["operationName"] as String,
);
@override
Map<String, dynamic> toJson(Operation operation) => <String, dynamic>{
"document": printNode(operation.document),
if (operation.operationName != null)
"operationName": operation.operationName,
};
}

For more information about serialization using built_value, check out this blog post.