oracle.kv.avro

## Interface JsonAvroBinding

- All Superinterfaces:
    AvroBinding<JsonRecord>, ValueBinding<JsonRecord>

      public interface JsonAvroBinding
      extends AvroBinding<JsonRecord>

  The `JsonAvroBinding` interface has the same methods as `AvroBinding`, but represents values as instances of `JsonRecord`. A single schema binding is created using `AvroCatalog.getJsonBinding(org.apache.avro.Schema)`, and a multiple schema binding is created using `AvroCatalog.getJsonMultiBinding(java.util.Map<java.lang.String, org.apache.avro.Schema>)`.

  The trade-offs in using a `JsonAvroBinding`, compared to other types of bindings, are:

  - Probably the most important reason for using a JSON binding is for interoperability with other components or external systems that use JSON objects.

  - Like generic bindings, an advantage of using a JSON binding is that values may be treated generically. Also, if schemas are treated dynamically, then the set of schemas used in the application need not be fixed at build time.

  - Unlike `GenericRecord`, certain Avro data types are not represented conveniently using JSON syntax, namely:
    - Avro `int` and `long` are both represented as a JSON `integer`, and Avro `float` and `double` are both represented as a JSON `number`. WARNING: To avoid type conversion errors, consider defining integer fields in the Avro schema using type `long`, and floating point fields using type `double`.
    - Avro `bytes` and `fixed` are both represented as a JSON `string`, using Unicode escape syntax. WARNING: Characters greater than `0xFF` are invalid because they cannot be translated to bytes without loss of information. Also note that the Jackson `BinaryNode` class is not used with these Avro types.
    - Avro unions have a special JSON representation.
    Therefore, applications using JSON should limit the data types used in their Avro schemas, and should treat the above data types carefully.

    Also note the following type mappings:

    - Avro `record` and `map` are both represented as a JSON `object`.
    - An Avro `enum` is represented as a JSON `string`.

  - Like a `GenericRecord`, a JSON object does not provide type safety. It can also be error prone, because fields are accessed by string name.

  - To support class evolution, with JSON bindings (like generic bindings, but unlike specific bindings) the application must supply the Avro `Schema` objects at runtime. In other words, the application must maintain a set of known schemas for use at runtime.

  See `AvroCatalog` for general information on Avro bindings and schemas. The schemas used in the examples below are described in the `AvroCatalog` javadoc.

  When using a `JsonAvroBinding`, a `JsonRecord` is used to represent values. A `JsonRecord` represents an Avro object as an `JsonNode` in the Jackson API.

  As with a generic binding, a `JsonNode` represents an Avro object roughly as a map of string field names to field values. However, the Jackson API is very rich and fully supports the JSON format. For those familiar with XML, the Jackson API is for JSON what the DOM API is for XML. JSON text may also be easily parsed and formatted using the Jackson API.

  The following code fragment demonstrates writing and reading a value using a JSON single schema binding.

       Schema.Parser parser = new Schema.Parser();
       Schema nameSchema = parser.parse(nameSchemaText);
       Schema memberSchema = parser.parse(memberSchemaText);

       JsonAvroBinding binding = avroCatalog.getJsonBinding(memberSchema);

       // Create object
       ObjectNode member = JsonNodeFactory.instance.objectNode();
       JsonRecord object = new JsonRecord(member, memberSchema)
       ObjectNode name = member.putObject("name");
       name.put("first", ...);
       name.put("last", ...);
       member.put("age", ...);

       // Serialize and store
       Value value = binding.toValue(object);
       kvStore.put(key, value);

       // Sometime later, retrieve and deserialize
       ValueVersion vv = kvStore.get(key);
       JsonRecord object = binding.toObject(vv.getValue());

       // Use object
       ObjectNode member = (ObjectNode) object.getNode();
       ObjectNode name = (ObjectNode) member.get("name");
       int age = member.get("age").getIntValue();
       ...

  The following code fragment demonstrates reading values with different schemas using a JSON multiple schema binding.

       Schema.Parser parser = new Schema.Parser();
       Schema nameSchema = parser.parse(nameSchemaText);
       Schema memberSchema = parser.parse(memberSchemaText);
       Schema anotherSchema = parser.parse(anotherSchemaText);

       Map<String, Schema> schemas = new HashMap<String, Schema>()
       schemas.put(memberSchema.getFullName(), memberSchema);
       schemas.put(anotherSchema.getFullName(), anotherSchema);

       JsonAvroBinding binding = avroCatalog.getJsonMultiBinding(schemas);

       Iterator<KeyValueVersion> iter = kvStore.multiGetIterator(...);
       for (KeyValueVersion kvv : iter) {
           JsonRecord object = binding.toObject(kvv.getValue());
           JsonNode jsonNode = object.getJsonNode();
           String schemaName = object.getSchema().getFullName();
           if (schemaName.equals(memberSchema.getFullName())) {
               ...
           } else if (schemaName.equals(anotherSchema.getFullName())) {
               ...
           } else {
               ...
           }
       }

  A special use case for a JSON multiple schema binding is when the application treats values dynamically based on their schema, rather than using a fixed set of known schemas. The `AvroCatalog.getCurrentSchemas()` method can be used to obtain a map of the most current schemas, which can be passed to `AvroCatalog.getJsonMultiBinding(java.util.Map<java.lang.String, org.apache.avro.Schema>)`.

  For example, the following code fragment demonstrates reading values with different schemas using a JSON multiple schema binding. Note that in a long running application, it is possible that a schema may be added and used to store a key-value pair, after the binding has been created. The application may handle this possibility by catching `SchemaNotAllowedException`.

       JsonAvroBinding binding =
           avroCatalog.getJsonMultiBinding(avroCatalog.getCurrentSchemas());

       Iterator<KeyValueVersion> iter = kvStore.storeIterator(...);
       for (KeyValueVersion kvv : iter) {
           JsonRecord object;
           try {
               object = binding.toObject(kvv.getValue());
           } catch (SchemaNotAllowedException e) {
               // In this example, ignore values with a schema that was not
               // known at the time the binding was created.
               continue;
           }
           JsonNode jsonNode = object.getJsonNode();
           String schemaName = object.getSchema().getFullName();
           if (schemaName.equals(memberSchema.getFullName())) {
               ...
           } else if (schemaName.equals(anotherSchema.getFullName())) {
               ...
           } else {
               ...
           }
       }

  Since:
    2.0

