Manatee.Json の紹介


JSON Schema 便利ですよね。現在の規格は draft-07 で程良く枯れてきたように感じます。これを .NET で扱うライブラリにはいくつか候補があります。そのなかでも Manatee.Json を紹介したいと思います。

特徴

JSON 関連で大抵のことはできるだけの機能が実装されています。その中でも特筆に値するのは以下の2点です。
- Supports JSON Schema INCLUDED AND FREE!
- 良さげなソリューションがないから自分で書いた(意訳)

Json.Net の JSON Schema は nuget で簡単にインストールできるけど実は有料という…後から知って軽くトラブりました。有料でもいいんだけど取り回しにくい…

簡単に動かす例

せっかくなので F# を使ってみます。必要なファイルは Gist にも置いておきました。
まずは dotnet-sdk をなんらかの方法でインストールしてください。dotnet コマンドを使って開発環境を作りましょう。

dotnet new console -lang F# -o sample
dotnet add package Manatee.Json

名前と数が入っているオブジェクトです。これをバリデーションしてみます。(item.json

{
  "name": "ペン",
  "count": 3
}

対象には名前と数が必ず入っている、と JSON Schema で定義します。(schema.json

{
    "$schema": "http:\/\/json-schema.org\/draft-07\/schema#",
    "type": "object",
    "properties": {
        "name": {
            "type": "string"
        },
        "count": {
            "type": "integer"
        }
    },
    "required": [
        "name",
        "count"
    ]
}

JSON Schema の条件に合致していたら Item クラスのインスタンスに値をセットして標準出力に表示、合っていなかったら例外を投げます。(Program.fs
バリデーションが通らなかったときのエラーメッセージは適当です。Manatee.Json ver9 に慣れてしまってるので これ を見て検討必要かも。

// for Manatee.Json ver10
open System
open System.IO
open System.Linq
open Manatee.Json
open Manatee.Json.Schema
open Manatee.Json.Serialization

type Item() =
    member val Name = "" with get, set
    member val Count = 0 with get, set

[<EntryPoint>]
let main argv =

    let serializer = JsonSerializer()

    let schema =
        File.ReadAllText (if argv.Length > 0 then argv.[0] else "schema.json")
        |> JsonValue.Parse
        |> serializer.Deserialize<JsonSchema>

    let json =
        File.ReadAllText (if argv.Length > 1 then argv.[1] else "item.json")
        |> JsonValue.Parse

    let result = schema.Validate json

    if not (result.IsValid)
    then
        result.AdditionalInfo.Select (fun i -> i.Key + i.Value.ToString())
        |> String.concat "\n"
        |> System.Exception
        |> raise

    printfn "Schema OK"

    let item = serializer.Deserialize<Item> json
    printfn "Name: %s, Count: %d" item.Name item.Count
    0

実行してみます。良さそうですね。

% dotnet run
Schema OK
Name: ペン, Count: 3

入力ファイルをいじってバリデーションを通らなくしてみます。下記は name を削除して実行した結果です。これも良さそうですね。

% dotnet run

Unhandled Exception: System.Exception: missing["name"]
   at Program.main(String[] argv) in /tmp/sample/Program.fs:line 30

落とし穴

上記の実行結果でめでたしめでたし、のように見えますよね。Json.Net に比べて知名度は高くないのにちゃんと動いた!やったー!といいたいところですが、では別の入力ファイルを用意してみましょう。

入力ファイル(item_escaped.json
名前と数が入っているオブジェクト。但し、名前は Unicode エスケープされている。

{
  "name": "\u30da\u30f3",
  "count": 3
}

実行してみるとエラー…カタカナの範囲は U+30A0 - U+30FF で文字に問題はなさそうなんですが…

% dotnet run schema.json item_escaped.json

Unhandled Exception: Manatee.Json.JsonSyntaxException: Invalid UTF-32 code point. Path: '/name'
   at Manatee.Json.Parsing.JsonParser.Parse(String source)
   at Program.main(String[] argv) in /tmp/sample/Program.fs:line 23

どうやら Unicode エスケープされた文字をアンエスケープできない場合があるようです。利用者が少ないのか、そもそも気にされていないのかは分かりませんが結構な痛手です。
では修正 PR を作るか?それとも Unicode エスケープの処理を自前で書くか?いいえ、エラーメッセージの通り JSON Schema 以前に JSON の扱いで問題が起きています。JSON を扱えるライブラリはいくつかありますよね?

プロダクトコードで Manatee.Json を使ってるところは他の JSON ライブラリを使って文字をアンエスケープして、その結果を使ってバリデーションをしています。
この記事を見て Manatee.Json を使いたいと思った人はその辺りも含めて、使うかどうかを検討してみてください。

まとめ

  • Manatee.Json いいよ
  • F# 初めて書いて楽しかった(Emacs の fsharp-mode がデフォルトで十分使い易かった)
  • 修正 PR 作りたいと思って早1年。とりあえず忘れないように記事化できて良かった