Racc で JSON スキーマを生成する DSL を書いてみた

Ruby には Yacc ならぬ Racc というものがあり、Yacc のようなパーサをつくることができます。 Yacc のことやプログラミング言語をつくることに詳しいわけではないのですが、ちょっとお試しをしてみました。 まがりなりにも独自の言語(といっても JSON schema をつくるためだけのもの)ができたことになります。

動機

JSON には、余分なカンマを打てないこと、コメントアウトができないことなど、独特の不便な点があります。また、フィールド(項目)名を " で囲まなければいけないことも、なんとなく私には億劫だったりもします。

もちろん、これらのことは、JSONYAML で書いて変換するようにしてしまえば問題はないとも言えます。すべての JSONYAML ですので、これは可能なことです。単に YAML を読み込んで、JSON で出力すればいいだけです。

わざわざ新しい言語を覚えなくてもいいのが JSONYAML の利点ですから、これで解決できるなら、そうしたほうがいいと言えそうです。

しかし、JSONYAML の形式で表現しなければいけないことからくる面倒くささがもしあるなら、そこには別言語を試してみる余地もあるのかもしれません。

文法

といった名目で(?)、JSON schema の内容を簡潔に表現する新しい DSL をつくってみました。その文法を簡単にご紹介します。

データ型の表現

データ型は <> で囲んで表現します。

<object> { }

こう書くと

{ "type": "object" }

と同等になります。

プロパティの表現

プロパティは、先頭に + もしくは - をつけて表現します。 +required を表します。 ですので、

<object> {
  + name <string>
  + price <number>
}

と書くと、

{
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },
    "price": {
      "type": "number"
    }
  },
  "required": [
    "name",
    "price"
  ]
}

と同等になります。

ボキャブラリの表現

フィールド以外の JSON schema ボキャブラリは @ をつけて表します。 なお、何が定義されたボキャブラリであるかなどは、パーサはまったく関知しません。

<object> {
  + email <string> { @format: "email" }
  @additional-properties: false
}

たとえばこのように書くと

{
  "type": "object",
  "properties": {
    "email": {
      "type": "string",
      "format": "email"
    }
  },
  "required": [
    "email"
  ],
  "additionalProperties": false
}

こうなります。

キーワードの表現

$id$schema などのキーワードは、基本的に $ をつけてそのまま書きます。

<object> {
  $schema: "http://..."
}

と書けば

{
  "type": "object",
  "$schema": "http://..."
}

ということになります。

ただし、$id{} の外にも書けるようになっています。これは、id を名前のように目立たせたい場合があると思ったからです。

$id: "http://example.com/foo.schema.json"
<object> { }

これは

{
  "$id": "http://example.com/foo.schema.json",
  "type": "object"
}

となります。

$defs$ref の表現

$defs$ref は、若干わかりにくいかと思いました。ですので、こんなふうに書けるようにしてみました(もう少しこなれたものにしたいところです)。

<array> {
  @items: [
    { $ref: (ref employee) }
  ]

  def employee is <object> {
    + name <string>
    + email <string> { @format: "email" }
    - date-of-birth <string> { @format: "date" }
  }
}

こう書くと、

{
  "type": "array",
  "items": [
    {
      "$ref": "#/$defs/employee"
    }
  ],
  "$defs": {
    "employee": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        },
        "email": {
          "type": "string",
          "format": "email"
        },
        "dateOfBirth": {
          "type": "string",
          "format": "date"
        }
      },
      "required": [
        "name",
        "email"
      ]
    }
  }
}

こうなります。

リポジトリ

プログラムはこちらリポジトリから見ていただけます。 ただ、お試しの迷い書き状態なので、実装できていないことやバグも多いかなと思います(テスト書きます)。