JSON Schema
2020/02/19更新
目次
概要
JSON Schemaは、JSONデータの構造を定義するためのスキーマ言語である。JSON Schema自体もJSONで記述する。独自のJSONデータを定義した場合などに、与えられたデータがその定義に合致しているかどうかの確認(バリデーション)に使うことができる。
以下に、簡単なJSON Schemaの例を示す。
{
"type": "object", # 全体が1つのオブジェクトである
"properties": {
"name": { # 「name」という名前のキーを持つ
"type": "string" # 「name」の値は文字列である
},
"age": { # 「age」という名前のキーを持つ
"type": "integer", # 「age」の値は整数である
"minimum": 0 # 「age」の値は0以上である
}
}
}
上記のスキーマに対して合致する(validな)JSONデータは、例えば以下のようなものになる。
{
"name": "JSON太郎",
"age": 20
}
このように、typeを使ってJSONデータの値の型を記述し、さらにそれぞれの型ごとに定められた属性を使ってその値の特徴や制限事項を記述していく。
※注:以降、説明のため、JSONの中でYAMLスタイルのコメント# ~を記述しているが、実際にはJSONの中にはコメントは書けないので注意されたい。スキーマを作成する際にコメントを残したい場合は、後述するようにコメントが記述可能なYAMLで作成し、それをJSONに変換する方法を取ると良い。
型の定義
文字列
文字列は{"type": "string"}で定義する。
{
"type": "string"
}
minLengthとmaxLengthで、文字数の下限と上限を指定できる。また、patternで文字列の内容を正規表現で指定できる。
{
"type": "string",
"minLength": 8, # 8文字以上
"pattern": "^hoge" # 「hoge」から始まる
}
# ○ "hogehogehoge"
# × "hoge"
# × "fugafugafuga"
数値
数値は{"type": "number"}、または整数限定の場合は{"type": "integer"}で定義する。
{
"type": "number"
}
数値の範囲を指定するのにminimum(~以上)、maximum(~以下)、exclusiveMinimum(~より大)、exclusiveMaximum(~より小)を使用できる。また、multipleOfでその数の倍数のみに限定できる。
{
"type": "integer",
"minimum": 100, # 100以上
"multipleOf": 10 # 10の倍数
}
# ○ 100、110、120、…
# × 90、80、70、…
# × 101、102、103、…
オブジェクト
オブジェクトは{"type": "object"}で定義する。また、propertiesで各キー(プロパティ)を定義する。
{
"type": "object",
"properties": {
"name": {
"type": "string"
},
"age": {
"type": "integer",
"minimum": 0
}
}
}
上記の例では、文字列を値に持つ「name」というキーと、0以上の整数を値に持つ「age」というキーを持ったオブジェクトが定義できる。しかし、これだけでは、余分なキーが追加されている場合や、「name」や「age」が存在しない場合もOKとなってしまい、定義した意味がほぼ無い。そこで、余分なキーの追加を禁止する場合はadditionalProperties、また、定義したキーを必須とする場合はrequiredを使う。
{
"type": "object",
"properties": {
… 略 …
},
"additionalProperties": false, # 余分なキーの追加は禁止
"required": ["name", "age"] # 「name」キーと「age」キーは必須
}
# ○ { "name": "JSON太郎", "age": 20 }
# × { "name": "JSON太郎", "age": 20, "area": "東京" }
# × { "name": "JSON太郎" }
additionalPropertiesは、真偽値の代わりに{"type": "string"}などと指定すると、文字列を値に持つキーのみ追加を許可したりすることもできる。
存在が他のキーに依存する場合はdependenciesを使う。必ず組み合わせて使用しなければいけないキーがある場合などに有用である。
{
"type": "object",
"properties": {
… 略 …
},
"dependencies": {
"age": ["name"] # 「age」キーを使う場合は「name」キーが必須
}
}
他に、キー名を正規表現で指定できるpropertyNames、キーの数を制限できるminPropertiesとmaxProperties、正規表現にマッチするキー名の値を一度に指定できるpatternPropertiesなどを使って、キーの名前や数をより柔軟に指定することもできる。
{
"type": "object",
"propertyNames": {
"pattern": "^[A-Za-z0-9]+$" # 英数字からなるキー名のみ許可
},
"minProperties": 1, # キーの数の下限
"maxProperties": 2, # キーの数の上限
"patternProperties": {
"^str": { "type": "string" }, # 「str」で始まる名前のキーは文字列
"^int": { "type": "integer" } # 「int」で始まる名前のキーは整数
}
}
# ○ { "strHogehoge": "abcde", "intFugafuga": 12345 }
# × { "strHogehoge": "abcde", "intFugafuga": 12345, "intPiyopiyo": 67890 }
# × { "str_hogehoge": "abcde" }
# × { "strHogehoge": 12345 }
配列
配列は{"type": "array"}で定義する。また、itemsで各要素を定義する。
{
"type": "array",
"items": {
"type": "string" # 各要素は文字列
}
}
itemsを配列にすると、要素を先頭から順に一つずつ定義できる。この場合、要素の追加を禁止するにはadditionalItemsを使う。また、要素数を制限するのにminItemsとmaxItemsを使用できる。
{
"type": "array",
"items": [
{ "type": "number" }, # 0番目の要素は数値
{ "type": "string" }, # 1番目の要素は文字列
… 略 …
],
"minItmes": 2, # 最低2個の要素が必要
"additionalItems": false # 余分な要素の追加は禁止
}
# ○ [0, "hoge"]
# × [0, 1234]
# × [0]
additionalItemsは、真偽値の代わりに{"type": "string"}などと指定すると、文字列の要素のみ追加を許可したりすることもできる。
他に、最低限含まれていなければならない要素を定義するcontainsや、要素の重複を禁止するuniqueItemsなどが使用できる。
{
"type": "array",
"contains": {
"type": "number" # 数値の要素が含まれていなければならない
},
"uniqueItems": true # 要素の重複があってはいけない
}
# ○ [1, 2, 3, 4, 5]
# ○ [1, "a", "b", "c", "d"]
# × ["a", "b", "c", "d"]
# × [1, 1, 2, 2, 3]
真偽値とnull
真偽値は{"type": "boolean"}で定義する。nullは{"type": "null"}で定義する。
列挙値
enumを使うことで、取りうる値を列挙したものの中のいずれかのみに限定することができる。
# typeで型を指定し、enumで値を限定
{
"type": "integer",
"enum": [16, 32, 64]
}
# typeを指定せず、enumだけで値を限定することも可
{
"enum": ["hoge", "fuga", 123, 456]
}
組合せ
和集合
anyOfを使うことで、複数の型のいずれかであることを記述することができる。なお、単純なものであれば、typeを配列にして記述することもできる。
{
"anyOf": [
{ "type": "integer", "minimum": 100 },
{ "type": "string", "minLength": 3 }
]
}
# 「100以上の整数」もしくは「3文字以上の文字列」のいずれか
{
"type": ["integer", "string"]
}
# 整数または文字列のいずれか
補集合
notで、その型以外であることを記述できる。
{
"not" { "type": "string" }
}
# 文字列以外
定義の再利用
同じ型を複数の箇所で使用する場合、それぞれの場所で定義を書いていくと煩雑になってしまう。その場合は、definitionsを使うと独自の型として名前を付けて定義し、$refによって定義を使い回すことができる。
{
# definitionsで独自の型を定義できる
"definitions": {
"color": { # 「color」という名前の型を定義
"type": "string",
"pattern": "^#[0-9a-fA-F]{6}$"
}
}
"type": "object",
"properties": {
"foreground": {
"$ref": "#/definitions/color" # 「foreground」キーの値は「color」型
},
"background": {
"$ref": "#/definitions/color" # 「background」キーの値も「color」型
}
}
}
スキーマの作成とバリデーションの実行
スキーマの作成
JSON SchemaはJSONで記述しなければならないが、長大なJSONを手書きするのはいささか大変であり、またJSONの中にはコメントを残すこともできないため、より簡潔で柔軟な記述が可能なYAMLで記述し、それをJSONに変換するとよい。
以下は、Node.jsを使って、コマンドラインでYAMLをJSONに変換する例である。
npm install js-yaml js-yaml my_schema.yaml > my_schema.json
バリデーションの実行
JSON Schemaが完成し、対象のJSONデータが与えられたら、いよいよバリデーションの実行である。
以下は、Node.jsを使って、コマンドラインでバリデーションを実行する例である。
npm install jsonschema-commandline jsvalidate -s my_schema.json data.json
上記を実行すると、以下のような出力が得られる。
ValidatorResult {
instance:
{
… 略 …
},
schema:
{
… 略 …
},
propertyPath: 'instance',
errors: [],
throwError: undefined,
disableFormat: false }
やや分かりにくいが、終盤にerrorsというキーがあるのが分かる。データがスキーマに合致しないなどの問題があれば、ここにエラー内容が出力される。バリデーションを無事に通過した場合は、errorsは空配列[]となる。