JSONバインディング

AvroのJSONバインディングの使用
JSONレコードでのJSONバインディングの使用

JsonAvroBindingインタフェースでは、値をJsonRecordのインスタンスとして表します。1つのスキーマのバインディングはAvroCatalog.getJsonBinding()を使用して作成します。複数のスキーマのバインディングはAvroCatalog.getJsonMultiBinding()を使用して作成します。

JSONバインディングを使用する最も重要な理由は、JSONオブジェクトを使用する外部システムや他のコンポーネントとの相互運用性です。つまり、JSONバインディングによってJSONオブジェクトが公開され、それらをJackson API (JSONレコードの管理に使用される一般的なAPI)で管理できるからです。

汎用バインディングと同様に、JSONバインディングでは値を汎用的に扱います。また、これも汎用バインディングと同様に、スキーマを動的に扱う場合には、アプリケーションで使用するスキーマをビルド時に固定する必要はありません。

ただし、GenericRecordとは異なり、一部のAvroデータ型をJSON構文で表すときは注意が必要です。

このため、JSONバインディングを使用するアプリケーションは、Avroスキーマで使用されるデータ型を制限して、前述のデータ型の扱いには注意する必要があります。

GenericRecordと同様に、JSONオブジェクトは型の安全性を備えていません。フィールドに文字列名でアクセスするため、データ型をコンパイル時にチェックできないので、エラーが発生しやすくなります。

AvroのJSONバインディングの使用

生成されたAvro固有のクラスにカプセル化されたスキーマを使用するには、まずストアにスキーマを格納します(「ストアのAvroスキーマの管理」で説明)。

Oracle NoSQL Databaseクライアント・コードで、コードからそのスキーマを使用できるようにする必要があります。これを行うには、スキーマの作成先のファイルからそのスキーマを直接読み取ります。たとえば、my-schema.avscという名前のファイルに次のスキーマが定義されているとします。

{
    "type": "record",
    "name": "MyInfo",
    "namespace": "avro",
    "fields": [
        {"name": "ID", "type": "int"}
    ]
} 

そのスキーマをクライアント・コードに読み取るには、次にようにします。

package avro;

import java.io.File;
import org.apache.avro.Schema;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.node.JsonNodeFactory;
import org.codehaus.jackson.node.ObjectNode;

import oracle.kv.avro.AvroCatalog;
import oracle.kv.avro.JsonAvroBinding;
import oracle.kv.avro.JsonRecord;

...

final Schema.Parser parser = new Schema.Parser();
parser.parse(new File("my-schema.avsc")); 
final Schema myInfoSchema = 
    parser.getTypes().get("avro.MyInfo"); 

Avroデータ形式を使用する値のシリアライズとデシリアライズを開始するには、事前にJSONバインディングを作成してから、そのバインディングに対してAvroレコードを作成します。

/**
 * For the sake of brevity, we skip the necessary steps of 
 * declaring and opening the store handle.
 */
final AvroCatalog catalog = store.getAvroCatalog();
final JsonAvroBinding binding = 
    catalog.getJsonBinding(myInfoSchema);

バインディングが作成されると、アプリケーションには、スキーマのフィールドを読み書きできるような表現方法が必要になります。それには、JSONレコードを作成し、そこからオブジェクト・ノード(スキーマのフィールドの読取りと書込みを可能にするデータ構造)を作成します。

たとえば、ストアの読取りを実行した後で、Oracle NoSQL Databaseレコードに格納されている情報を調べようとしていると仮定します。

/**
  * Assume a store read was performed here, and resulted in a 
  * ValueVersion instance called 'vv'. Then, to deserialize
  * the value in the returned record:
  */
final JsonRecord record = binding.toObject(vv.getValue());
final ObjectNode member = (ObjectNode) record.getJsonNode();
/* Retrieve the contents of the ID field. Because we are 
 * using a generic binding, we must type cast the retrieved
 * value.
 */
final int ID = member.get("ID").getIntValue(); 

フィールドに書き込む(つまり、一部のデータをシリアライズする)場合は、メンバーのput()メソッドを使用します。例として、まったく新しいオブジェクトを作成し、ストアに書き込むとします。その場合は、次のようにします。

final ObjectNode member2 = JsonNodeFactory.instance.objectNode();
member2.put("ID", 100011);
final JsonRecord record2 = new JsonRecord(member2, myInfoSchema);
...
/**
  * Assume the creation of a store key here.
 */
