オレオレJSONスキーマ定義言語を考えた
田町deナイトの宣伝しなきゃなーと思いつつも、ちょっと脱線中です。
檜山さんのこのエントリにあった、JSONスキーマを説明するための簡易記述方法をつらつら眺めていました。
integer {minimum : 1, maximum : 10}
array {} { * : integer }
object {} { "name" : string, "age" : integer {minimum : 0, maximum : 150}, "mailAddress" : string }
なんとなく関数呼び出しに似ている気がしてきました。つまりtype属性定義=関数呼び出しと見立てて、こんな感じになりました
integer({minimum : 1, maximum : 10}); array({} ,{"*";integer({})}); object({}, { "name": string(), "age" : integer({minimum : 0, maximum : 150}), "mailAddress" : string() ]);
integerはスキーマ属性となる引数オブジェクトを一つとる関数で、arrayは第一引数がスキーマ属性を表現するオブジェクト、第二引数が配列要素に適用するためのスキーマ定義になります(*がついてるので全要素に適用)。objectは、第一引数がスキーマ属性を表現するオブジェクト、第二引数がpropertiesに適用するためのスキーマ定義になります。
檜山さんの折れ線グラフの例だとこんな感じになります。読みやすい!!
array( {"minItems":2,"maxItems":4}, {"*" : object({},{ "x":integer({minmum:0 , maximum:100}), "y":integer({minmum:100 , maximum:200}) })});
さらにさらに、こんな感じに使えるとうれしいと思いました。たとえば、、
pointSchema = array( {"minItems":2,"maxItems":4}, {"*" : object({},{ "x":integer({minmum:0 , maximum:100}), "y":integer({minmum:100 , maximum:200}) })}); pointSchema([{x:50,y:160} ,{x:50,y:199}]) ); //true pointSchema([{x:50,y:160} ,{x:50,y:309}]) ); //false
つまり各type定義になってる関数を呼び出すと、スキーマ検証用の関数が返ってくる感じです。
実装してみる
それでは簡単にそれぞれのtype定義を実装してみます
string
function string() { return function(obj) { return (typeof(obj)=="string") } } //本当はいろいろスキーマ属性を定義しなきゃいけないけど、ちょっとサボってます
integer
function integer(schemaAttr) { return function(obj) { if(typeof(obj)!="number") return false if(schemaAttr==null) return true; if(schemaAttr["minimum"]!=null){ if(obj < schemaAttr.minimum ) {return false;} } if(schemaAttr["maximum"]!=null){ if(obj > schemaAttr.maximum ) {return false;} } return true; } }
array
function array(schemaAttr, peroperties) { return function (obj) { if(schemaAttr.minItems){ if(obj.length < schemaAttr.minItems ) {return false;} } if(schemaAttr.maxItems){ if(obj.length > schemaAttr.maxItems ) {return false;} } if(peroperties["*"]) { items = peroperties["*"]; for(i in obj) { if(items(obj[i])==false) return false; } return true; } for(i in obj) { if(peroperties[i] != null) { f = peroperties[i]; if(!f(obj[i])) return false; } } return true; } }
object
function object(schemaAttr , peroperties) { return function(obj) { for(i in obj) { if(peroperties[i] != null) { f = peroperties[i]; if(!f(obj[i])) return false; } } return true; } }
折れ線グラフJSONを検証してみる
グラフの定義を実行して代入!
pointSchema = array( {"minItems":2,"maxItems":4}, {"*" : object({},{ "x":integer({minmum:0 , maximum:100}), "y":integer({minmum:100 , maximum:200}) })});
その関数をRhinoで実行してみると
print(pointSchema([{x:50,y:160} ,{x:50,y:199}]) ); //true print(pointSchema([{x:50,y:160} ,{x:50,y:309}]) ); //false
実行可能なスキーマ定義って、どこか魅力的ですね。パーサの必要が無いこともあり言語内DSLっぽいです。定義はJSONではないのですが、JavaScriptのサブセットになってるので、ちょっと便利かなーと思います。
注意: 眠いので、かなりバグが混じってるかもしれません。まぁアイディアということで。。