AvroJsoniter

dev.constructive.eo.avro.jsoniter.AvroJsoniter
object AvroJsoniter

Structural Avro ↔ JSON-'''bytes''' bridge: move between Avro generic runtime values / payload bytes and UTF-8 JSON byte arrays through jsoniter-scala's streaming JsonWriter / JsonReader, '''without''' a typed case class — and without a JSON AST — in the middle. The AST-free sibling of dev.constructive.eo.avro.circe.AvroJson: same walks, same conventions both ways, but the JSON side is Array[Byte] instead of io.circe.Json, so circe never touches the classpath. Lives inside cats-eo-avro with jsoniter-scala-core as an Optional dependency (the rendering runs through its JsonWriter) — add com.github.plokhotnyuk.jsoniter-scala:jsoniter-scala-core to use this package.

==Rendering conventions (record → JSON bytes)==

avroToJson mirrors AvroJson.avroToJson case for case, so the two bridges render parse-equivalent documents for the same record:

  • IndexedRecord → object (field name from getSchema.getFields, recurse on value, schema declaration order);
  • java.util.Map → object (stringify keys, recurse on values, entry iteration order);
  • java.util.List → array (recurse);
  • CharSequence (incl. org.apache.avro.util.Utf8) → JSON string;
  • Integer / Long → JSON number; Float / Double → JSON number via jsoniter's shortest-round-trip rendering, non-finite values → null (matching circe's fromFloatOrNull / fromDoubleOrNull);
  • Boolean → JSON boolean; a resolved null union branch → null;
  • GenericEnumSymbol → JSON string;
  • ByteBuffer / GenericFixed → array of signed byte ints (circe's Encoder[Array[Byte]] convention, same as AvroJson);
  • any other runtime type → JSON string of value.toString — a lenient last resort (the walk is total), not a convention to rely on.

Unions are resolved at the value level (the runtime value IS the branch), so dispatch on the runtime type needs no union special-casing.

==Codec diagonals==

The typed prism family mirrors AvroJson's, with JsoniterBytes in the Json slot: the fundamental diagonal valuePrism tears a generic runtime value into a typed A and mends any generic value out as JSON bytes; bytesPrism and recordPrism pre-compose its input slots via tearFrom / mendFrom. The AvroBytes / JsoniterBytes aliases (see the package object) keep the two Array[Byte] roles apart in the signatures.

==Drilled cursors (.json and .avro faces)==

The full AvroPrism cursor sugar — .field(_.x) / .fields(...) / .at(i) / .union[B] / .each / Dynamic selection — reaches this bridge through the json extensions on AvroPrism / AvroTraversal (drill first, flip last, like .record): reads yield the typed focus, writes render the whole modified document as JSON bytes. render is the focus-as-standalone-JSON terminal for the read side.

The reverse cursor is the avro extension on JsoniterPrism: drill a JSON document with the jsoniter sugar and the '''drilled focus itself''' converts — reads structurally parse the focused slice under the focus codec's schema and yield its Avro binary encoding, writes accept Avro binary, render it structurally, and splice the JSON slice back. Both directions are schema-directed walks; no typed value is ever materialised.

==Parsing conventions (JSON bytes → record)==

record's getOption (and the .avro face's read) is the '''strict''' schema-directed inverse — a streaming JsonReader walk, no AST — mirroring AvroJson's parsing conventions: a record object's key set must equal the schema's field names exactly; enum requires a schema symbol; fixed the exact byte length; bytes / fixed the signed-byte-int array rendering; a union is matched '''first branch that parses wins''' (the branch slice is captured raw once and re-attempted per branch — a streaming reader cannot backtrack). Two deliberate strictness-only divergences from the AST parse: numeric syntax is token-level (1.0 misses an int schema where circe's toInt accepts it), and a duplicate object key misses (circe's JsonObject silently de-duplicates before AvroJson ever sees it).

==Non-goals (deliberate)==

Same as AvroJson: the bridge sees only the '''runtime''' Avro value, never the logical type — an Instant stored as timestamp-millis renders as a JSON number, not an ISO-8601 string.

Attributes

Source
AvroJsoniter.scala
Graph
Supertypes
class Object
trait Matchable
class Any
Self type

Members list

Grouped members

Bidirectional prism (JSON bytes ↔ record)

def jsonToRecord(json: JsoniterBytes, schema: Schema): Option[IndexedRecord]

Strict schema-directed structural parse of a JSON document into a generic record — record's getOption. None on anything the schema does not pin; no typed case class and no AST in the middle.

Strict schema-directed structural parse of a JSON document into a generic record — record's getOption. None on anything the schema does not pin; no typed case class and no AST in the middle.

Attributes

Source
AvroJsoniter.scala
def record(schema: Schema): MendTearPrism[JsoniterBytes, JsoniterBytes, IndexedRecord, IndexedRecord]

The bidirectional bridge — AvroJson.record without the AST: a Prism[JsoniterBytes, IndexedRecord] for schema. Reading (getOption / to) is the strict schema-directed streaming parse — see ''Parsing conventions''; it misses (Left of the untouched JSON bytes) on anything the schema does not pin. Writing (reverseGet) is the total structural walk avroToJson.

The bidirectional bridge — AvroJson.record without the AST: a Prism[JsoniterBytes, IndexedRecord] for schema. Reading (getOption / to) is the strict schema-directed streaming parse — see ''Parsing conventions''; it misses (Left of the untouched JSON bytes) on anything the schema does not pin. Writing (reverseGet) is the total structural walk avroToJson.

Attributes

Source
AvroJsoniter.scala

Codec diagonals (tearFrom / mendFrom of valuePrism)

def bytesPrism[A](using codec: AvroCodec[A]): MendTearPrism[AvroBytes, JsoniterBytes, A, A]

Typed-both-ways byte diagonal — tear Avro '''payload bytes''' (AvroBytes) into a typed A, mend A out as JSON bytes through the codec's encode, so modify(f: A => A): AvroBytes => JsoniterBytes works in one hop. The counterpart of AvroJson.bytesPrism; same parse contract as bytesToJson (position-based under codec.schema, no writer/reader resolution).

Typed-both-ways byte diagonal — tear Avro '''payload bytes''' (AvroBytes) into a typed A, mend A out as JSON bytes through the codec's encode, so modify(f: A => A): AvroBytes => JsoniterBytes works in one hop. The counterpart of AvroJson.bytesPrism; same parse contract as bytesToJson (position-based under codec.schema, no writer/reader resolution).

Attributes

Source
AvroJsoniter.scala
def recordPrism[A](using AvroCodec[A]): MendTearPrism[IndexedRecord, JsoniterBytes, A, IndexedRecord]

Record-sourced diagonal — for streams already resolved to generic records (e.g. the output of ConfluentWire.recordReader): tear an IndexedRecord into a typed A, mend an IndexedRecord out as JSON bytes.

Record-sourced diagonal — for streams already resolved to generic records (e.g. the output of ConfluentWire.recordReader): tear an IndexedRecord into a typed A, mend an IndexedRecord out as JSON bytes.

Attributes

Source
AvroJsoniter.scala
def valuePrism[A](using codec: AvroCodec[A]): MendTearPrism[Any, JsoniterBytes, A, Any]

The fundamental codec diagonal — the prisms below are this one with their '''input''' slots pre-composed via MendTearPrism.tearFrom / mendFrom. Tears an Avro '''generic runtime value''' into a typed A via the codec's decode; a miss surrenders the '''structural JSON-bytes view''' of the value (the avroToJson walk generalised to any value) instead of the raw input, so a payload that is valid Avro but not a valid A still lands somewhere inspectable. The mend renders any generic value back as JSON bytes — the same structural walk. AvroJson.valuePrism with JsoniterBytes in the Json slot.

The fundamental codec diagonal — the prisms below are this one with their '''input''' slots pre-composed via MendTearPrism.tearFrom / mendFrom. Tears an Avro '''generic runtime value''' into a typed A via the codec's decode; a miss surrenders the '''structural JSON-bytes view''' of the value (the avroToJson walk generalised to any value) instead of the raw input, so a payload that is valid Avro but not a valid A still lands somewhere inspectable. The mend renders any generic value back as JSON bytes — the same structural walk. AvroJson.valuePrism with JsoniterBytes in the Json slot.

Attributes

Source
AvroJsoniter.scala

Structural walk

def avroToJson(record: IndexedRecord): JsoniterBytes

The whole substance of the bridge: the recursive structural walk of an Avro generic record, rendered to UTF-8 JSON bytes via jsoniter's JsonWriter. Allocates no typed case class and no AST; field order is the schema's field declaration order.

The whole substance of the bridge: the recursive structural walk of an Avro generic record, rendered to UTF-8 JSON bytes via jsoniter's JsonWriter. Allocates no typed case class and no AST; field order is the schema's field declaration order.

Attributes

Source
AvroJsoniter.scala

Read optic (Avro bytes → JSON bytes)

def bytesToJson(schema: Schema): Getter[AvroBytes, JsoniterBytes]

The read optic: avroToJson composed onto a bytes → record read dev.constructive.eo.optics.Getter — a total Getter[Array[Byte], Array[Byte]] from Avro payload bytes to JSON document bytes. The AST-free counterpart of AvroJson.bytesToJson.

The read optic: avroToJson composed onto a bytes → record read dev.constructive.eo.optics.Getter — a total Getter[Array[Byte], Array[Byte]] from Avro payload bytes to JSON document bytes. The AST-free counterpart of AvroJson.bytesToJson.

The schema must be the exact writer schema the bytes were encoded under: the parse is position-based and does no writer/reader resolution, so a mismatched schema silently misreads. For a mixed-schema stream, resolve writer → reader first (e.g. dev.constructive.eo.avro.ConfluentWire.recordReader) and walk the resolved record with avroToJson.

Attributes

Source
AvroJsoniter.scala
def render[A](using codec: AvroCodec[A]): Getter[A, JsoniterBytes]

Focus-as-JSON terminal: render a typed focus as a '''standalone JSON document''' through the codec's encode + the structural walk. Compose it after any drilled optic to read just the focused field as JSON bytes:

Focus-as-JSON terminal: render a typed focus as a '''standalone JSON document''' through the codec's encode + the structural walk. Compose it after any drilled optic to read just the focused field as JSON bytes:

 codecPrism[Person].field(_.address).andThen(AvroJsoniter.render[Address])
   .getOption(avroBytes)                       // Option[JsoniterBytes] of the address alone

For the whole-document face (drilled writes included) use the json extension instead.

Attributes

Source
AvroJsoniter.scala