...
store.put(key, binding.toValue(record2)); 

JSONレコードでのJSONバインディングの使用

JSONバインディングの最も一般的な用途は、JSONですでにマークアップされている情報の格納です。よくある例は、WebサーバーからJSON形式でデータを収集してから、それをストアに格納する場合です。

Oracle NoSQL DatabaseのAvro実装は、Jackson API (JSONレコードの解析に使用する一般的なAPI)に依存するため、JSONレコードをストアに格納するために必要なものはすべて用意されています。

次のようなJSONレコードがあるとします。

{
    "name": {
        "first": "Percival",
        "last":  "Lowell"
    },
    "age": 156,
    "address": {
        "street": "Mars Hill Rd",
        "city": "Flagstaff",
        "state": "AZ",
        "zip": 86001
    }
} 

このようなレコードをサポートするには、次のスキーマ定義が必要です。

{
    "type": "record",
    "name": "MemberInfo",
    "namespace": "avro",
    "fields": [
        {"name": "name", "type": {
            "type": "record",
            "name": "FullName",
            "fields": [
                {"name": "first", "type": "string"},
                {"name": "last", "type": "string"}
            ]
        }},
        {"name": "age", "type": "int"},
        {"name": "address", "type": {
            "type": "record",
            "name": "Address",
            "fields": [
                {"name": "street", "type": "string"},
                {"name": "city", "type": "string"},
                {"name": "state", "type": "string"},
                {"name": "zip", "type": "int"}
            ]
        }}
    ]
} 

このスキーマをMemberInfo.avscというファイルに追加したと仮定して、CLI (ddl add-schemaコマンドを使用)でストアにそのスキーマを追加してから、前述の例で説明した同じ方法でJSONバインディングを作成します。

(この例では、ObjectMapperというクラスを追加する必要があります。また、ディスクからのJSONレコードの読取りをサポートするには、java.io.BufferedReaderjava.io.FileReaderを使用しますが、本番環境のクライアント・コードでこれを行うことはほとんどないでしょう。)

package avro;

import java.io.BufferedReader;
import java.io.File;
import org.apache.avro.Schema;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.node.JsonNodeFactory;
import org.codehaus.jackson.node.ObjectNode;

import oracle.kv.avro.AvroCatalog;
import oracle.kv.avro.JsonAvroBinding;
import oracle.kv.avro.JsonRecord;

import oracle.kv.Key;
import oracle.kv.Value;

...

final Schema.Parser parser = new Schema.Parser();
parser.parse(new File("MemberInfo.avsc")); 
final Schema memberInfoSchema = 
    parser.getTypes().get("avro.MemberInfo"); 

...

/**
 * For the sake of brevity, we skip the necessary steps of 
 * declaring and opening the store handle.
 */

...

final AvroCatalog catalog = store.getAvroCatalog();
final JsonAvroBinding binding = 
    catalog.getJsonBinding(memberInfoSchema);

次の手順では、JSONレコードをアプリケーションに読み込みます。通常、これはネットワーク経由で実行されますが、簡略化のため、ここではディスク上のファイルからレコードを読み取ります。

try {
    final String jsonText = 
        readFile(new File("MemberInfoRecord.json"));

次に、Jackson APIを使用してテキストを解析し、JSONオブジェクトを作成します。

    final ObjectMapper jsonMapper = new ObjectMapper();

    final JsonNode jsonObject = jsonMapper.readTree(jsonText); 

最後に、JSONオブジェクトをJSONレコードに書き込み、それを使用してOracle NoSQL DatabaseのValueを作成します。

    final JsonRecord jsonRecord = 
        new JsonRecord(jsonObject, memberInfoSchema);

    final Value value = binding.toValue(jsonRecord);

このオブジェクトを通常の方法でストアに書き込めば終了です。

    store.put(Key.fromString("/any/old/key"), value);
} catch (IOException io) {
    io.printStackTrace();
} 

この例で使用したreadFile()メソッドのコードを最後に示します。

private String readFile(File f) throws IOException {
    final BufferedReader r = new BufferedReader(new FileReader(f));
    final StringBuilder buf = new StringBuilder(1000);
    String line;
    while ((line = r.readLine()) != null) {
        buf.append(line);
        buf.append("\n");
    }
    return buf.toString();
}