Catyのスキーマ記述言語は、JSONスキーマ仕様(http://tools.ietf.org/html/draft-zyp-json-schema-01)に基づいています。構文は違いますが、できる限り本家JSONスキーマのセマンティクスを保存するように努めています。ですから、JSONスキーマの書き方がなんとなくでも理解できた人なら、Catyスキーマも書けます。そして、Catyスキーマのほうがはるかに簡潔に記述できます。
以下では、「最近のJSONスキーマを解説します」と同じ構成で、Catyスキーマの記述パターンを説明します。Catyスキーマがいかに単純明快かが分かると思います*1。
内容:
事例
「しょうがないので、マイクロフォーマットのデータモデルを僕が考えた」で例題とした、マイクロフォーマットのhCardに適合した簡単な人物情報の例を挙げましょう。インスタンスは次のようなものです。
{ "vcard" : { "fn" : "檜山 正幸", "email" : ["hiyama{AT}chimaira{DOT}org"], "url" : ["http://www.chimaira.org/", "http://d.hatena.ne.jp/m-hiyama/"] } }
次の約束をします。
- fnプロパティは必須
- emailプロパティは最低1つの項目を持つ配列
- urlプロパティは任意個数の項目を持つ配列、空でもよい
メールアドレスとURLは、本来なら専用のデータ型を定義すべきでしょうが、文字列で済ませます。
Catyスキーマの型表現は、インスタンスとほとんど同じ形になります。次のとおりです。
{ "vcard" : { "fn" : string, "email" : [string, string*], "url" : [string*] } }
JSONスキーマなら以下のようになります。
{ "type" : "object", "properties" : { "vcard" : { "type" : "object" "properties" : { "fn" : {"type", "strning"}, "email" : { "type" : "array", "items" : [ {"type": "string"} ], "additionalProperties" : {"type": "string"} }, "url" : { "type" : "array", "items" : {"type": "string"} } } } } }
本家JSONスキーマに不公平にならないように、JSONスキーマのメリットも言っておきます。(「最近のJSONスキーマを解説します」より引用):
JSONスキーマの特徴は、型表現がまたJSONデータであることです。これはメタデータをデータとして扱えるという大きな利点があります。
Catyスキーマの型表現自体はJSONデータではありません。JSONデータとすごく似てますが、実は違います。よって、JSONデータを扱うツールでCatyスキーマを扱うことはできません。例えば、JSONデータをコンテキストとするテンプレートエンジンへの入力としてCatyスキーマは使えません。JSONスキーマならそれができます。
型の分類
「最近のJSONスキーマを解説します」にある分類とまったく同じ内容ですが再掲します。
- 基本スカラー型: integer, number, string, boolean, null
- リスト型: 項目がすべて同じ型である配列型、項目の個数は任意
- タプル型: 決まった個数の項目を持つ配列型
- タプル+リスト型: 決まった個数の項目と、それに続く同じ型の任意個の項目を持つ配列型
- 閉じたオブジェクト型: 決まった個数のプロパティを持つオブジェクト型
- 開いたオブジェクト型: 決まった個数のプロパティと、同じ型の任意個のプロパティを持つオブジェクト型
- ユニオン型: 複数の型のどれかを意味する型
- 列挙型: 有限個の定数リテラルのどれかを意味する型
- 全称型: any
Catyスキーマの記述パターン
型の分類ごとに、どのようにスキーマを書くかを説明します。
基本スカラー型
型の名前そのものを書きます。
<型の名前>
例えば、integer とか。
基本スカラー型の名前は次の5つで全部です。nullはスカラーとは別な「特殊な型」として扱うこともあります*2。
- integer
- number
- string
- boolean
- null
リスト型
リストの項目の型表現を、<項目の型表現>と書くとします。リスト型は次のとおりです。
[<項目の型表現>*]
星印は正規表現の「任意回の繰り返し」の記号です。例えば整数値のリストは [integer*] です。空リストも含まれます。
タプル型
タプルの項目ごとの型を表現する型表現を、<項目の型表現1>、<項目の型表現2>などと書くとします。タプル型は次のとおりです。
[<項目の型表現1>, <項目の型表現2>, ..., <項目の型表現n>]
例えば、[string, string, integer] とか。
タプル+リスト型
タプルの項目の型を表現する型表現を、<項目の型表現1>、<項目の型表現2>など、残りの項目の型は<残余項目の型表現>と書くとします。タプル+リスト型は次のとおりです。
[<項目の型表現1>, <項目の型表現2>, ..., <項目の型表現n>, <残余項目の型表現>*]
例えば、最初が真偽値で、後は文字列が並ぶような配列なら、[boolean, string*] です。
閉じたオブジェクト型
オブジェクトのプロパティ名を<プロパティ名1>、<プロパティ名2>など、プロパティ値の型を表現する型表現を、<プロパティの型表現1>、<プロパティの型表現2>などと書くとします。閉じたオブジェクト型は次のとおりです。
{ "プロパティ名1" : <プロパティの型表現1>, "プロパティ名2" : <プロパティの型表現2>, ... "プロパティ名n" : <プロパティの型表現n> }
開いたオブジェクト型
オブジェクトのプロパティ名を<プロパティ名1>、<プロパティ名2>など、プロパティ値の型を表現する型表現を、<プロパティの型表現1>、<プロパティの型表現2>など、残りのプロパティの型は<残余プロパティの型表現>と書くとします。開いたオブジェクト型は次のとおりです。
{ "プロパティ名1" : <プロパティの型表現1>, "プロパティ名2" : <プロパティの型表現2>, ... "プロパティ名n" : <プロパティの型表現n>, * : <残余プロパティの型表現>? }
最後の<残余プロパティの型表現>には疑問符を付けるのが正統的記法ですが、「しばしば忘れる」という人間的な理由で疑問符の省略を許します。
ユニオン型
ユニオン型を構成する型の表現を <型表現1>、<型表現2>などとします。ユニオン型は次のとおりです。
(<型表現1> | <型表現2> | ... | <型表現n>)
縦棒で区切って型表現を並べます。外側を囲んでいる丸括弧は省略することもあります。Catyでは、成分となる各型(alternatives)が排他的であることを要求します。
列挙型
列挙型は次のとおりです。
(<リテラル1> | <リテラル2> | ... |<リテラルn>)
縦棒で区切って定数リテラルを並べます。外側を囲んでいる丸括弧は省略することもあります。一番短い表現になるように、各リテラルは違う値になるように書く必要があります。
全称型
any です。
オプショナル指定
プロパティや項目がオプショナルであることを表すには、次のように書きます。
<型表現>?
お尻に疑問符を付けます。正規表現と同じです。
リテラルとシングルトン型
すべてのJSONリテラルは、その値だけを持つシングルトンセットである型を表します。型表現とリテラルを自由に混ぜることができます。例えば、{"family-name" : "檜山", "given-name" : string(minLength=1)} とか、(integer | "infinity" | null) とか。
スキーマ属性
型ごとに有効なスキーマ属性は、型表現に続けて (<属性名1>=<属性値1>, <属性名2>=<属性値2>, ...) の形で書きます。integer(minimum=0, maximum=5)、[string*](maxItems=10) とか。属性の名前と値は、本家JSONスキーマに従いますが、一部の属性(requires, default, readonlyなど)はサポートしません -- 型システムの整合性を壊してしまう可能性があるからです。型システムと相性が悪いスキーマ属性はアノテーションとして記述するかもしれません。