Ferry Custom Type Mapping
Configure Ferry’s build.yaml to map GraphQL scalar types directly to Dart classes,
eliminating the need for built_value serialization on custom scalars.
Ferry Custom Type Mapping
Section titled “Ferry Custom Type Mapping”Why Custom Type Mapping?
Section titled “Why Custom Type Mapping?”By default, Ferry generates built_value classes for GraphQL types. With custom type mapping in build.yaml, you can:
- Map GQL scalar types directly to standard Dart classes (e.g.,
DateTime,Map<String, dynamic>) - Eliminate manual conversion extensions for scalar types
- Prevent the “forgotten field” problem where manual extensions miss newly added fields
- Keep generated code using familiar Dart types
Configuration
Section titled “Configuration”Step 1: Create or Update build.yaml
Section titled “Step 1: Create or Update build.yaml”In the root of your datasource package (or project root), configure Ferry’s type overrides:
targets: $default: builders: ferry_generator: options: schema: lib/src/core/remote/gql/schema.graphql type_overrides: # Map DateTime scalar to Dart's DateTime DateTime: name: DateTime import: 'dart:core'
# Map JSON scalar to Map JSON: name: Map<String, dynamic> import: 'dart:core'
# Map Upload scalar to MultipartFile (for file uploads) Upload: name: MultipartFile import: 'package:http/http.dart'
# Map custom scalars as needed BigInt: name: int import: 'dart:core'
# Map UUID to String UUID: name: String import: 'dart:core'Step 2: Run Code Generation
Section titled “Step 2: Run Code Generation”dart run build_runner build --delete-conflicting-outputsStep 3: Verify Generated Code
Section titled “Step 3: Verify Generated Code”After generation, verify that the generated Dart classes use your mapped types:
// Before (without type mapping) - uses built_value GDateTimeclass GQueryEventData_event { GDateTime? get date; // GDateTime needs manual conversion}
// After (with type mapping) - uses standard Dart DateTimeclass GQueryEventData_event { DateTime? get date; // Standard Dart DateTime, no conversion needed}Common Type Mappings
Section titled “Common Type Mappings”| GraphQL Scalar | Dart Type | Import |
|---|---|---|
DateTime | DateTime | dart:core |
JSON | Map<String, dynamic> | dart:core |
Upload | MultipartFile | package:http/http.dart |
BigInt | int | dart:core |
UUID | String | dart:core |
Decimal | double | dart:core |
How This Fits in the Three-Model Pipeline
Section titled “How This Fits in the Three-Model Pipeline”Custom type mapping operates at Layer 1 (GQL Raw Models):
- Ferry generates Dart classes with mapped types (e.g.,
DateTimeinstead ofGDateTime) - Repository model convertors still transform raw models to domain models, but scalar conversion is already handled
- Domain models receive properly typed values from the repository
Before Custom Type Mapping
Section titled “Before Custom Type Mapping”// Manual conversion extension - prone to forgotten fieldsextension EventExtension on GQueryEventData_event { EventModel toDomain() { return EventModel( id: id ?? '', title: title ?? '', date: date?.toDateTime() ?? DateTime.now(), // Manual GDateTime conversion metadata: json != null ? jsonDecode(json!) : {}, // Manual JSON parsing ); }}After Custom Type Mapping
Section titled “After Custom Type Mapping”// Model convertor - scalar types already resolved@riverpodConvertor<EventModel, GQueryEventData_event>eventModelConvertor(Ref ref) { return Convertor((raw) { return EventModel( id: raw.id ?? '', title: raw.title ?? '', date: raw.date ?? DateTime.now(), // Already a DateTime metadata: raw.metadata ?? {}, // Already a Map<String, dynamic> ); });}Handling Complex Types
Section titled “Handling Complex Types”For complex GQL types (objects, lists), Ferry generates proper Dart classes. The model convertor in the repository handles the transformation to domain models:
@riverpodConvertor<AttendeeModel, GQueryAttendeeData_attendee>attendeeModelConvertor(Ref ref) { return Convertor((raw) { return AttendeeModel( id: raw.id ?? '', name: raw.fullName ?? 'Unknown', email: raw.email ?? '', // Nested object conversion organization: raw.organization != null ? OrganizationModel( id: raw.organization!.id ?? '', name: raw.organization!.name ?? '', ) : null, // List conversion tags: raw.tags?.nonNulls.map((t) => t.name ?? '').toList() ?? [], ); });}Troubleshooting
Section titled “Troubleshooting”Type Not Found After Generation
Section titled “Type Not Found After Generation”Ensure the import path is correct in build.yaml. For third-party packages, use the full package import:
Upload: name: MultipartFile import: 'package:http/http.dart' # Full package pathSerialization Errors in Ferry Cache
Section titled “Serialization Errors in Ferry Cache”Ferry’s cache requires JSON-compatible types. If you map a scalar to a custom Dart class, ensure it serializes correctly. Standard types (DateTime, String, int, double, Map, List) work without issues.
build_runner Conflicts
Section titled “build_runner Conflicts”If you see conflicting output errors, use the --delete-conflicting-outputs flag:
dart run build_runner build --delete-conflicting-outputs