🙌

class-validatorで作成されたオブジェクトを1次元にする

2022/05/10に公開

class-validatorをとある案件で使用しているのですが、メッセージの整形に手間取ったのでメモ程度に記しておく。

前提

class-validatorを導入済み
大まかな使い方は下記参照
https://github.com/typestack/class-validator

本題

class-validatorを用いてバリデーションをかけると、エラーが発生した場合、下記オブジェクトの配列が返される

{
    target: Object; // Object that was validated.
    property: string; // Object's property that haven't pass validation.
    value: any; // Value that haven't pass a validation.
    constraints?: { // Constraints that failed validation with error messages.
        [type: string]: string;
    };
    children?: ValidationError[]; // Contains all nested validation errors of the property
}

こちらを1次元の配列に変換したい。

forでループさせてconstraintsのメッセージを取り出したら?と最初考えていたのですが、ネストされているオブジェクトでエラーが発生した場合、childrenに上記オブジェクトがネストされて返却されるのでうまくいかなかった。

そこで再帰処理によるオブジェクトの整形処理を作成した。

import { ValidationError } from 'class-validator'

private static formatValidationErrors(
    validationErrors: ValidationError[]
  ): ({ property: string, message:string })[] {
    let errorArray: ({ property: string, message:string })[] = []

    validationErrors.forEach((validationError) => {
      // エラー詳細に値あり
      if (validationError.constraints !== undefined) {
        const constraints = validationError.constraints
        Object.keys(constraints).forEach((key) => {
          errorArray.push({
            property: validationError.property,
            message: constraints[key]
          })
        })
      }
      if (validationError.children !== undefined) {
        // ネストの深さが分からないので再帰的に処理する
        errorArray = errorArray.concat(
          this.formatValidationErrors(validationError.children)
        )
      }
    })

    return errorArray
  }

処理内容を簡単に説明しておく。
引数にclass-validatorを使用して得られたValidationErrorの配列をとる。
返却値としてproperty, messageを持ったオブジェクトを設定しているが、なぜmessageだけじゃダメなのかは後ほど記載します。
ValidationError.constraintsにエラーメッセージの配列が格納されるので、何かしら値が入っていればerrorArrayに格納する。
ValidationError.childrenには子オブジェクトで発生したエラーが格納される。
このchildrenを引数に上記関数を再帰的に呼び出すことでネストされたオブジェクトが1次元になる。

返却値がmessageだけではダメだった理由ですが、今回の案件ではclass-validatorの機能でgroupというものがあるのですが、こちらを使いバリデーションを必須・型などのチェックで別で走らせるように実装を行なっていました。
このとき問題となったのが、例えば必須項目が未入力だった場合に必須チェックだけでなく型チェックにも引っかかり、必須・型両方のメッセージが出力されてしまうことでした。
この問題を解決するためにproperty(エラーだった項目名)も返す必要があったという経緯です。

再帰処理に関しては以上ですが、同じ項目に対して複数のエラーが発生した際の重複削除についてもメモしておきます!

// 重複削除
    errorWithProperties.filter((errorWithProperty, index) => {
      // propertyに対して重複してメッセージが発生しないように、propertyが同値の一つ目のindexでフィルター
      return (
        errorWithProperties.findIndex((searchErrorWithProperty) => {
          return searchErrorWithProperty.property === errorWithProperty.property
        }) === index
      )
    })

記事初投稿なので、問題があればコメント等くだされば嬉しいです。
閲覧ありがとうございました。

Discussion