Skip to main content

arrow_json/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: See the NOTICE file distributed with this work for additional information regarding copyright ownership
3// SPDX-FileContributor: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements
4
5//! Transfer data between the Arrow memory format and JSON line-delimited records.
6//!
7//! See the module level documentation for the
8//! [`reader`] and [`writer`] for usage examples.
9//!
10//! # Binary Data Encoding
11//!
12//! As per [RFC7159] JSON cannot encode arbitrary binary data. This crate works around that
13//! limitation by encoding/decoding binary data as a [hexadecimal] string (i.e.
14//! [`Base16` encoding]).
15//!
16//! Note that `Base16` only has 50% space efficiency (i.e., the encoded data is twice as large
17//! as the original). If that is an issue, there are two alternatives:
18//!
19//! 1. Provide a custom encoder and/or decoder. See the [Customizing the encoder] section of the
20//!    writer documentation and the [Customizing the decoder] section of the reader documentation.
21//! 2. Convert binary data to/from a different encoding format such as `Base64` before
22//!    writing / after reading, as shown in the following example.
23//!
24//! [Customizing the encoder]: writer#customizing-the-encoder
25//! [Customizing the decoder]: reader#customizing-the-decoder
26//!
27//! ## `Base64` Encoding Example
28//!
29//! [`Base64`] is a common [binary-to-text encoding] scheme with a space efficiency of 75%. The
30//! following example shows how to use the [`arrow_cast`] crate to encode binary data to `Base64`
31//! before converting it to JSON and how to decode it back.
32//!
33//! ```
34//! # use std::io::Cursor;
35//! # use std::sync::Arc;
36//! # use arrow_array::{BinaryArray, RecordBatch, StringArray};
37//! # use arrow_array::cast::AsArray;
38//! use arrow_cast::base64::{b64_decode, b64_encode, BASE64_STANDARD};
39//! # use arrow_json::{LineDelimitedWriter, ReaderBuilder};
40//! #
41//! // The data we want to write
42//! let input = BinaryArray::from(vec![b"\xDE\x00\xFF".as_ref()]);
43//!
44//! // Base64 encode it to a string
45//! let encoded: StringArray = b64_encode(&BASE64_STANDARD, &input);
46//!
47//! // Write the StringArray to JSON
48//! let batch = RecordBatch::try_from_iter([("col", Arc::new(encoded) as _)]).unwrap();
49//! let mut buf = Vec::with_capacity(1024);
50//! let mut writer = LineDelimitedWriter::new(&mut buf);
51//! writer.write(&batch).unwrap();
52//! writer.finish().unwrap();
53//!
54//! // Read the JSON data
55//! let cursor = Cursor::new(buf);
56//! let mut reader = ReaderBuilder::new(batch.schema()).build(cursor).unwrap();
57//! let batch = reader.next().unwrap().unwrap();
58//!
59//! // Reverse the base64 encoding
60//! let col: BinaryArray = batch.column(0).as_string::<i32>().clone().into();
61//! let output = b64_decode(&BASE64_STANDARD, &col).unwrap();
62//!
63//! assert_eq!(input, output);
64//! ```
65//!
66//! [RFC7159]: https://datatracker.ietf.org/doc/html/rfc7159#section-8.1
67//! [binary-to-text encoding]: https://en.wikipedia.org/wiki/Binary-to-text_encoding
68//! [hexadecimal]: https://en.wikipedia.org/wiki/Hexadecimal
69//! [`Base16` encoding]: https://en.wikipedia.org/wiki/Base16#Base16
70//! [`Base64`]: https://en.wikipedia.org/wiki/Base64
71//!
72//! # Platform Support
73//!
74//! Only little-endian platforms are officially supported and tested in CI.
75//! Big-endian platforms are not tested in CI and may not work correctly.
76//! Fixes for big-endian platforms are welcome and handled on a best-effort basis,
77//! but compatibility is not guaranteed.
78
79#![doc(
80    html_logo_url = "https://arrow.apache.org/img/arrow-logo_chevrons_black-txt_white-bg.svg",
81    html_favicon_url = "https://arrow.apache.org/img/arrow-logo_chevrons_black-txt_transparent-bg.svg"
82)]
83#![cfg_attr(docsrs, feature(doc_cfg))]
84#![deny(rustdoc::broken_intra_doc_links)]
85#![warn(missing_docs)]
86
87pub mod reader;
88pub mod writer;
89
90pub use self::reader::{
91    ArrayDecoder, DecoderContext, DecoderFactory, Reader, ReaderBuilder, Tape, TapeElement,
92};
93pub use self::writer::{
94    ArrayWriter, Encoder, EncoderFactory, EncoderOptions, LineDelimitedWriter, Writer,
95    WriterBuilder,
96};
97use half::f16;
98use serde_json::{Number, Value};
99
100/// Specifies what is considered valid JSON when reading or writing
101/// RecordBatches or StructArrays.
102///
103/// This enum controls which form(s) the Reader will accept and which form the
104/// Writer will produce. For example, if the RecordBatch Schema is
105/// `[("a", Int32), ("r", Struct("b": Boolean, "c" Utf8))]`
106/// then a Reader with [`StructMode::ObjectOnly`] would read rows of the form
107/// `{"a": 1, "r": {"b": true, "c": "cat"}}` while with [`StructMode::ListOnly`]
108/// would read rows of the form `[1, [true, "cat"]]`. A Writer would produce
109/// rows formatted similarly.
110///
111/// The list encoding is more compact if the schema is known, and is used by
112/// tools such as [Presto] and [Trino].
113///
114/// When reading objects, the order of the key does not matter. When reading
115/// lists, the entries must be the same number and in the same order as the
116/// struct fields. Map columns are not affected by this option.
117///
118/// [Presto]: https://prestodb.io/docs/current/develop/client-protocol.html#important-queryresults-attributes
119/// [Trino]: https://trino.io/docs/current/develop/client-protocol.html#important-queryresults-attributes
120#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
121pub enum StructMode {
122    #[default]
123    /// Encode/decode structs as objects (e.g., {"a": 1, "b": "c"})
124    ObjectOnly,
125    /// Encode/decode structs as lists (e.g., [1, "c"])
126    ListOnly,
127}
128
129/// Trait declaring any type that is serializable to JSON. This includes all primitive types (bool, i32, etc.).
130pub trait JsonSerializable: 'static {
131    /// Converts self into json value if its possible
132    fn into_json_value(self) -> Option<Value>;
133}
134
135macro_rules! json_serializable {
136    ($t:ty) => {
137        impl JsonSerializable for $t {
138            fn into_json_value(self) -> Option<Value> {
139                Some(self.into())
140            }
141        }
142    };
143}
144
145json_serializable!(bool);
146json_serializable!(u8);
147json_serializable!(u16);
148json_serializable!(u32);
149json_serializable!(u64);
150json_serializable!(i8);
151json_serializable!(i16);
152json_serializable!(i32);
153json_serializable!(i64);
154
155impl JsonSerializable for i128 {
156    fn into_json_value(self) -> Option<Value> {
157        // Serialize as string to avoid issues with arbitrary_precision serde_json feature
158        // - https://github.com/serde-rs/json/issues/559
159        // - https://github.com/serde-rs/json/issues/845
160        // - https://github.com/serde-rs/json/issues/846
161        Some(self.to_string().into())
162    }
163}
164
165impl JsonSerializable for f16 {
166    fn into_json_value(self) -> Option<Value> {
167        Number::from_f64(f64::round(f64::from(self) * 1000.0) / 1000.0).map(Value::Number)
168    }
169}
170
171impl JsonSerializable for f32 {
172    fn into_json_value(self) -> Option<Value> {
173        Number::from_f64(f64::round(self as f64 * 1000.0) / 1000.0).map(Value::Number)
174    }
175}
176
177impl JsonSerializable for f64 {
178    fn into_json_value(self) -> Option<Value> {
179        Number::from_f64(self).map(Value::Number)
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186    use crate::writer::JsonArray;
187    use crate::writer::LineDelimited;
188    use arrow_array::{
189        ArrayRef, GenericBinaryArray, GenericByteViewArray, GenericListViewArray, RecordBatch,
190        RecordBatchWriter, builder::FixedSizeBinaryBuilder, types::BinaryViewType,
191    };
192    use arrow_schema::{DataType, Field, Fields, Schema};
193    use serde_json::Value::{Bool, Number as VNumber, String as VString};
194    use std::io::Cursor;
195    use std::sync::Arc;
196
197    #[test]
198    fn test_arrow_native_type_to_json() {
199        assert_eq!(Some(Bool(true)), true.into_json_value());
200        assert_eq!(Some(VNumber(Number::from(1))), 1i8.into_json_value());
201        assert_eq!(Some(VNumber(Number::from(1))), 1i16.into_json_value());
202        assert_eq!(Some(VNumber(Number::from(1))), 1i32.into_json_value());
203        assert_eq!(Some(VNumber(Number::from(1))), 1i64.into_json_value());
204        assert_eq!(Some(VString("1".to_string())), 1i128.into_json_value());
205        assert_eq!(Some(VNumber(Number::from(1))), 1u8.into_json_value());
206        assert_eq!(Some(VNumber(Number::from(1))), 1u16.into_json_value());
207        assert_eq!(Some(VNumber(Number::from(1))), 1u32.into_json_value());
208        assert_eq!(Some(VNumber(Number::from(1))), 1u64.into_json_value());
209        assert_eq!(
210            Some(VNumber(Number::from_f64(0.01f64).unwrap())),
211            0.01.into_json_value()
212        );
213        assert_eq!(
214            Some(VNumber(Number::from_f64(0.01f64).unwrap())),
215            0.01f64.into_json_value()
216        );
217        assert_eq!(None, f32::NAN.into_json_value());
218    }
219
220    #[test]
221    fn test_json_roundtrip_structs() {
222        let schema = Arc::new(Schema::new(vec![
223            Field::new(
224                "c1",
225                DataType::Struct(Fields::from(vec![
226                    Field::new("c11", DataType::Int32, true),
227                    Field::new(
228                        "c12",
229                        DataType::Struct(vec![Field::new("c121", DataType::Utf8, false)].into()),
230                        false,
231                    ),
232                ])),
233                false,
234            ),
235            Field::new("c2", DataType::Utf8, false),
236        ]));
237
238        {
239            let object_input = r#"{"c1":{"c11":1,"c12":{"c121":"e"}},"c2":"a"}
240{"c1":{"c12":{"c121":"f"}},"c2":"b"}
241{"c1":{"c11":5,"c12":{"c121":"g"}},"c2":"c"}
242"#
243            .as_bytes();
244            let object_reader = ReaderBuilder::new(schema.clone())
245                .with_struct_mode(StructMode::ObjectOnly)
246                .build(object_input)
247                .unwrap();
248
249            let mut object_output: Vec<u8> = Vec::new();
250            let mut object_writer = WriterBuilder::new()
251                .with_struct_mode(StructMode::ObjectOnly)
252                .build::<_, LineDelimited>(&mut object_output);
253            for batch_res in object_reader {
254                object_writer.write(&batch_res.unwrap()).unwrap();
255            }
256            assert_eq!(object_input, &object_output);
257        }
258
259        {
260            let list_input = r#"[[1,["e"]],"a"]
261[[null,["f"]],"b"]
262[[5,["g"]],"c"]
263"#
264            .as_bytes();
265            let list_reader = ReaderBuilder::new(schema.clone())
266                .with_struct_mode(StructMode::ListOnly)
267                .build(list_input)
268                .unwrap();
269
270            let mut list_output: Vec<u8> = Vec::new();
271            let mut list_writer = WriterBuilder::new()
272                .with_struct_mode(StructMode::ListOnly)
273                .build::<_, LineDelimited>(&mut list_output);
274            for batch_res in list_reader {
275                list_writer.write(&batch_res.unwrap()).unwrap();
276            }
277            assert_eq!(list_input, &list_output);
278        }
279    }
280
281    #[test]
282    #[expect(invalid_from_utf8)]
283    fn test_json_roundtrip_binary() {
284        let not_utf8: &[u8] = b"Not UTF8 \xa0\xa1!";
285        assert!(str::from_utf8(not_utf8).is_err());
286
287        let values: &[Option<&[u8]>] = &[
288            Some(b"Bob Thompson" as &[u8]),
289            None,
290            Some(b"Troy McClure" as &[u8]),
291            Some(not_utf8),
292        ];
293        // Binary:
294        assert_binary_json(Arc::new(GenericBinaryArray::<i32>::from_iter(values)));
295
296        // LargeBinary:
297        assert_binary_json(Arc::new(GenericBinaryArray::<i64>::from_iter(values)));
298
299        // FixedSizeBinary:
300        assert_binary_json(build_array_fixed_size_binary(12, values));
301
302        // BinaryView:
303        assert_binary_json(Arc::new(GenericByteViewArray::<BinaryViewType>::from_iter(
304            values,
305        )));
306    }
307
308    fn build_array_fixed_size_binary(byte_width: i32, values: &[Option<&[u8]>]) -> ArrayRef {
309        let mut builder = FixedSizeBinaryBuilder::new(byte_width);
310        for value in values {
311            match value {
312                Some(v) => builder.append_value(v).unwrap(),
313                None => builder.append_null(),
314            }
315        }
316        Arc::new(builder.finish())
317    }
318
319    fn assert_binary_json(array: ArrayRef) {
320        // encode and check JSON with and without explicit nulls
321        assert_binary_json_with_writer(
322            array.clone(),
323            WriterBuilder::new().with_explicit_nulls(true),
324        );
325        assert_binary_json_with_writer(array, WriterBuilder::new().with_explicit_nulls(false));
326    }
327
328    fn assert_binary_json_with_writer(array: ArrayRef, builder: WriterBuilder) {
329        let batch = RecordBatch::try_from_iter([("bytes", array)]).unwrap();
330
331        let mut buf = Vec::new();
332        let json_value: Value = {
333            let mut writer = builder.build::<_, JsonArray>(&mut buf);
334            writer.write(&batch).unwrap();
335            writer.close().unwrap();
336            serde_json::from_slice(&buf).unwrap()
337        };
338
339        let json_array = json_value.as_array().unwrap();
340
341        let decoded = {
342            let mut decoder = ReaderBuilder::new(batch.schema().clone())
343                .build_decoder()
344                .unwrap();
345            decoder.serialize(json_array).unwrap();
346            decoder.flush().unwrap().unwrap()
347        };
348
349        assert_eq!(batch, decoded);
350    }
351
352    fn assert_list_view_roundtrip<O: arrow_array::OffsetSizeTrait>() {
353        let flat_field = Arc::new(Field::new("item", DataType::Int32, true));
354        let flat_dt = GenericListViewArray::<O>::DATA_TYPE_CONSTRUCTOR(flat_field);
355
356        let nested_inner = Arc::new(Field::new("item", DataType::Int32, false));
357        let nested_inner_dt = GenericListViewArray::<O>::DATA_TYPE_CONSTRUCTOR(nested_inner);
358        let nested_outer = Arc::new(Field::new("item", nested_inner_dt, true));
359        let nested_dt = GenericListViewArray::<O>::DATA_TYPE_CONSTRUCTOR(nested_outer);
360
361        let schema = Arc::new(Schema::new(vec![
362            Field::new("flat", flat_dt, true),
363            Field::new("nested", nested_dt, true),
364        ]));
365
366        let input = r#"{"flat":[1,2,3],"nested":[[1,2],[3]]}
367{"flat":[4,null]}
368{}
369{"flat":[6],"nested":[[4,5,6]]}
370{"flat":[]}
371"#
372        .as_bytes();
373
374        let batches: Vec<RecordBatch> = ReaderBuilder::new(schema.clone())
375            .with_batch_size(1024)
376            .build(Cursor::new(input))
377            .unwrap()
378            .collect::<Result<Vec<_>, _>>()
379            .unwrap();
380
381        let mut output = Vec::new();
382        let mut writer = WriterBuilder::new().build::<_, LineDelimited>(&mut output);
383        for batch in &batches {
384            writer.write(batch).unwrap();
385        }
386        writer.finish().unwrap();
387
388        assert_eq!(input, &output);
389    }
390
391    #[test]
392    fn test_json_roundtrip_list_view() {
393        assert_list_view_roundtrip::<i32>();
394        assert_list_view_roundtrip::<i64>();
395    }
396
397    #[test]
398    fn test_json_roundtrip_fixed_size_list() {
399        let inner = Arc::new(Field::new("item", DataType::Int32, true));
400        let schema = Arc::new(Schema::new(vec![
401            Field::new("flat", DataType::FixedSizeList(inner.clone(), 3), true),
402            Field::new(
403                "nested",
404                DataType::FixedSizeList(
405                    Arc::new(Field::new("item", DataType::FixedSizeList(inner, 2), true)),
406                    2,
407                ),
408                true,
409            ),
410        ]));
411
412        let input = r#"{"flat":[1,2,3],"nested":[[1,2],[3,4]]}
413{"flat":[4,null,5]}
414{"flat":[6,7,8],"nested":[[null,5],[6,null]]}
415"#
416        .as_bytes();
417
418        let batches: Vec<RecordBatch> = ReaderBuilder::new(schema.clone())
419            .with_batch_size(1024)
420            .build(Cursor::new(input))
421            .unwrap()
422            .collect::<Result<Vec<_>, _>>()
423            .unwrap();
424
425        let mut output = Vec::new();
426        let mut writer = WriterBuilder::new().build::<_, LineDelimited>(&mut output);
427        for batch in &batches {
428            writer.write(batch).unwrap();
429        }
430        writer.finish().unwrap();
431
432        assert_eq!(input, &output);
433    }
434}