pub trait DecoderFactory:
Debug
+ Send
+ Sync {
// Provided method
fn make_default_decoder(
&self,
_ctx: &DecoderContext,
_field: &FieldRef,
_is_nullable: bool,
) -> Result<Option<Box<dyn ArrayDecoder>>, ArrowError> { ... }
}Expand description
A trait to create custom decoders for specific data types.
Overrides the reader’s decoder for a data type, or adds support for one it does not
handle. The reader-side counterpart of EncoderFactory; register an
implementation with ReaderBuilder::with_decoder_factory.
§Examples
Decodes Binary from a JSON array of integers rather than the default hex string.
use std::sync::Arc;
use arrow_array::{Array, ArrayRef, BinaryArray};
use arrow_array::types::Float64Type;
use arrow_array::cast::AsArray;
use arrow_json::reader::{ArrayDecoder, DecoderContext, DecoderFactory, Tape, TapeElement};
use arrow_json::ReaderBuilder;
use arrow_schema::{ArrowError, DataType, Field, FieldRef, Fields, Schema};
use arrow_schema::extension::EXTENSION_TYPE_NAME_KEY;
use arrow_array::StringArray;
/// Decodes `[104, 105]` into the bytes `b"hi"`
struct IntArrayBinaryDecoder;
impl ArrayDecoder for IntArrayBinaryDecoder {
fn decode(&mut self, tape: &Tape<'_>, pos: &[u32]) -> Result<ArrayRef, ArrowError> {
let mut values: Vec<Option<Vec<u8>>> = Vec::with_capacity(pos.len());
for p in pos {
match tape.get(*p) {
TapeElement::Null => values.push(None),
TapeElement::StartList(end) => {
let mut bytes = Vec::new();
let mut cur = p + 1;
while cur < end {
match tape.get(cur) {
// JSON text yields `Number`; serde yields `I32`
TapeElement::Number(idx) => {
let s = tape.get_string(idx);
bytes.push(s.parse::<u8>().map_err(|e| {
ArrowError::JsonError(format!("invalid byte {s}: {e}"))
})?);
}
TapeElement::I32(v) => bytes.push(v as u8),
_ => return Err(tape.error(cur, "byte")),
}
cur = tape.next(cur, "byte")?;
}
values.push(Some(bytes));
}
_ => return Err(tape.error(*p, "list of bytes")),
}
}
Ok(Arc::new(BinaryArray::from_iter(values.iter().map(|v| v.as_deref()))))
}
}
/// Upper-cases whatever the reader's own decoder produced
struct ShoutDecoder(Box<dyn ArrayDecoder>);
impl ArrayDecoder for ShoutDecoder {
fn decode(&mut self, tape: &Tape<'_>, pos: &[u32]) -> Result<ArrayRef, ArrowError> {
let inner = self.0.decode(tape, pos)?;
let values = inner.as_string::<i32>();
Ok(Arc::new(StringArray::from_iter(
values.iter().map(|v| v.map(str::to_uppercase)),
)))
}
}
#[derive(Debug)]
struct IntArrayBinaryDecoderFactory;
impl DecoderFactory for IntArrayBinaryDecoderFactory {
fn make_default_decoder(
&self,
ctx: &DecoderContext,
field: &FieldRef,
is_nullable: bool,
) -> Result<Option<Box<dyn ArrayDecoder>>, ArrowError> {
// Selection can key off metadata, e.g. to recognise an extension type, and
// build on the reader's own decoder for the very same field
if field.metadata().get(EXTENSION_TYPE_NAME_KEY).map(String::as_str)
== Some("apache.shout")
{
let inner = ctx.make_builtin_decoder(field, is_nullable)?;
return Ok(Some(Box::new(ShoutDecoder(inner))));
}
match field.data_type() {
DataType::Binary => Ok(Some(Box::new(IntArrayBinaryDecoder))),
// Returning `None` uses the reader's default decoder
_ => Ok(None),
}
}
}
let nested = Fields::from(vec![Field::new("inner", DataType::Binary, true)]);
let schema = Arc::new(Schema::new(vec![
Field::new("bytes", DataType::Binary, true),
Field::new("float", DataType::Float64, true),
Field::new("nested", DataType::Struct(nested), true),
Field::new("shout", DataType::Utf8, true)
.with_metadata([(EXTENSION_TYPE_NAME_KEY, "apache.shout")]),
]));
let json = r#"{"bytes": [104, 105], "float": 1.0, "nested": {"inner": [104, 105]}, "shout": "hi"}
{"float": 2.3}
{"bytes": [98], "nested": {"inner": [98]}}
"#;
let batch = ReaderBuilder::new(schema)
.with_decoder_factory(Arc::new(IntArrayBinaryDecoderFactory))
.build(json.as_bytes())
.unwrap()
.next()
.unwrap()
.unwrap();
let bytes = batch.column(0).as_binary::<i32>();
assert_eq!(bytes.value(0), b"hi");
assert!(bytes.is_null(1));
assert_eq!(bytes.value(2), b"b");
// The override applies wherever `Binary` appears, including nested, while types
// the factory declines are decoded as usual
let inner = batch.column(2).as_struct().column(0).as_binary::<i32>();
assert_eq!(inner.value(0), b"hi");
assert_eq!(batch.column(1).as_primitive::<Float64Type>().value(0), 1.0);
// Dispatched on metadata, and decoded by the reader's own `Utf8` decoder
assert_eq!(batch.column(3).as_string::<i32>().value(0), "HI");Provided Methods§
Sourcefn make_default_decoder(
&self,
_ctx: &DecoderContext,
_field: &FieldRef,
_is_nullable: bool,
) -> Result<Option<Box<dyn ArrayDecoder>>, ArrowError>
fn make_default_decoder( &self, _ctx: &DecoderContext, _field: &FieldRef, _is_nullable: bool, ) -> Result<Option<Box<dyn ArrayDecoder>>, ArrowError>
Make a decoder for field, or Ok(None) to use the reader’s default.
Receives the FieldRef rather than just its DataType so decoder
selection can consider the field’s metadata, e.g. to identify extension
types. The root of a ReaderBuilder::new schema is presented as a
synthesized nameless Struct field.
Use DecoderContext::make_decoder on ctx to build child decoders, and
DecoderContext::make_builtin_decoder to build on the reader’s own decoder
for this field. Calling make_decoder with the field this was invoked with
recurses back here and loops.
is_nullable folds in ancestor nullability, so it may differ from
field.is_nullable() in either direction: a nullable struct widens its
children, while a run-end encoded array narrows its values.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".