- - ### Method Summary

    Methods
    Modifier and Type | Method and Description
    --- | ---
    `JsonRecord` | `toObject(Value value)` After doing a read operation using a `KVStore` method, the user calls `toObject` with the `Value` obtained from the read operation.
    `Value` | `toValue(JsonRecord object)` Before doing a write operation, the user calls `toValue` passing an object she wishes to store.

- - ### Method Detail

    - #### toObject

          JsonRecord toObject(Value value)
                              throws SchemaNotAllowedException,
                                     IllegalArgumentException

      After doing a read operation using a `KVStore` method, the user calls `toObject` with the `Value` obtained from the read operation.

      If necessary, this method automatically performs schema evolution, as described in `AvroCatalog`. In the context of schema evolution, the writer schema is the one associated internally with the `value` parameter (this association was normally made earlier when the value was stored), and the reader schema is the one associated with this binding (and was specified when the binding was created).

      In other words, this method transforms the serialized data in the `value` parameter to conform to the schema of the `JsonRecord` that is returned.

      Specified by:
        `toObject` in interface `AvroBinding<JsonRecord>`
      Specified by:
        `toObject` in interface `ValueBinding<JsonRecord>`
      Parameters:
        `value` - the `Value` obtained from a `KVStore` read operation method.. The byte array of the `Value` is serialized Avro data, packaged in an internal format that includes a reference to the Avro schema
      Returns:
        the deserialized `JsonRecord` instance. The `JsonRecord.getSchema()` method will return the reader schema, which is the schema that was specified when this binding was created.
      Throws:
        `SchemaNotAllowedException` - if the schema associated with the `value` parameter is not allowed with this binding.
        `IllegalArgumentException` - if the value format is not `Value.Format.AVRO`, the schema identifier embedded in the `value` parameter is invalid, or the serialized data cannot be parsed.

    - #### toValue

          Value toValue(JsonRecord object)
                        throws SchemaNotAllowedException,
                               UndefinedSchemaException,
                               IllegalArgumentException

      Before doing a write operation, the user calls `toValue` passing an object she wishes to store. The resulting `Value` is then passed to the write operation method in `KVStore`.

      In the context of schema evolution, as described in `AvroCatalog`, the returned value is serialized according to the writer schema. The writer schema is the one associated with the `JsonRecord` `object` parameter; it is returned by `JsonRecord.getSchema()` and specified when creating a `JsonRecord` object. The writer schema must be one of the schemas specified when this binding was created.

      In other words, this method returns serialized data that conforms to the schema of the given `JsonRecord`.

      Specified by:
        `toValue` in interface `AvroBinding<JsonRecord>`
      Specified by:
        `toValue` in interface `ValueBinding<JsonRecord>`
      Parameters:
        `object` - the `JsonRecord` instance the user wishes to store, or at least serialize.
      Returns:
        the serialized object.. The byte array of the `Value` is serialized Avro data, packaged in an internal format that includes a reference to the Avro schema
      Throws:
        `SchemaNotAllowedException` - if the schema associated with the `object` parameter is not allowed with this binding.
        `UndefinedSchemaException` - if the schema associated with the `object` parameter has not been defined using the NoSQL Database administration interface. Note that when the allowed schemas for a binding are specified (and validate) at the time the binding is created, this exception is extremely unlikely and is only possible if a schema is mistakenly disabled after the binding is created.
        `IllegalArgumentException` - if the `object` parameter is invalid according to its schema, and cannot be serialized.

Copyright (c) 2011, 2014 Oracle and/or its affiliates. All rights reserved.