Skip to content

Map Domain Models to GraphQL Types

Problem

You need to convert between domain models and GraphQL types while maintaining clean separation between domain and datasource packages.

Team Input Needed

Conversion extensions need better organization patterns to prevent missing fields in filters and GQL type mappings. Team discussion required for standardization.

Create extension methods to handle conversions between domain models and GraphQL types:

These generic filter extensions are essential for all projects and exist in the datasource package:

extension GStringFiltersBuilderToGql on GStringFiltersBuilder {
GStringFiltersBuilder? construct({
String? contains,
String? equals,
String? startsWith,
String? endsWith,
String? nullOrEmpty,
}) {
if (contains == null &&
equals == null &&
startsWith == null &&
endsWith == null &&
nullOrEmpty == null) {
return null;
}
return GStringFilters(
(b) =>
b
..G_contains = contains
..G_eq = equals
..G_startswith = startsWith
..G_endswith = endsWith
..G_null_or_empty = nullOrEmpty,
).toBuilder();
}
}
extension GRelatedFiltersDtoExtension on GRelatedFiltersBuilder {
GRelatedFiltersBuilder? construct({
List<String?>? anyIds,
List<String?>? allIds,
List<String?>? anyTitles,
List<String?>? allTitles,
}) {
final filteredAnyIds = anyIds?.nonNulls.toList();
final filteredAnyTitles = anyTitles?.nonNulls.toList();
final filteredAllIds = allIds?.nonNulls.toList();
final filteredAllTitles = allTitles?.nonNulls.toList();
if ((filteredAnyIds?.isNotEmpty != true) &&
(filteredAllIds?.isNotEmpty != true) &&
(filteredAnyTitles?.isNotEmpty != true) &&
(filteredAllTitles?.isNotEmpty != true)) {
return null;
}
final builder = GRelatedFiltersBuilder();
if (filteredAnyIds?.isNotEmpty == true ||
filteredAnyTitles?.isNotEmpty == true) {
builder.G_any = ListBuilder([
...?filteredAnyIds?.map((e) => GrelatedFilter((b) => b.id = e)),
...?filteredAnyTitles?.map((e) => GrelatedFilter((b) => b.title = e)),
]);
}
if (filteredAllIds?.isNotEmpty == true ||
filteredAllTitles?.isNotEmpty == true) {
builder.G_all = ListBuilder([
...?filteredAllIds?.map((e) => GrelatedFilter((b) => b.id = e)),
...?filteredAllTitles?.map((e) => GrelatedFilter((b) => b.title = e)),
]);
}
return builder;
}
}
extension HotelBookingFilterCriteriaExtensions on HotelBookingFilterCriteria {
GhotelbookingsFilters get toGql {
return GhotelbookingsFilters(
(b) => b
..id = id
..systemParentId = conferenceId
..bookingNumber = GStringFiltersBuilder().construct(
contains: bookingNumberContains,
equals: bookingNumber,
startsWith: bookingNumberStartsWith,
)
..roomNumber = GStringFiltersBuilder().construct(
contains: roomNumberContains,
equals: roomNumber,
)
..bookingStatus = GRelatedFiltersBuilder().construct(
anyIds: bookingStatusIds,
anyTitles: bookingStatusTitles,
)
..hotel = GRelatedFiltersBuilder().construct(
anyIds: hotelIds,
anyTitles: hotelTitles,
),
);
}
}
extension HotelBookingSortCriteriaExtensions on HotelBookingSortCriteria {
GhotelbookingsOrder get toGql {
return GhotelbookingsOrder(
(b) => b
..createdAt = createdAt?.toGql
..updatedAt = updatedAt?.toGql
..bookingNumber = bookingNumber?.toGql
..checkIn = checkIn?.toGql
..checkOut = checkOut?.toGql,
);
}
}
extension SortDirectionExtensions on SortDirection {
GSortEnumType get toGql {
switch (this) {
case SortDirection.asc:
return GSortEnumType.ASC;
case SortDirection.desc:
return GSortEnumType.DESC;
}
}
}
extension HotelBookingsUpsertVarsExtensions on HotelBookingsUpsertVars {
GMutateHotelBookingsSaveVars get toMutationVars {
return GMutateHotelBookingsSaveVars(
(b) => b
..hotelBookingsArgs = GhotelBookingsArgs(
(args) => args
..id = id
..title = title
..bookingNumber = bookingNumber
..roomNumber = roomNumber
..roomCapacity = roomCapacity
..checkIn = checkIn?.toGqlDateTime
..checkOut = checkOut?.toGqlDateTime
..notes = notes
..bookingStatus = bookingStatusId != null
? GRelatedArgs(
(rb) => rb
..connect.replace([
GrelatedArg((fb) => fb..id = bookingStatusId!),
]),
).toBuilder()
: null
..roomType = roomTypeId != null
? GRelatedArgs(
(rb) => rb
..connect.replace([
GrelatedArg((fb) => fb..id = roomTypeId!),
]),
).toBuilder()
: null
..hotel = hotelId != null
? GRelatedArgs(
(rb) => rb
..connect.replace([
GrelatedArg((fb) => fb..id = hotelId!),
]),
).toBuilder()
: null,
).toBuilder(),
);
}
}
extension HotelBookingStatusGqlExtensions on GQueryHotelBookingsData_hotelbookings_hotelbookingsItems_bookingStatus {
HotelBookingStatusEntity toEntity() {
return HotelBookingStatusEntity(
id: id!,
title: title!,
status: _mapTitleToStatusEnum(title!),
);
}
HotelBookingStatusEnum _mapTitleToStatusEnum(String title) {
switch (title.toLowerCase()) {
case 'canceled':
return HotelBookingStatusEnum.canceled;
case 'completed':
return HotelBookingStatusEnum.completed;
case 'requested':
return HotelBookingStatusEnum.requested;
case 'approved':
return HotelBookingStatusEnum.approved;
default:
return HotelBookingStatusEnum.requested;
}
}
}
extension GuestGqlExtensions on GQueryHotelBookingsData_hotelbookings_hotelbookingsItems_guests {
GuestEntity toEntity() {
return GuestEntity(
id: id!,
fullName: '${firstName} ${lastName}',
firstName: firstName!,
lastName: lastName!,
jobTitle: jobTitle ?? '',
organization: organizationName ?? '',
);
}
}
extension DateTimeExtensions on DateTime {
GDateTime get toGqlDateTime {
return GDateTime(toIso8601String());
}
}
extension GDateTimeExtensions on GDateTime {
DateTime get toDto {
return DateTime.parse(value);
}
}

