LoginSignup
7
5

More than 1 year has passed since last update.

Ajvを使ってJSONのバリデーションをする(JavaScript/TypeScript)

Last updated at Posted at 2020-08-12

環境

はじめに

Ajv はブラウザ環境でも使用できますが、ここでは NodeJS(TypeScript)で使った場合の例になります。
githubページ に必要十分な情報はありますが、分量が多いので、ここでは主にスキーマ記述例をまとめています。

ブラウザで使う場合の参照先は、こちら(github)

Ajv の特徴

 ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'))
  • 他の同種のライブラリより高速(参照先)。
  • ajv-18n を使うと、エラーメッセージの多言語化も対応可能です。

使い方

import Ajv from 'ajv'
// ここで自分たちの schema ファイルを読み込みます。
import schema from 'somewhere/schema.json'

const ajv = new Ajv()
const validate = ajv.compile(schema)

const valid = validate(data)
if (!valid) console.log(validate.errors)

schema ファイルの例

JSON ではコメントは使えませんが、ここでは説明のために //〜 としてコメントを追加して、且つ改行を入れています。

{
  // この値は他のschemaファイルから参照される時に使われるだけなので、
  // 実際にこのURLにschemaファイルを置いてダウンロード出来るようにする必要はありません。
  "$id": "http://example.com/schemas/schema.json",

  // この設定がない場合、draft 6 meta-schema に対してバリデーションが行われます。
  "$schema": "http://json-schema.org/draft-07/schema",

  "title": "Your title comes here",
  "description": "Your description comes here",
  // schema ファイルは、基本的に object になると思われるので、
  // トップレベルの type  object とします。
  "type": "object",

  // これにより、定義していないフィールドがあった場合にエラーとします。
  // 尚、定義されたレイヤーでしか有効ではありません。
  // 階層化された下位層の object に対しては、それ毎に定義が必要です。
  // 基本的に、"type": "object" の後にはつけたほうがいいでしょう。 
  "additionalProperties": false,

  // definitions に定義した値は、$ref としてスキーマ内から参照可能です(後述)。
  "definitions": {
    "languageStrings": {
      "description": "This is an example of definition",
      "type": "object",
      "minProperties": 1,
      "examples": [
        {
          "en": "English title",
          "ja": "日本語タイトル"
        }
      ],
    }
  },

  // properties にフィールドの定義をしていきます。
  "properties": {
    "$schema": {
      "type": "string"
    },
    "stringExample": {
      "description": "This is an example of string type",
      "type": "string"
    },
    "stringOfDateFormatExample": {
      "description": "This is an example of string type of the date format",
      "type": "string"
      "format": "date",
      "maxLength": 10,
      "examples": ["2020-01-23"]
    },
    "stringOfPatternExample": {
      "description": "This is an example of string type of the pattern",
      "type": "string",
      "pattern": "^0x[!-~]{40}$",
      "maxLength": 42
    },
    "numberExample": {
      "description": "This is an example of number type",
      "type": "number"
    },
    "arrayExample": {
      "description": "This is an example of array type",
      "type": "array",
      "items": {
        "type": "string"
      },
      "uniqueItems": true
    },
    "enumExample": {
      "description": "This is an example of enum",
      "enum": [
        "one",
        "two",
        "three"
      ]
    },
    "refExample": {
      "description": "This is an example of $ref",
      "$ref": "#/definitions/languageStrings",
      "examples": [
        {
          "en": "English"
        }
      ]
    },
    "objectWithAnyOfExample": {
      "description": "This is an example of object with anyOf",
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "from": {
          "type": "string",
          "format": "date",
          "maxLength": 10,
          "examples": ["2020-01-23"]
        },
        "to": {
          "type": "string",
          "format": "date",
          "maxLength": 10,
          "examples": ["2020-03-21"]
        }
      },
      "anyOf": [
        {
          "required": [
            "from",
            "to"
          ]
        },
        {
          "required": [
            "from"
          ]
        },
        {
          "required": [
            "to"
          ]
        }
      ]
    },
    "dependenciesExample": {
      "description": "This is an example of object, dependencies and required",
      "type": "object",
      // 前述の通り、トップの階層で設定していても、ここでも "additionalProperties": false としなければ、
      // 意図しないフィールドが設定できてしまいます。
      "additionalProperties": false,
      // この定義により、xx を定義した時に、必ず yy を定義する、というルールを付けることが出来ます。
      // ここの例では width、height、depth のいずれかが定義された場合は、unit を定義しなければエラーになります。
      "dependencies": { "width": ["unit"], "height": ["unit"], "depth": ["unit"] },
      // ここに定義されたものは必須項目となります。
      "required": [
        "note"
      ],
      "properties": {
        "width": {
          "type": "number"
        },
        "height": {
          "type": "number"
        },
        "depth": {
          "type": "number"
        },
        "unit": {
          "enum": [
            "mm",
            "cm",
            "m"
          ]
        },
        "description": {
          "$ref": "#/definitions/languageStrings"
        },
        "note": {
          "$ref": "#/definitions/languageStrings"
        }
      }
    },
  },
  // ここに定義されたものは必須項目となります。
  "required": [
    "numberExample",
    "arrayExample"
  ],
  "minProperties": 0
}

バリデーションルールについて

上記以外にも様々なバリデーションルールが設定可能です。
参照先:
https://ajv.js.org/json-schema.html
https://ajv.js.org/json-type-definition.html

セキュリティ対応

https://github.com/ajv-validator/ajv#security-considerations にリストアップされている内容に準拠しているかを、以下のように Jest などで確認しておきましょう。

import Ajv from 'ajv'
import schema from 'somewhere/schema.json'

// see: https://github.com/ajv-validator/ajv#security-considerations
describe('isSchemaSecure', () => {
  const ajv = new Ajv()
  const isSchemaSecure = ajv.compile(
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    require('ajv/lib/refs/json-schema-secure.json')
  )
  expect(isSchemaSecure(schema)).toBe(true)
})

例えば、以下の通り formatpattern を指定した時は、maxLength を指定しないとチェックに引っかかります。

const isSchemaSecure = ajv.compile(require('ajv/lib/refs/json-schema-secure.json'));

const schema1 = {format: 'email'};
isSchemaSecure(schema1); // false

const schema2 = {format: 'email', maxLength: MAX_LENGTH};
isSchemaSecure(schema2); // true

ブラウザで確認

https://www.jsonschemavalidator.net/ に schema と JSON を入れれば確認できます。

CLI で確認

ajv-cli を使って、CLI でチェックすることも出来ます。

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5