このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

JSONスキーマと総称コレクション型

JSONスキーマ(http://json-schema.org/)を実際に使おうと、あれこれ考えております。

まず、言語道断に書きにくい/読みにくいという問題には代替構文を作ろうか、と(上の「JSONスキーマのローカル構文に関する思案・試案」参照)。ただし、これはあくまで構文レベルでの対処で、オリジナルJSONスキーマのセマンティクスを変える気はありません。また、代替構文からオリジナルのJSON形式への変換ができることも要請します。そうじゃないと、「JSONスキーマに関する良いツールが出ても使えない」なんて事になっちゃいますから。

そんなわけで、JSONスキーマ仕様自体を拡張するのは避けたいのですが、それでも「やっぱり、どうしても足りないな」というところがいくつかあります。その難点のひとつは、総称の有限集合型がないことです -- 以下に問題点の指摘と対処案を述べます。

内容:

JSONスキーマにおけるタプルとリスト

JSONスキーマでは、配列(array)の用途が2つ想定されています。1つはタプルであり、もう1つはリストです。この2つの特徴は次のとおり。

  • タプルは、項目の数が決まっている。項目の型はバラバラでもよい。
  • リストは、項目の数は自由(項目なしの空でもよい)。項目の型は決まっていて、どの項目も同じ型。

項目の数が2で、項目の型が string, integer であるタプル、それと項目の型がintegerであるリストは次のようにスキーマ定義されます。

{
  "description" : "タプルとしての配列",
  "id" : "ArrayAsTuple"
  "type" : "array",
  "items" : [
    {"type" : "string"}, 
    {"type" : "integer"}
  ]
}

{
  "description": "リストとしての配列",
  "id" : "ArrayAsList"
  "type" : "array",
  "items" : {"type" : "integer"}
}

私家版スキーマ構文で書けば:

// タプルとしての配列
ArrayAsTuple = array [string, integer];

// リストとしての配列
ArrayAsList = array [integer*];

リストとしての配列を総称型で書けば List<integer> と書けるでしょう。タプル型のほうも、型パラメータを可変引数っぽく渡せるならば、Tuple<string, integer> となるでしょう。

JSONスキーマは、総称型の構文を明白にサポートはしてませんが、総称の(型パラメータを持つ)コレクション型を事実上サポートしています。

総称の有限集合型も欲しいんですけど

総称型 Set<T> とは、Tをベースとした有限集合の型です。T = {1, 2, 3} だとすると、Set<T> = Set<{1, 2, 3}> に含まれる値(インスタンス)は:

  • {}, {1}, {2}, {3}, {1, 2}, {1, 3}, {2, 3}, {1, 2, 3}

以上の8個です。

有限集合型が必要になる典型的状況は、HTMLフォームの「checkbox群」や「multipleを指定されたselect」からやってくるデータです。例えば、バナナ、オレンジ、リンゴのなからから好きなものをいくつ選んでいいとしましょう。

  <div class="checkboxGroup">フルーツ:
    <input type="checkbox" name="fruit" value="1" />
    バナナ
    <input type="checkbox" name="fruit" value="2" />
    オレンジ
    <input type="checkbox" name="fruit" value="3" />
    リンゴ
  </div>

このとき、fruitの値の型は Set<{1, 2, 3}> となります。

スキーマ属性により、集合型とバッグ型を導入する

タプル型とリスト型が配列型の変種として定義されているので、集合型もやはり配列型に細工するのがスジでしょう。実際、Set<T> の表現として List<T> を使うことができます。次の2点を考慮すれば、リストをセットとみなせるのです。

  1. 同じ項目が2度出現しても、1個分だとみなす。例えば、[1, 2, 1] と [1, 2] は区別しない。
  2. 項目の出現順序は無視する。例えば、[1, 2] と [2, 1] は区別しない。

アプリケーションプログラム側が、このルールで解釈するという前提があれば、スキーマとしては単なる配列でもかまいません。

Fruit = array [integer(enum = [1, 2, 3])*];

しかし、これではスキーマの外部に取り決めが存在することになり、スキーマ自体を見ても「単なるリスト」と「集合を表すリスト」の区別が付きません。

できるだけインパクトが少ない方法で集合型を導入しようとするなら、スキーマ属性の追加しかありません。配列型に対して、次の2つのスキーマ属性を追加します。

スキーマ属性名 意味 値の型 デフォルト値
unique 重複を許さない boolean false
unordered 順序を考慮しない boolean false

すると、Set<{1, 2, 3}> は次のように書けます。

Fruit = array(unique = true, unordered = true) 
          [integer(enum = [1, 2, 3])*];

unique = false, unordered = true だと、順序は無視するが項目(要素)の重複は許すようなコレクションとなります。これはバッグ(bag)とかマルチセット(multiset)とか呼ばれるコレクション型ですね。総称集合型 Set<T> だけでなく、ついでに総称バッグ型 Bag<T> も記述できました。

他の案

もう少し安直な方法として、multiple というスキーマ属性も考えてみました。multiple = true とセットされると、その型の多重値を許す、つまりは有限集合型と同じになります。代替構文で例を挙げると:

/* 複数の文字列 */
Hobbies = string(multiple = true);

/* 1, 2, 3のなかから、複数の整数 */
Fruit = integer(multiple = true, enum = [1, 2, 3]);

ちょっと安直すぎるきらいがありますが、こういう安直さが便利な場面があるかもしれません。

僕の代替構文をベースに考えるなら、キーワードとして list, tuple, set, bag を導入してしまう手もあります。

// 項目は2つ、x座標とy座標
Point2D = tuple [integer, integer];

// 任意個数の点の列
Polyline = list [Point2D];

// 点の集合
PointSet = set [Point2D];

// 重なることができる点の集合
PointBag = bag [Point2D];

こうすると、記述の煩雑さを抑えることができるし、用途と操作の仕方がハッキリと伝わる点がいいんじゃないかと思います。

あと、それと

JSONスキーマで不足や不満を感じて、いくつかのスキーマ属性を追加導入しました。が、これがベストの解決策かどうかは疑問です。僕の目的では、オリジナルのJSONスキーマ仕様を尊重しながらも、自家製スキーマ言語に必要な機能を乗せる方向がふさわしい気がします。現状のスキーマ言語試案はまだダメダメなところがあるので、もうちょい考えてみます。