This is an automated email from the ASF dual-hosted git repository.

tustvold pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git


The following commit(s) were added to refs/heads/master by this push:
     new 514641918a Raw JSON writer (~10x faster) (#5314)  (#5318)
514641918a is described below

commit 514641918a77c9d1f01c15cd2df6f66aa163f23d
Author: Raphael Taylor-Davies <[email protected]>
AuthorDate: Wed Jan 24 22:17:24 2024 +0000

    Raw JSON writer (~10x faster) (#5314)  (#5318)
    
    * Raw JSON writer (#5314)
    
    * Fix bench name
    
    * Review feedback
    
    * Review feedback
---
 arrow-json/src/writer.rs         |  96 +++++----
 arrow-json/src/writer/encoder.rs | 445 +++++++++++++++++++++++++++++++++++++++
 arrow-json/test/data/basic.json  |   4 +-
 arrow/benches/json_writer.rs     |   2 +-
 4 files changed, 502 insertions(+), 45 deletions(-)

diff --git a/arrow-json/src/writer.rs b/arrow-json/src/writer.rs
index cabda5e2dc..dd77328cb7 100644
--- a/arrow-json/src/writer.rs
+++ b/arrow-json/src/writer.rs
@@ -20,28 +20,6 @@
 //! This JSON writer converts Arrow [`RecordBatch`]es into arrays of
 //! JSON objects or JSON formatted byte streams.
 //!
-//! ## Writing JSON Objects
-//!
-//! To serialize [`RecordBatch`]es into array of
-//! [JSON](https://docs.serde.rs/serde_json/) objects, use
-//! [`record_batches_to_json_rows`]:
-//!
-//! ```
-//! # use std::sync::Arc;
-//! # use arrow_array::{Int32Array, RecordBatch};
-//! # use arrow_schema::{DataType, Field, Schema};
-//!
-//! let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
-//! let a = Int32Array::from(vec![1, 2, 3]);
-//! let batch = RecordBatch::try_new(Arc::new(schema), 
vec![Arc::new(a)]).unwrap();
-//!
-//! let json_rows = 
arrow_json::writer::record_batches_to_json_rows(&[&batch]).unwrap();
-//! assert_eq!(
-//!     serde_json::Value::Object(json_rows[1].clone()),
-//!     serde_json::json!({"a": 2}),
-//! );
-//! ```
-//!
 //! ## Writing JSON formatted byte streams
 //!
 //! To serialize [`RecordBatch`]es into line-delimited JSON bytes, use
@@ -97,6 +75,8 @@
 //! In order to explicitly write null values for keys, configure a custom 
[`Writer`] by
 //! using a [`WriterBuilder`] to construct a [`Writer`].
 
+mod encoder;
+
 use std::iter;
 use std::{fmt::Debug, io::Write};
 
@@ -109,7 +89,9 @@ use arrow_array::types::*;
 use arrow_array::*;
 use arrow_schema::*;
 
+use crate::writer::encoder::EncoderOptions;
 use arrow_cast::display::{ArrayFormatter, FormatOptions};
+use encoder::make_encoder;
 
 fn primitive_array_to_json<T>(array: &dyn Array) -> Result<Vec<Value>, 
ArrowError>
 where
@@ -481,6 +463,7 @@ fn set_column_for_json_rows(
 
 /// Converts an arrow [`RecordBatch`] into a `Vec` of Serde JSON
 /// [`JsonMap`]s (objects)
+#[deprecated(note = "Use Writer")]
 pub fn record_batches_to_json_rows(
     batches: &[&RecordBatch],
 ) -> Result<Vec<JsonMap<String, Value>>, ArrowError> {
@@ -597,11 +580,7 @@ pub type ArrayWriter<W> = Writer<W, JsonArray>;
 
 /// JSON writer builder.
 #[derive(Debug, Clone, Default)]
-pub struct WriterBuilder {
-    /// Controls whether null values should be written explicitly for keys
-    /// in objects, or whether the key should be omitted entirely.
-    explicit_nulls: bool,
-}
+pub struct WriterBuilder(EncoderOptions);
 
 impl WriterBuilder {
     /// Create a new builder for configuring JSON writing options.
@@ -629,7 +608,7 @@ impl WriterBuilder {
 
     /// Returns `true` if this writer is configured to keep keys with null 
values.
     pub fn explicit_nulls(&self) -> bool {
-        self.explicit_nulls
+        self.0.explicit_nulls
     }
 
     /// Set whether to keep keys with null values, or to omit writing them.
@@ -654,7 +633,7 @@ impl WriterBuilder {
     ///
     /// Default is to skip nulls (set to `false`).
     pub fn with_explicit_nulls(mut self, explicit_nulls: bool) -> Self {
-        self.explicit_nulls = explicit_nulls;
+        self.0.explicit_nulls = explicit_nulls;
         self
     }
 
@@ -669,7 +648,7 @@ impl WriterBuilder {
             started: false,
             finished: false,
             format: F::default(),
-            explicit_nulls: self.explicit_nulls,
+            options: self.0,
         }
     }
 }
@@ -702,8 +681,8 @@ where
     /// Determines how the byte stream is formatted
     format: F,
 
-    /// Whether keys with null values should be written or skipped
-    explicit_nulls: bool,
+    /// Controls how JSON should be encoded, e.g. whether to write explicit 
nulls or skip them
+    options: EncoderOptions,
 }
 
 impl<W, F> Writer<W, F>
@@ -718,11 +697,12 @@ where
             started: false,
             finished: false,
             format: F::default(),
-            explicit_nulls: false,
+            options: EncoderOptions::default(),
         }
     }
 
     /// Write a single JSON row to the output writer
+    #[deprecated(note = "Use Writer::write")]
     pub fn write_row(&mut self, row: &Value) -> Result<(), ArrowError> {
         let is_first_row = !self.started;
         if !self.started {
@@ -738,18 +718,48 @@ where
         Ok(())
     }
 
-    /// Convert the `RecordBatch` into JSON rows, and write them to the output
+    /// Serialize `batch` to JSON output
     pub fn write(&mut self, batch: &RecordBatch) -> Result<(), ArrowError> {
-        for row in record_batches_to_json_rows_internal(&[batch], 
self.explicit_nulls)? {
-            self.write_row(&Value::Object(row))?;
+        if batch.num_rows() == 0 {
+            return Ok(());
         }
+
+        // BufWriter uses a buffer size of 8KB
+        // We therefore double this and flush once we have more than 8KB
+        let mut buffer = Vec::with_capacity(16 * 1024);
+
+        let mut is_first_row = !self.started;
+        if !self.started {
+            self.format.start_stream(&mut buffer)?;
+            self.started = true;
+        }
+
+        let array = StructArray::from(batch.clone());
+        let mut encoder = make_encoder(&array, &self.options)?;
+
+        for idx in 0..batch.num_rows() {
+            self.format.start_row(&mut buffer, is_first_row)?;
+            is_first_row = false;
+
+            encoder.encode(idx, &mut buffer);
+            if buffer.len() > 8 * 1024 {
+                self.writer.write_all(&buffer)?;
+                buffer.clear();
+            }
+            self.format.end_row(&mut buffer)?;
+        }
+
+        if !buffer.is_empty() {
+            self.writer.write_all(&buffer)?;
+        }
+
         Ok(())
     }
 
-    /// Convert the [`RecordBatch`] into JSON rows, and write them to the 
output
+    /// Serialize `batches` to JSON output
     pub fn write_batches(&mut self, batches: &[&RecordBatch]) -> Result<(), 
ArrowError> {
-        for row in record_batches_to_json_rows_internal(batches, 
self.explicit_nulls)? {
-            self.write_row(&Value::Object(row))?;
+        for b in batches {
+            self.write(b)?;
         }
         Ok(())
     }
@@ -1453,6 +1463,7 @@ mod tests {
     }
 
     #[test]
+    #[allow(deprecated)]
     fn json_writer_one_row() {
         let mut writer = ArrayWriter::new(vec![] as Vec<u8>);
         let v = json!({ "an": "object" });
@@ -1465,6 +1476,7 @@ mod tests {
     }
 
     #[test]
+    #[allow(deprecated)]
     fn json_writer_two_rows() {
         let mut writer = ArrayWriter::new(vec![] as Vec<u8>);
         let v = json!({ "an": "object" });
@@ -1564,9 +1576,9 @@ mod tests {
             r#"{"a":{"list":[1,2]},"b":{"list":[1,2]}}
 {"a":{"list":[null]},"b":{"list":[null]}}
 {"a":{"list":[]},"b":{"list":[]}}
-{"a":null,"b":{"list":[3,null]}}
+{"b":{"list":[3,null]}}
 {"a":{"list":[4,5]},"b":{"list":[4,5]}}
-{"a":null,"b":{}}
+{"b":{}}
 {"a":{},"b":{}}
 "#,
         );
@@ -1621,7 +1633,7 @@ mod tests {
         assert_json_eq(
             &buf,
             r#"{"map":{"foo":10}}
-{"map":null}
+{}
 {"map":{}}
 {"map":{"bar":20,"baz":30,"qux":40}}
 {"map":{"quux":50}}
diff --git a/arrow-json/src/writer/encoder.rs b/arrow-json/src/writer/encoder.rs
new file mode 100644
index 0000000000..87efcb9f39
--- /dev/null
+++ b/arrow-json/src/writer/encoder.rs
@@ -0,0 +1,445 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use arrow_array::cast::AsArray;
+use arrow_array::types::*;
+use arrow_array::*;
+use arrow_buffer::{ArrowNativeType, NullBuffer, OffsetBuffer, ScalarBuffer};
+use arrow_cast::display::{ArrayFormatter, FormatOptions};
+use arrow_schema::{ArrowError, DataType, FieldRef};
+use half::f16;
+use lexical_core::FormattedSize;
+use serde::Serializer;
+use std::io::Write;
+
+#[derive(Debug, Clone, Default)]
+pub struct EncoderOptions {
+    pub explicit_nulls: bool,
+}
+
+/// A trait to format array values as JSON values
+///
+/// Nullability is handled by the caller to allow encoding nulls implicitly, 
i.e. `{}` instead of `{"a": null}`
+pub trait Encoder {
+    /// Encode the non-null value at index `idx` to `out`
+    ///
+    /// The behaviour is unspecified if `idx` corresponds to a null index
+    fn encode(&mut self, idx: usize, out: &mut Vec<u8>);
+}
+
+pub fn make_encoder<'a>(
+    array: &'a dyn Array,
+    options: &EncoderOptions,
+) -> Result<Box<dyn Encoder + 'a>, ArrowError> {
+    let (encoder, nulls) = make_encoder_impl(array, options)?;
+    assert!(nulls.is_none(), "root cannot be nullable");
+    Ok(encoder)
+}
+
+fn make_encoder_impl<'a>(
+    array: &'a dyn Array,
+    options: &EncoderOptions,
+) -> Result<(Box<dyn Encoder + 'a>, Option<NullBuffer>), ArrowError> {
+    macro_rules! primitive_helper {
+        ($t:ty) => {{
+            let array = array.as_primitive::<$t>();
+            let nulls = array.nulls().cloned();
+            (Box::new(PrimitiveEncoder::new(array)) as _, nulls)
+        }};
+    }
+
+    Ok(downcast_integer! {
+        array.data_type() => (primitive_helper),
+        DataType::Float16 => primitive_helper!(Float16Type),
+        DataType::Float32 => primitive_helper!(Float32Type),
+        DataType::Float64 => primitive_helper!(Float64Type),
+        DataType::Boolean => {
+            let array = array.as_boolean();
+            (Box::new(BooleanEncoder(array.clone())), array.nulls().cloned())
+        }
+        DataType::Null => (Box::new(NullEncoder), array.logical_nulls()),
+        DataType::Utf8 => {
+            let array = array.as_string::<i32>();
+            (Box::new(StringEncoder(array.clone())) as _, 
array.nulls().cloned())
+        }
+        DataType::LargeUtf8 => {
+            let array = array.as_string::<i64>();
+            (Box::new(StringEncoder(array.clone())) as _, 
array.nulls().cloned())
+        }
+        DataType::List(_) => {
+            let array = array.as_list::<i32>();
+            (Box::new(ListEncoder::try_new(array, options)?) as _, 
array.nulls().cloned())
+        }
+        DataType::LargeList(_) => {
+            let array = array.as_list::<i64>();
+            (Box::new(ListEncoder::try_new(array, options)?) as _, 
array.nulls().cloned())
+        }
+
+        DataType::Dictionary(_, _) => downcast_dictionary_array! {
+            array => (Box::new(DictionaryEncoder::try_new(array, options)?) as 
_,  array.logical_nulls()),
+            _ => unreachable!()
+        }
+
+        DataType::Map(_, _) => {
+            let array = array.as_map();
+            (Box::new(MapEncoder::try_new(array, options)?) as _,  
array.nulls().cloned())
+        }
+
+        DataType::Struct(fields) => {
+            let array = array.as_struct();
+            let encoders = fields.iter().zip(array.columns()).map(|(field, 
array)| {
+                let (encoder, nulls) = make_encoder_impl(array, options)?;
+                Ok(FieldEncoder{
+                    field: field.clone(),
+                    encoder, nulls
+                })
+            }).collect::<Result<Vec<_>, ArrowError>>()?;
+
+            let encoder = StructArrayEncoder{
+                encoders,
+                explicit_nulls: options.explicit_nulls,
+            };
+            (Box::new(encoder) as _, array.nulls().cloned())
+        }
+        d => match d.is_temporal() {
+            true => {
+                // Note: the implementation of Encoder for ArrayFormatter 
assumes it does not produce
+                // characters that would need to be escaped within a JSON 
string, e.g. `'"'`.
+                // If support for user-provided format specifications is 
added, this assumption
+                // may need to be revisited
+                let options = FormatOptions::new().with_display_error(true);
+                let formatter = ArrayFormatter::try_new(array, &options)?;
+                (Box::new(formatter) as _, array.nulls().cloned())
+            }
+            false => return Err(ArrowError::InvalidArgumentError(format!("JSON 
Writer does not support data type: {d}"))),
+        }
+    })
+}
+
+fn encode_string(s: &str, out: &mut Vec<u8>) {
+    let mut serializer = serde_json::Serializer::new(out);
+    serializer.serialize_str(s).unwrap();
+}
+
+struct FieldEncoder<'a> {
+    field: FieldRef,
+    encoder: Box<dyn Encoder + 'a>,
+    nulls: Option<NullBuffer>,
+}
+
+struct StructArrayEncoder<'a> {
+    encoders: Vec<FieldEncoder<'a>>,
+    explicit_nulls: bool,
+}
+
+impl<'a> Encoder for StructArrayEncoder<'a> {
+    fn encode(&mut self, idx: usize, out: &mut Vec<u8>) {
+        out.push(b'{');
+        let mut is_first = true;
+        for field_encoder in &mut self.encoders {
+            let is_null = field_encoder.nulls.as_ref().is_some_and(|n| 
n.is_null(idx));
+            if is_null && !self.explicit_nulls {
+                continue;
+            }
+
+            if !is_first {
+                out.push(b',');
+            }
+            is_first = false;
+
+            encode_string(field_encoder.field.name(), out);
+            out.push(b':');
+
+            match is_null {
+                true => out.extend_from_slice(b"null"),
+                false => field_encoder.encoder.encode(idx, out),
+            }
+        }
+        out.push(b'}');
+    }
+}
+
+trait PrimitiveEncode: ArrowNativeType {
+    type Buffer;
+
+    // Workaround https://github.com/rust-lang/rust/issues/61415
+    fn init_buffer() -> Self::Buffer;
+
+    /// Encode the primitive value as bytes, returning a reference to that 
slice.
+    ///
+    /// `buf` is temporary space that may be used
+    fn encode(self, buf: &mut Self::Buffer) -> &[u8];
+}
+
+macro_rules! integer_encode {
+    ($($t:ty),*) => {
+        $(
+            impl PrimitiveEncode for $t {
+                type Buffer = [u8; Self::FORMATTED_SIZE];
+
+                fn init_buffer() -> Self::Buffer {
+                    [0; Self::FORMATTED_SIZE]
+                }
+
+                fn encode(self, buf: &mut Self::Buffer) -> &[u8] {
+                    lexical_core::write(self, buf)
+                }
+            }
+        )*
+    };
+}
+integer_encode!(i8, i16, i32, i64, u8, u16, u32, u64);
+
+macro_rules! float_encode {
+    ($($t:ty),*) => {
+        $(
+            impl PrimitiveEncode for $t {
+                type Buffer = [u8; Self::FORMATTED_SIZE];
+
+                fn init_buffer() -> Self::Buffer {
+                    [0; Self::FORMATTED_SIZE]
+                }
+
+                fn encode(self, buf: &mut Self::Buffer) -> &[u8] {
+                    if self.is_infinite() || self.is_nan() {
+                        b"null"
+                    } else {
+                        lexical_core::write(self, buf)
+                    }
+                }
+            }
+        )*
+    };
+}
+float_encode!(f32, f64);
+
+impl PrimitiveEncode for f16 {
+    type Buffer = <f32 as PrimitiveEncode>::Buffer;
+
+    fn init_buffer() -> Self::Buffer {
+        f32::init_buffer()
+    }
+
+    fn encode(self, buf: &mut Self::Buffer) -> &[u8] {
+        self.to_f32().encode(buf)
+    }
+}
+
+struct PrimitiveEncoder<N: PrimitiveEncode> {
+    values: ScalarBuffer<N>,
+    buffer: N::Buffer,
+}
+
+impl<N: PrimitiveEncode> PrimitiveEncoder<N> {
+    fn new<P: ArrowPrimitiveType<Native = N>>(array: &PrimitiveArray<P>) -> 
Self {
+        Self {
+            values: array.values().clone(),
+            buffer: N::init_buffer(),
+        }
+    }
+}
+
+impl<N: PrimitiveEncode> Encoder for PrimitiveEncoder<N> {
+    fn encode(&mut self, idx: usize, out: &mut Vec<u8>) {
+        out.extend_from_slice(self.values[idx].encode(&mut self.buffer));
+    }
+}
+
+struct BooleanEncoder(BooleanArray);
+
+impl Encoder for BooleanEncoder {
+    fn encode(&mut self, idx: usize, out: &mut Vec<u8>) {
+        match self.0.value(idx) {
+            true => out.extend_from_slice(b"true"),
+            false => out.extend_from_slice(b"false"),
+        }
+    }
+}
+
+struct StringEncoder<O: OffsetSizeTrait>(GenericStringArray<O>);
+
+impl<O: OffsetSizeTrait> Encoder for StringEncoder<O> {
+    fn encode(&mut self, idx: usize, out: &mut Vec<u8>) {
+        encode_string(self.0.value(idx), out);
+    }
+}
+
+struct ListEncoder<'a, O: OffsetSizeTrait> {
+    offsets: OffsetBuffer<O>,
+    nulls: Option<NullBuffer>,
+    encoder: Box<dyn Encoder + 'a>,
+}
+
+impl<'a, O: OffsetSizeTrait> ListEncoder<'a, O> {
+    fn try_new(
+        array: &'a GenericListArray<O>,
+        options: &EncoderOptions,
+    ) -> Result<Self, ArrowError> {
+        let (encoder, nulls) = make_encoder_impl(array.values().as_ref(), 
options)?;
+        Ok(Self {
+            offsets: array.offsets().clone(),
+            encoder,
+            nulls,
+        })
+    }
+}
+
+impl<'a, O: OffsetSizeTrait> Encoder for ListEncoder<'a, O> {
+    fn encode(&mut self, idx: usize, out: &mut Vec<u8>) {
+        let end = self.offsets[idx + 1].as_usize();
+        let start = self.offsets[idx].as_usize();
+        out.push(b'[');
+        match self.nulls.as_ref() {
+            Some(n) => (start..end).for_each(|idx| {
+                if idx != start {
+                    out.push(b',')
+                }
+                match n.is_null(idx) {
+                    true => out.extend_from_slice(b"null"),
+                    false => self.encoder.encode(idx, out),
+                }
+            }),
+            None => (start..end).for_each(|idx| {
+                if idx != start {
+                    out.push(b',')
+                }
+                self.encoder.encode(idx, out);
+            }),
+        }
+        out.push(b']');
+    }
+}
+
+struct DictionaryEncoder<'a, K: ArrowDictionaryKeyType> {
+    keys: ScalarBuffer<K::Native>,
+    encoder: Box<dyn Encoder + 'a>,
+}
+
+impl<'a, K: ArrowDictionaryKeyType> DictionaryEncoder<'a, K> {
+    fn try_new(
+        array: &'a DictionaryArray<K>,
+        options: &EncoderOptions,
+    ) -> Result<Self, ArrowError> {
+        let encoder = make_encoder(array.values().as_ref(), options)?;
+
+        Ok(Self {
+            keys: array.keys().values().clone(),
+            encoder,
+        })
+    }
+}
+
+impl<'a, K: ArrowDictionaryKeyType> Encoder for DictionaryEncoder<'a, K> {
+    fn encode(&mut self, idx: usize, out: &mut Vec<u8>) {
+        self.encoder.encode(self.keys[idx].as_usize(), out)
+    }
+}
+
+impl<'a> Encoder for ArrayFormatter<'a> {
+    fn encode(&mut self, idx: usize, out: &mut Vec<u8>) {
+        out.push(b'"');
+        // Should be infallible
+        // Note: We are making an assumption that the formatter does not 
produce characters that require escaping
+        let _ = write!(out, "{}", self.value(idx));
+        out.push(b'"')
+    }
+}
+
+struct NullEncoder;
+
+impl Encoder for NullEncoder {
+    fn encode(&mut self, _idx: usize, _out: &mut Vec<u8>) {
+        unreachable!()
+    }
+}
+
+struct MapEncoder<'a> {
+    offsets: OffsetBuffer<i32>,
+    keys: Box<dyn Encoder + 'a>,
+    values: Box<dyn Encoder + 'a>,
+    value_nulls: Option<NullBuffer>,
+    explicit_nulls: bool,
+}
+
+impl<'a> MapEncoder<'a> {
+    fn try_new(array: &'a MapArray, options: &EncoderOptions) -> Result<Self, 
ArrowError> {
+        let values = array.values();
+        let keys = array.keys();
+
+        if !matches!(keys.data_type(), DataType::Utf8 | DataType::LargeUtf8) {
+            return Err(ArrowError::JsonError(format!(
+                "Only UTF8 keys supported by JSON MapArray Writer: got {:?}",
+                keys.data_type()
+            )));
+        }
+
+        let (keys, key_nulls) = make_encoder_impl(keys, options)?;
+        let (values, value_nulls) = make_encoder_impl(values, options)?;
+
+        // We sanity check nulls as these are currently not enforced by 
MapArray (#1697)
+        if key_nulls.is_some_and(|x| x.null_count() != 0) {
+            return Err(ArrowError::InvalidArgumentError(
+                "Encountered nulls in MapArray keys".to_string(),
+            ));
+        }
+
+        if array.entries().nulls().is_some_and(|x| x.null_count() != 0) {
+            return Err(ArrowError::InvalidArgumentError(
+                "Encountered nulls in MapArray entries".to_string(),
+            ));
+        }
+
+        Ok(Self {
+            offsets: array.offsets().clone(),
+            keys,
+            values,
+            value_nulls,
+            explicit_nulls: options.explicit_nulls,
+        })
+    }
+}
+
+impl<'a> Encoder for MapEncoder<'a> {
+    fn encode(&mut self, idx: usize, out: &mut Vec<u8>) {
+        let end = self.offsets[idx + 1].as_usize();
+        let start = self.offsets[idx].as_usize();
+
+        let mut is_first = true;
+
+        out.push(b'{');
+        for idx in start..end {
+            let is_null = self.value_nulls.as_ref().is_some_and(|n| 
n.is_null(idx));
+            if is_null && !self.explicit_nulls {
+                continue;
+            }
+
+            if !is_first {
+                out.push(b',');
+            }
+            is_first = false;
+
+            self.keys.encode(idx, out);
+            out.push(b':');
+
+            match is_null {
+                true => out.extend_from_slice(b"null"),
+                false => self.values.encode(idx, out),
+            }
+        }
+        out.push(b'}');
+    }
+}
diff --git a/arrow-json/test/data/basic.json b/arrow-json/test/data/basic.json
index a6a8766bf9..fdcae9e655 100644
--- a/arrow-json/test/data/basic.json
+++ b/arrow-json/test/data/basic.json
@@ -1,5 +1,5 @@
-{"a":1, "b":2.0, "c":false, "d":"4", "e":"1970-1-2", "f": "1.02", "g": 
"2012-04-23T18:25:43.511", "h": 1.1}
-{"a":-10, "b":-3.5, "c":true, "d":"4", "e": "1969-12-31", "f": "-0.3", "g": 
"2016-04-23T18:25:43.511", "h": 3.141}
+{"a":1, "b":2.0, "c":false, "d":"4", "e":"1970-1-2", "f": "1.02", "g": 
"2012-04-23T18:25:43.511", "h": 1.125}
+{"a":-10, "b":-3.5, "c":true, "d":"4", "e": "1969-12-31", "f": "-0.3", "g": 
"2016-04-23T18:25:43.511", "h": 3.5}
 {"a":2, "b":0.6, "c":false, "d":"text", "e": "1970-01-02 11:11:11", "f": 
"1377.223"}
 {"a":1, "b":2.0, "c":false, "d":"4", "f": "1337.009"}
 {"a":7, "b":-3.5, "c":true, "d":"4", "f": "1"}
diff --git a/arrow/benches/json_writer.rs b/arrow/benches/json_writer.rs
index a4c486bac6..48be0bccb4 100644
--- a/arrow/benches/json_writer.rs
+++ b/arrow/benches/json_writer.rs
@@ -133,7 +133,7 @@ fn bench_string(c: &mut Criterion) {
     let batch =
         RecordBatch::try_from_iter([("c1", c1 as _), ("c2", c2 as _), ("c3", 
c3 as _)]).unwrap();
 
-    do_bench(c, "bench_dict_array", &batch)
+    do_bench(c, "bench_string", &batch)
 }
 
 fn bench_struct(c: &mut Criterion) {

Reply via email to