Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeError at formatI18nErrors when validating nested array of objects #633

Closed
heken84 opened this issue May 5, 2024 · 2 comments · Fixed by #674
Closed

TypeError at formatI18nErrors when validating nested array of objects #633

heken84 opened this issue May 5, 2024 · 2 comments · Fixed by #674

Comments

@heken84
Copy link

heken84 commented May 5, 2024

Hi there,

I always get a TypeError: Cannot convert undefined or null to object\n when using validation method "i18n.validate" from the "I18nService" while validating nested array of objects.

This error does not occur, when the normal "validate" function of the "class-validator" library is used.

Thank you,
Heiko


My Dto (Parent)

export class RestrictionCodeDto {

  @IsNotEmpty({ message: i18nValidationMessage<I18nTranslations>('messages.validation.isNotEmptyError') })
  @Validate(hasRestrictionCodeValidLength, { message: i18nValidationMessage<I18nTranslations>('messages.validation.rcLengthError') })
  id: string;

  @IsNotEmpty({ message: i18nValidationMessage<I18nTranslations>('messages.validation.isNotEmptyError') })
  @IsString({ message: i18nValidationMessage<I18nTranslations>('messages.validation.isStringError') })
  @Length(4, 4, { each: true, message: i18nValidationMessage<I18nTranslations>('messages.validation.lengthError') })
  salesOrganization: string;

  @Type(() => Boolean)
  @IsBoolean({ message: i18nValidationMessage<I18nTranslations>('messages.validation.isBooleanError') })
  active: boolean

  @Type(() => Boolean)
  @IsBoolean({ message: i18nValidationMessage<I18nTranslations>('messages.validation.isBooleanError') })
  defectMaingroupRequired: boolean;

  @IsNotEmpty({ message: i18nValidationMessage<I18nTranslations>('messages.validation.isNotEmptyError') })
  @IsString({ message: i18nValidationMessage<I18nTranslations>('messages.validation.isStringError') })
  @Length(7, 7, { message: i18nValidationMessage<I18nTranslations>('messages.validation.lengthError') })
  changedBy: string;

  @IsOptional()
  @Type(() => Date)
  @IsDate({ message: i18nValidationMessage<I18nTranslations>('messages.validation.isDateError') })
  lastChangeOn: Date | null;

  @ValidateNested({each: true})
  @Type(() => RestrictionCodeTextDto)
  texts: RestrictionCodeTextDto [];
}

My Dto (Nested)

export class RestrictionCodeTextDto {
  
  @IsString({ message: i18nValidationMessage<I18nTranslations>('messages.validation.isStringError') })
  @Length(2, 2, { message: i18nValidationMessage<I18nTranslations>('messages.validation.lengthError') })
  language: string;

  @IsString({ message: i18nValidationMessage<I18nTranslations>('messages.validation.isStringError') })
  @MaxLength(200, { message: i18nValidationMessage<I18nTranslations>('messages.validation.maxLengthError') })
  text: string;
}

My Pipe

@Injectable()
export class RestrictionCodePipe implements PipeTransform {

  constructor(private i18n: I18nService) { }

  async transform(value: any, { metatype }: ArgumentMetadata) {
    const appException: AppException = new AppException(HttpStatus.BAD_REQUEST, []);

    const restrictionCodeDto: RestrictionCodeDto = plainToInstance(metatype, value);

    // this method throws the stack trace
    const errors: I18nValidationError[] = await this.i18n.validate( 
      restrictionCodeDto, { lang: I18nContext.current().lang }
    );

   // processing of errors
  }
}

Stack Trace

{"TypeError: Cannot convert undefined or null to object\n    at Function.keys (<anonymous>)\n    at ...node_modules\\nestjs-i18n\\src\\utils\\util.ts:79:32\n    at Array.map (<anonymous>)\n    at formatI18nErrors (...node_modules\\nestjs-i18n\\src\\utils\\util.ts:64:17)\n    at ...\\node_modules\\nestjs-i18n\\src\\utils\\util.ts:78:22\n    at Array.map (<anonymous>)\n    at formatI18nErrors (...\\node_modules\\nestjs-i18n\\src\\utils\\util.ts:64:17)\n    at I18nService.validate (...\\node_modules\\nestjs-i18n\\src\\services\\i18n.service.ts:361:28)\n    at processTicksAndRejections (node:internal/process/task_queues:95:5)\n    at RestrictionCodePipe.transform (...\\src\\rc-maintenance\\pipes\\restriction-code.pipe.ts:19:43)"}

Originally posted by @heken84 in #630

@heken84 heken84 changed the title Hi there, TypeError at formatI18nErrors when validating nested array of objects May 5, 2024
@heken84
Copy link
Author

heken84 commented Jun 26, 2024

Hi there,

is there someone who can help me with this issue?

Best regards,
Heiko

@velvet-lynx
Copy link
Contributor

velvet-lynx commented Nov 6, 2024

Hello there !

I also had this issue. By following the stack trace and inspecting the code I found the source of the problem in this function:

// src/utils/util.ts, function definition starts at line 55

// ...
export function formatI18nErrors<K = Record<string, unknown>>(
  errors: I18nValidationError[],
  i18n: I18nService<K>,
  options?: TranslateOptions,
): I18nValidationError[] {
  return errors.map((error) => {
    error.children = formatI18nErrors(error.children ?? [], i18n, options);
    // the error is in the next line
    error.constraints = Object.keys(error.constraints).reduce((result, key) => {
      const [translationKey, argsString] = error.constraints[key].split('|');
      const args = !!argsString ? JSON.parse(argsString) : {};
      const constraints = args.constraints
        ? args.constraints.reduce((acc: object, cur: any, index: number) => {
            acc[index.toString()] = cur;
            return acc;
          }, {})
        : error.constraints;
      result[key] = i18n.translate(translationKey as Path<K>, {
        ...options,
        args: {
          property: error.property,
          value: error.value,
          target: error.target,
          contexts: error.contexts,
          ...args,
          constraints,
        },
      });
      return result;
    }, {});
    return error;
  });
}
// ...

Object.keys(...) will throw if error.constraints === undefined, which happens when the validation errors originate in the children and no validation error occurs in the parent.

The solution I found is to replace Object.keys(error.constraints) by Object.keys(error.constraints ?? {})

But obviously without a fix in the repo nothing will change when fetching from npm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants