Working on a project where I have mongoose schema with a field typed with Record<string, string>
; I need graphql to understand that particular type (since Object
is too “vague”), but I keep encountering the following issue:
CannotDetermineOutputTypeError: Cannot determine a GraphQL output type for the “measurements”. Make sure your class is decorated with an appropriate decorator.
Here is the field definition if the @Schema
annotated schema class:
@Prop({ type: Map, of: String, default: {} })
@Field(() => StringRecordScalar)
measurements: Record<string, string>;
This is the definition of StringRecordScalar
:
import { Scalar, CustomScalar } from '@nestjs/graphql';
import { Kind, ValueNode, ObjectValueNode } from 'graphql';
@Scalar('StringRecordScalar', () => Object)
export class StringRecordScalar
implements CustomScalar<any, Record<string, string>>
{
description = 'Custom scalar for Record<string, string>';
// Serialize the data (output to the client)
serialize(value: Record<string, string>): Record<string, string> {
if (typeof value !== 'object' || Array.isArray(value)) {
throw new Error('Invalid Record<string, string> output');
}
return value;
}
// Parse the input value from the client (input type)
parseValue(value: any): Record<string, string> {
if (typeof value !== 'object' || Array.isArray(value)) {
throw new Error('Invalid Record<string, string> input');
}
// Ensure all keys/values are strings
Object.keys(value).forEach((key) => {
if (typeof value[key] !== 'string') {
throw new Error('All values in Record<string, string> must be strings');
}
});
return value;
}
// Parse literal values from the GraphQL query itself
parseLiteral(ast: ValueNode): Record<string, string> {
if (ast.kind !== Kind.OBJECT) {
throw new Error('Invalid Record<string, string> literal');
}
const result: Record<string, string> = {};
(ast as ObjectValueNode).fields.forEach((field) => {
if (field.value.kind === Kind.STRING) {
result[field.name.value] = field.value.value;
} else {
throw new Error(
`Expected value of type String for key "${field.name.value}"`,
);
}
});
return result;
}
}