yojikのlog

yojikのブログです

オレオレ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のサブセットになってるので、ちょっと便利かなーと思います。

注意: 眠いので、かなりバグが混じってるかもしれません。まぁアイディアということで。。