python で jsonschema を使ってバリデートしてみる

メモレベルですが。

参考

インストールする

pip install jsonschema

コードを用意する

下記のようなコード schemacheck.py を用意します。
辞書 item に jsonschema のバリデートをかけて行きます。

import json
from jsonschema import validate, ValidationError

with open('schema.json') as schema_json:
    schema_dict = json.load(schema_json)

item = {
    "animal": "cat"
}

try:
    validate(item, schema_dict)
    print("OK")
except ValidationError as e:
    print(e.message)

JSON スキーマの基本形

同階層に下記のようなファイル schema.json を用意します。

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "animals schema validate",
  "description": "動物のデータを投入する際に使用する JSON の形式をチェックします。",
  "type": "object"
}

属性 $shema で使用するスキーマのバージョンを指定しています。
他の属性は割愛。

属性値に型を指定する

schema.json に情報を付け加えてみます。
下の形だと属性 name の属性値は文字列、属性 age の属性値は数値型を指定しています。

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "animals schema validate",
  "description": "動物のデータを投入する際に使用する JSON の形式をチェックします。",
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },
    "age": {
      "type": "integer"
    }
  }
}

失敗

ファイル schemacheck.py を下の通りに修正してみます。

item = {
    "kind": "cat",
    "name": 1,
}

schemacheck.py を実行すると下の通りのエラーが表示されます。
属性 name の属性値を string に指定したのに数値なのでちゃんとバリデーションが怒られているようです。

1 is not of type 'string'

成功

属性 name の属性値を文字列にするとエラーは発生しません。

item = {
    "kind": "cat",
    "name": "tama",
}

必須属性名を指定する

required を指定することで、属性名を必須にできます。
下の場合は name と age が必須になります。

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "animals schema validate",
  "description": "動物のデータを投入する際に使用する JSON の形式をチェックします。",
  "type": "object",
  "required": ["name", "age"],
  "properties": {
    "name": {
      "type": "string"
    },
    "age": {
      "type": "integer"
    }
  }
}

失敗

item = {
    "kind": "cat",
    "name": "tama",
}

属性名 age が無いのでちゃんとエラーが出ています。

'age' is a required property

成功

item = {
    "kind": "cat",
    "name": "tama",
    "age": 10,
}

不要な属性を禁止する

additionalProperties を指定することで、余計な属性名が JSON に紛れ込むのを防ぎます。
下の例では、 name と age 以外の属性名が JSON に含まれていた場合にエラーが発生します。

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "animals schema validate",
  "description": "動物のデータを投入する際に使用する JSON の形式をチェックします。",
  "type": "object",
  "additionalProperties": false,
  "required": ["name", "age"],
  "properties": {
    "name": {
      "type": "string"
    },
    "age": {
      "type": "integer"
    }
  }
}

失敗

item = {
    "kind": "cat",
    "name": "tama",
    "age": 10,
}
Additional properties are not allowed ('kind' was unexpected)

成功

item = {
    "name": "tama",
    "age": 10,
}

配列内にオブジェクトを指定する

よくある配列内にオブジェクトを入れている次のようなパターンがあります。

item = {
    "farm": "hogehogeFarm",
    "place": "Chiba",
    "animals": [
        {
            "kind": "cat",
            "color": "brown",
        },
        {
            "kind": "dog",
            "color": "black"
        }
    ]
}

これには items を使います。

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "animals schema validate",
  "description": "動物のデータを投入する際に使用する JSON の形式をチェックします。",
  "type": "object",
  "additionalProperties": false,
  "required": ["farm", "place", "animals"],
  "properties": {
    "farm": {
      "type": "string"
    },
    "place": {
      "type": "string"
    },
    "animals": {
      "type": "array",
      "items": {
        "type": "object"
      }
    }
  }
}

required や properties を入れ子で指定すると下記のようになります。

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "animals schema validate",
    "description": "動物のデータを投入する際に使用する JSON の形式をチェックします。",
    "type": "object",
    "additionalProperties": false,
    "required": [
        "farm",
        "place",
        "animals"
    ],
    "properties" :{
        "farm": {
            "type": "string"
        },
        "place": {
            "type": "string"
        },
        "animals": {
            "type": "array",
            "items": {
                "type": "object",
                "required": ["kind", "color"],
                "properties": {
                    "kind": {
                        "type": "string"
                    },
                    "color": {
                        "type": "string"
                    }
                }
            }
        }
    }
}

$ref で入れ子を防ぐ

items の中身は、オブジェクトになりますが、これを直接入れ子で書くと分からなくなるので $ref を使って参照することが出来ます。

"items": {
  "$ref": "#definitions/animal"
}

参照先は definitions に定義する事が来ます。

"definitions": {
    "animal": {
    "type": "object",
    "required": ["kind", "color"],
    "properties": {
        "kind": {
            "type": "string"
        },
        "color": {
            "type": "string"
        }
        }
    }
},

最終的には下記の通り。

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "animals schema validate",
  "description": "動物のデータを投入する際に使用する JSON の形式をチェックします。",

  "definitions": {
    "animal": {
      "type": "object",
      "required": ["kind", "color"],
      "properties": {
        "kind": {
          "type": "string"
        },
        "color": {
          "type": "string"
        }
      }
    }
  },

  "type": "object",
  "additionalProperties": false,
  "required": ["farm", "place", "animals"],
  "properties": {
    "farm": {
      "type": "string"
    },
    "place": {
      "type": "string"
    },
    "animals": {
      "type": "array",
      "items": {
        "$ref": "#definitions/animal"
      }
    }
  }
}

入れ子部分を外部ファイルにする

definitions を使用して入れ子は防げましたが別ファイルにしたい場合があります。

これは、RefResolver を使用することで解決出来ます。

import os
import json
from jsonschema import validate, ValidationError,  RefResolver

with open('schema.json') as schema_json:
    schema_dict = json.load(schema_json)

item = {
    "farm": "hogehogeFarm",
    "place": "Chiba",
    "animals": [
        {
            "kind": "cat",
            "color": "brown",
        },
        {
            "kind": "dog",
            "color": "black"
        }
    ]
}

try:
    schema_path = 'file:///{0}/'.format((os.getcwd()).replace("\\", "/"))
    ref = RefResolver(schema_path, schema_dict)
    validate(item, schema_dict, resolver=ref)
    print("OK")
except ValidationError as e:
    print(e.message)

親のスキーマは次の通り。(./schema.json)

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "animals schema validate",
  "description": "動物のデータを投入する際に使用する JSON の形式をチェックします。",

  "type": "object",
  "additionalProperties": false,
  "required": ["farm", "place", "animals"],
  "properties": {
    "farm": {
      "type": "string"
    },
    "place": {
      "type": "string"
    },
    "animals": {
      "type": "array",
      "items": {
        "$ref": "animal.json"
      }
    }
  }
}

子のスキーマは次の通り。(./animal.json)

{
  "type": "object",
  "required": ["kind", "color"],
  "properties": {
    "kind": {
      "type": "string"
    },
    "color": {
      "type": "string"
    }
  }
}