These essential extensions exist in the domain package and provide consistent data handling across all projects:

enum DateFormatter {
shortSlash('dd/MM/yyyy', '01/12/2023'),
shortDash('dd-MM-yyyy', '01-12-2023'),
shortDot('dd.MM.yyyy', '01.12.2023'),
medium('MMM d, yyyy', 'Dec 1, 2023'),
long('MMMM d, yyyy', 'December 1, 2023'),
full('EEEE, MMMM d, yyyy', 'Friday, December 1, 2023'),
yearMonth('MMM yyyy', 'Dec 2023'),
monthDay('MMM d', 'Dec 1'),
iso('yyyy-MM-dd', '2023-12-01');
const DateFormatter(this.pattern, this.example);
final String pattern;
final String example;
}
enum TimeFormatter {
hour24('HH:mm', '14:30'),
hour24WithSeconds('HH:mm:ss', '14:30:45'),
hour12('h:mm a', '2:30 PM'),
hour12WithSeconds('h:mm:ss a', '2:30:45 PM'),
hour12Short('h:mma', '2:30p'),
hourOnly24('HH', '14'),
hourOnly12('h a', '2 PM');
const TimeFormatter(this.pattern, this.example);
final String pattern;
final String example;
}
extension DateTimeExtensions on DateTime? {
int days(DateTime? other) {
if (this == null || other == null) return 0;
final current = this!;
final days = other.difference(current).inDays.abs();
return days;
}
int nights(DateTime? other) {
return days(other);
}
String formatDate(DateFormatter formatter, Locale locale) {
if (this == null) return '';
final localDateTime = this!.toLocal();
return DateFormat(
formatter.pattern,
locale.languageCode,
).format(localDateTime);
}
String formatTime(TimeFormatter formatter, Locale locale) {
if (this == null) return '';
final localDateTime = this!.toLocal();
return DateFormat(
formatter.pattern,
locale.languageCode,
).format(localDateTime);
}
}
extension StringExtensions on String? {
String get NA {
if (this == null || this!.trim().isEmpty) return 'N/A';
return this!;
}
String get emptyIfNull {
return this ?? '';
}
bool get isNullOrEmpty {
return this == null || this!.trim().isEmpty;
}
}
  1. Use Generic Extensions - Always leverage the generic filter and domain extensions from datasource/domain packages
  2. Group by Feature - Keep conversion extensions in feature-specific files
  3. Separate by Direction - Create separate extensions for to/from conversions
  4. Consistent Naming - Use toGql, toEntity, toDto naming conventions
  5. Null Safety - Always handle null values appropriately
  6. Default Values - Provide sensible defaults for missing data
  7. Package Separation - Domain and datasource are separate packages where datasource depends on domain
extension ListExtensions<T> on List<T>? {
List<T> get orEmpty => this ?? [];
T? get firstOrNull => (this?.isNotEmpty ?? false) ? this!.first : null;
}
extension RelatedFieldExtensions on List<GQueryData_field>? {
String? get firstTitle => firstOrNull?.title;
String? get firstId => firstOrNull?.id;
}