How to set property as union type for ObjectType class in NestJs?

Viewed 17

I have the following class:

@ObjectType()
class Input {
  @Field()
  id: string
  @Field()
  label: string
  @Field()
  element: Element
  @Field()
  defaultValue: '' // <-- HERE
}

This defaultValue can be either a string, an array (an array of strings, if it helps) or a boolean. So I tried to set it as defaultValue: string | string[] | boolean, tried to define a union type type DefaultValue = string | string[] | boolean (same thing, essentially), tried to change string[] to [string], but I kept getting this error message: Error: Undefined type error. Make sure you are providing an explicit type for the "defaultValue" of the "Input" class.

I tried to use createUnionType, but either I did it wrong, or it is incorrect way to do it in the first place

const DefaultValueUnion = createUnionType({
  name: 'DefaultValueUnion',
  types: [string, string[], boolean]
})

but I got an error, saying something like string is a type and it is used here as a value

Tried to use the CLI plugin, hoping it could determine the type automatically, but also no luck.

So is there any way to set a type as union? Or perhaps some workaround?

1 Answers

This is slightly complicated due to scalar types not being supported in the union types by the GraphQL spec, but the general workaround is this:

// 1. define value types for the scalars you want to use
@ObjectType()
class StringValue {
  @Field(() => String)
  value: string;
}

@ObjectType()
class StringArrayValue {
  @Field(() => [String])
  value: string[];
}

@ObjectType()
class BooleanValue {
  @Field(() => Boolean)
  value: boolean;
}


// 2. define the union type using the value types
const DefaultValueUnion = createUnionType({
  name: 'DefaultValueUnion',
  types: () => [StringValue, StringArrayValue, BooleanValue] as const,
  resolveType: (obj) => {
    if (typeof obj.value === 'string') {
      return StringValue;
    } else if (typeof obj.value === 'boolean') {
      return BooleanValue;
    } else if (Array.isArray(obj.value)) {
      return StringArrayValue;
    }
    return null;
  },
});


// 3. use the union type
@ObjectType()
class Input {
  @Field(() => String)
  id: string;
  @Field(() => String)
  label: string;
  @Field(() => String)
  element: string;
  @Field(() => DefaultValueUnion)
  defaultValue: typeof DefaultValueUnion;
}

The client would also need to do a little bit more:

query test {
  test {
    id
    label
    element
    defaultValue {
      ... on StringValue {
        stringValue: value
      }
      ... on StringArrayValue {
        stringArrayValue: value
      }
      ... on BooleanValue {
        booleanValue: value
      }
    }
  }
}

StackBlitz

Related