JSON形式のデータがある時、そのデータがあらかじめ決められたルールを守って書かれているかどうかをチェックしたい場合があります。このような目的のためにJSONスキーマという規格が策定されています。
JSONスキーマとは
例として、次のようなJSON形式のデータを考えます。
{
"name": "山田太郎",
"age": 42,
"hobbies": ["野球", "柔道"]
}
JSONスキーマでは、バリデーションの対象となるこのようなJSON形式のデータをJSONインスタンスと呼びます。ここでは、「人」を表す上記のJSONインスタンスに対して、次のような制約を設けたいとします。
-
name
プロパティの値の型は文字列 -
age
プロパティの値の型は0以上の整数 -
hobbies
プロパティの値の型は配列で、その要素の型は文字列 -
name
プロパティは省略不可
JSONスキーマではこのような制約を次のように表現します。JSONスキーマ自体もJSON形式で記述します。
{
"type": "object",
"properties": {
"name": {
"type": "string"
},
"age": {
"type": "integer",
"minimum": 0
},
"hobbies": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": ["name"]
}
JSONスキーマの仕様はjson-schema-orgによって策定されています。本記事執筆時点での最新仕様はDraft-07です。
JSONをバリデーションしてみる
それでは実際にJSONインスタンスをJSONスキーマにしたがってバリデーションしてみましょう。以降のコードはJavaで記述します。
本記事では、JSONのバリデーションを行うライブラリとして、Justifyを使用します。このライブラリでは、JSONインスタンスの読み込みにJava API for JSON Processing (JSR 374)が提供するjavax.json
パッケージのインタフェースをそのまま使います。このため、APIを使う側はバリデーションしていることをあまり意識する必要がありません。
準備
バリデーションを行うJavaアプリケーションへ依存ファイルとして以下の2つを追加します。ビルドツールとしてMavenを使った場合の例です。
<!-- ライブラリ本体 -->
<dependency>
<groupId>org.leadpony.justify</groupId>
<artifactId>justify</artifactId>
<version>2.0.0</version>
</dependency>
<!-- Java API for JSON Processingの実装 -->
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.json</artifactId>
<classifier>module</classifier>
<version>1.1.6</version>
</dependency>
2つめの依存ファイルは、Java API for JSON Processing (JSR 374)の実装としてそのリファレンス実装を用いた場合の例です。Apache Johnzonなどの互換性のある別の実装に差し替えても問題ありません。
ストリーミングAPIでバリデーション
Java API for JSON Processing (JSR 374)には、ストリーミングAPIとオブジェクトモデルAPIの2種類のAPIがあります。最初にストリーミングAPIを使ってJSONインスタンスをバリデーションしてみましょう。
ストリーミングAPIでは、オブジェクトの始まり・プロパティの名前・プロパティの値…といったJSONの読み込み中に発生する細かなイベント単位で呼出し側に制御が戻ります。JSONインスタンスの読み込みにはjavax.json.stream.JsonParser
インタフェースを使用します。
import java.nio.file.Paths;
import javax.json.stream.JsonParser;
import org.leadpony.justify.api.JsonSchema;
import org.leadpony.justify.api.JsonValidationService;
import org.leadpony.justify.api.ProblemHandler;
// バリデーションサービスを作成する。
JsonValidationService service = JsonValidationService.newInstance();
// ファイルからJSONスキーマを読み込む。
JsonSchema schema = service.readSchema(Paths.get("person.schema.json"));
// バリデーション中に見つかった問題を処理するハンドラ。
// ここでは標準出力に出力するのみ。
ProblemHandler handler = service.createProblemPrinter(System.out::println);
// JSONインスタンスを読み込むパーサーを作成する。
try (JsonParser parser = service.createParser(Paths.get("person.json"), schema, handler)) {
// すべてのパーサーイベントを処理する
while (parser.hasNext()) {
// 発生したパーサーイベント
JsonParser.Event event = parser.next();
// イベントを処理する。
doSomethingUseful(event);
}
}
試しに、次のように不正なJSONインスタンスを入力してみます。
{
"age": -1,
"hobbies": ["野球", "柔道"]
}
バリデーション中に見つかった問題は次のように出力されます。
[2行,13列] 数値は0以上でなければいけません。
[4行,1列] オブジェクトはプロパティ"name"を持たなければいけません。
実行時のロケールが日本語の場合はエラーメッセージも日本語になります。
オブジェクトモデルAPIでバリデーション
続いてオブジェクトモデルAPIを使ったコード例を見てみましょう。
オブジェクトモデルAPIでは、JSONの読み込みによって、JSONのオブジェクトや配列全体がメモリ上に構築されます。JSONインスタンスの読み込みにはjavax.json.JsonReader
インタフェースを使用します。
import java.nio.file.Paths;
import javax.json.JsonObject;
import javax.json.JsonReader;
import org.leadpony.justify.api.JsonSchema;
import org.leadpony.justify.api.JsonValidationService;
import org.leadpony.justify.api.ProblemHandler;
// バリデーションサービスを作成する。
JsonValidationService service = JsonValidationService.newInstance();
// ファイルからJSONスキーマを読み込む。
JsonSchema schema = service.readSchema(Paths.get("person.schema.json"));
// バリデーション中に見つかった問題を処理するハンドラ。
// ここでは標準出力に出力するのみ。
ProblemHandler handler = service.createProblemPrinter(System.out::println);
// JSONインスタンスを読み込むリーダーを作成する。
try (JsonReader reader = service.createReader(Paths.get("person.json"), schema, handler)) {
// JSONオブジェクトを読み込む
JsonObject object = reader.readObject();
// 読み込んだJSONオブジェクトを処理する。
doSomethingUseful(object);
}
オブジェクトモデルAPIの場合でも、JSONインスタンスに問題が見つかった時は、ストリーミングAPIと同様なエラーメッセージが出力されます。
バインディングAPIでバリデーション
Javaには、JSONインスタンスをダイレクトにPOJO (Plain Old Java Object)へ変換するためのAPI仕様として、Java API for JSON Binding (JSR 367)があります。最後に、このAPIを使ってJSONインスタンスをバリデーションしてみましょう。
Java API for JSON Binding (JSR 367)の実装としてYassonを使用するため、これを依存ファイルとして追加します。
<!-- Java API for JSON Bindingの実装 -->
<dependency>
<groupId>org.eclipse</groupId>
<artifactId>yasson</artifactId>
<version>1.0.2</version>
</dependency>
次に、変換先のPOJOとしてPerson
クラスを定義します。
public class Person {
public String name;
public int age;
public List<String> hobbies;
}
バリデーションのコードは次のようになります。
import java.nio.file.Paths;
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import javax.json.spi.JsonProvider;
import org.leadpony.justify.api.JsonSchema;
import org.leadpony.justify.api.JsonValidationService;
// バリデーションサービスを作成する。
JsonValidationService service = JsonValidationService.newInstance();
// ファイルからJSONスキーマを読み込む。
JsonSchema schema = service.readSchema(Paths.get("person.schema.json"));
// JsonProviderのインスタンスを作成する。
JsonProvider provider = service.createJsonProvider(
schema,
parser->service.createProblemPrinter(System.out::println));
// Jsonbのインスタンスを作成する。
Jsonb jsonb = JsonbBuilder.newBuilder().withProvider(provider).build();
// JSONインスタンスを入力ストリームとして開く。
try (InputStream stream = Files.newInputStream(Paths.get("person.json"))) {
// JSONインスタンスをPersonオブジェクトに変換する。
Person person = jsonb.fromJson(stream, Person.class);
// Personオブジェクトを処理する。
doSomethingUseful(person);
}
これまで紹介したコードと同様に、入力したJSONインスタンスに問題が見つかった場合は、エラーメッセージが出力されます。
その他のバリデータ実装
JSONスキーマ仕様をベースにしたJSONのバリデータに関しては、Java以外のプログラミング言語用を含め、json-schema.orgのImplementationsに最新リストが提示されています。なお、本記事で紹介したJustifyは私自身が開発したものです。