Skip to main content

arrow_json/reader/
schema.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use super::ValueIter;
19use arrow_schema::{ArrowError, DataType, Field, Fields, Schema};
20use indexmap::map::IndexMap as HashMap;
21use indexmap::set::IndexSet as HashSet;
22use serde_json::Value;
23use std::borrow::Borrow;
24use std::io::{BufRead, Seek};
25use std::sync::Arc;
26
27#[derive(Debug, Clone)]
28enum InferredType {
29    Scalar(HashSet<DataType>),
30    Array(Box<InferredType>),
31    Object(HashMap<String, InferredType>),
32    Any,
33}
34
35impl InferredType {
36    fn merge(&mut self, other: InferredType) -> Result<(), ArrowError> {
37        match (self, other) {
38            (InferredType::Array(s), InferredType::Array(o)) => {
39                s.merge(*o)?;
40            }
41            (InferredType::Scalar(self_hs), InferredType::Scalar(other_hs)) => {
42                other_hs.into_iter().for_each(|v| {
43                    self_hs.insert(v);
44                });
45            }
46            (InferredType::Object(self_map), InferredType::Object(other_map)) => {
47                for (k, v) in other_map {
48                    self_map.entry(k).or_insert(InferredType::Any).merge(v)?;
49                }
50            }
51            (s @ InferredType::Any, v) => {
52                *s = v;
53            }
54            (_, InferredType::Any) => {}
55            // convert a scalar type to a single-item scalar array type.
56            (InferredType::Array(self_inner_type), other_scalar @ InferredType::Scalar(_)) => {
57                self_inner_type.merge(other_scalar)?;
58            }
59            (s @ InferredType::Scalar(_), InferredType::Array(mut other_inner_type)) => {
60                other_inner_type.merge(s.clone())?;
61                *s = InferredType::Array(other_inner_type);
62            }
63            // incompatible types
64            (s, o) => {
65                return Err(ArrowError::JsonError(format!(
66                    "Incompatible type found during schema inference: {s:?} v.s. {o:?}",
67                )));
68            }
69        }
70
71        Ok(())
72    }
73
74    fn is_none_or_any(ty: Option<&Self>) -> bool {
75        matches!(ty, Some(Self::Any) | None)
76    }
77}
78
79/// Shorthand for building list data type of `ty`
80fn list_type_of(ty: DataType) -> DataType {
81    DataType::List(Arc::new(Field::new_list_field(ty, true)))
82}
83
84/// Coerce data type during inference
85///
86/// * `Int64` and `Float64` should be `Float64`
87/// * Lists and scalars are coerced to a list of a compatible scalar
88/// * All other types are coerced to `Utf8`
89fn coerce_data_type(dt: Vec<&DataType>) -> DataType {
90    let mut dt_iter = dt.into_iter().cloned();
91    let dt_init = dt_iter.next().unwrap_or(DataType::Utf8);
92
93    dt_iter.fold(dt_init, |l, r| match (l, r) {
94        (DataType::Null, o) | (o, DataType::Null) => o,
95        (DataType::Boolean, DataType::Boolean) => DataType::Boolean,
96        (DataType::Int64, DataType::Int64) => DataType::Int64,
97        (DataType::Float64 | DataType::Int64, DataType::Float64)
98        | (DataType::Float64, DataType::Int64) => DataType::Float64,
99        (DataType::List(l), DataType::List(r)) => {
100            list_type_of(coerce_data_type(vec![l.data_type(), r.data_type()]))
101        }
102        // coerce scalar and scalar array into scalar array
103        (DataType::List(e), not_list) | (not_list, DataType::List(e)) => {
104            list_type_of(coerce_data_type(vec![e.data_type(), &not_list]))
105        }
106        _ => DataType::Utf8,
107    })
108}
109
110fn generate_datatype(t: &InferredType) -> Result<DataType, ArrowError> {
111    Ok(match t {
112        InferredType::Scalar(hs) => coerce_data_type(hs.iter().collect()),
113        InferredType::Object(spec) => DataType::Struct(generate_fields(spec)?),
114        InferredType::Array(ele_type) => list_type_of(generate_datatype(ele_type)?),
115        InferredType::Any => DataType::Null,
116    })
117}
118
119fn generate_fields(spec: &HashMap<String, InferredType>) -> Result<Fields, ArrowError> {
120    spec.iter()
121        .map(|(k, types)| Ok(Field::new(k, generate_datatype(types)?, true)))
122        .collect()
123}
124
125/// Generate schema from JSON field names and inferred data types
126fn generate_schema(spec: HashMap<String, InferredType>) -> Result<Schema, ArrowError> {
127    Ok(Schema::new(generate_fields(&spec)?))
128}
129
130/// Infer the fields of a JSON file by reading the first n records of the file, with
131/// `max_read_records` controlling the maximum number of records to read.
132///
133/// If `max_read_records` is not set, the whole file is read to infer its field types.
134///
135/// Returns inferred schema and number of records read.
136///
137/// Contrary to [`infer_json_schema`], this function will seek back to the start of the `reader`.
138/// That way, the `reader` can be used immediately afterwards to create a [`Reader`].
139///
140/// # Examples
141/// ```
142/// use std::fs::File;
143/// use std::io::BufReader;
144/// use arrow_json::reader::infer_json_schema_from_seekable;
145///
146/// let file = File::open("test/data/mixed_arrays.json").unwrap();
147/// // file's cursor's offset at 0
148/// let mut reader = BufReader::new(file);
149/// let inferred_schema = infer_json_schema_from_seekable(&mut reader, None).unwrap();
150/// // file's cursor's offset automatically set at 0
151/// ```
152///
153/// [`Reader`]: super::Reader
154pub fn infer_json_schema_from_seekable<R: BufRead + Seek>(
155    mut reader: R,
156    max_read_records: Option<usize>,
157) -> Result<(Schema, usize), ArrowError> {
158    let schema = infer_json_schema(&mut reader, max_read_records);
159    // return the reader seek back to the start
160    reader.rewind()?;
161
162    schema
163}
164
165/// Infer the fields of a JSON file by reading the first n records of the buffer, with
166/// `max_read_records` controlling the maximum number of records to read.
167///
168/// If `max_read_records` is not set, the whole file is read to infer its field types.
169///
170/// Returns inferred schema and number of records read.
171///
172/// This function will not seek back to the start of the `reader`. The user has to manage the
173/// original file's cursor. This function is useful when the `reader`'s cursor is not available
174/// (does not implement [`Seek`]), such is the case for compressed streams decoders.
175///
176///
177/// Note that JSON is not able to represent all Arrow data types exactly. So the inferred schema
178/// might be different from the schema of the original data that was encoded as JSON. For example,
179/// JSON does not have different integer types, so all integers are inferred as `Int64`. Another
180/// example is binary data, which is encoded as a [Base16] string in JSON and therefore inferred
181/// as String type by this function.
182///
183/// [Base16]: https://en.wikipedia.org/wiki/Base16#Base16
184///
185/// # Examples
186/// ```
187/// use std::fs::File;
188/// use std::io::{BufReader, SeekFrom, Seek};
189/// use flate2::read::GzDecoder;
190/// use arrow_json::reader::infer_json_schema;
191///
192/// let mut file = File::open("test/data/mixed_arrays.json.gz").unwrap();
193///
194/// // file's cursor's offset at 0
195/// let mut reader = BufReader::new(GzDecoder::new(&file));
196/// let inferred_schema = infer_json_schema(&mut reader, None).unwrap();
197/// // cursor's offset at end of file
198///
199/// // seek back to start so that the original file is usable again
200/// file.seek(SeekFrom::Start(0)).unwrap();
201/// ```
202pub fn infer_json_schema<R: BufRead>(
203    reader: R,
204    max_read_records: Option<usize>,
205) -> Result<(Schema, usize), ArrowError> {
206    let mut values = ValueIter::new(reader, max_read_records);
207    let schema = infer_json_schema_from_iterator(&mut values)?;
208    Ok((schema, values.record_count()))
209}
210
211fn set_object_scalar_field_type(
212    field_types: &mut HashMap<String, InferredType>,
213    key: &str,
214    ftype: DataType,
215) -> Result<(), ArrowError> {
216    if InferredType::is_none_or_any(field_types.get(key)) {
217        field_types.insert(key.to_string(), InferredType::Scalar(HashSet::new()));
218    }
219
220    match field_types.get_mut(key).unwrap() {
221        InferredType::Scalar(hs) => {
222            hs.insert(ftype);
223            Ok(())
224        }
225        // in case of column contains both scalar type and scalar array type, we convert type of
226        // this column to scalar array.
227        scalar_array @ InferredType::Array(_) => {
228            let mut hs = HashSet::new();
229            hs.insert(ftype);
230            scalar_array.merge(InferredType::Scalar(hs))?;
231            Ok(())
232        }
233        t => Err(ArrowError::JsonError(format!(
234            "Expected scalar or scalar array JSON type, found: {t:?}",
235        ))),
236    }
237}
238
239fn infer_scalar_array_type(array: &[Value]) -> Result<InferredType, ArrowError> {
240    let mut hs = HashSet::new();
241
242    for v in array {
243        match v {
244            Value::Null => {}
245            Value::Number(n) => {
246                if n.is_i64() {
247                    hs.insert(DataType::Int64);
248                } else {
249                    hs.insert(DataType::Float64);
250                }
251            }
252            Value::Bool(_) => {
253                hs.insert(DataType::Boolean);
254            }
255            Value::String(_) => {
256                hs.insert(DataType::Utf8);
257            }
258            Value::Array(_) | Value::Object(_) => {
259                return Err(ArrowError::JsonError(format!(
260                    "Expected scalar value for scalar array, got: {v:?}"
261                )));
262            }
263        }
264    }
265
266    Ok(InferredType::Scalar(hs))
267}
268
269fn infer_nested_array_type(array: &[Value]) -> Result<InferredType, ArrowError> {
270    let mut inner_ele_type = InferredType::Any;
271
272    for v in array {
273        match v {
274            Value::Array(inner_array) => {
275                inner_ele_type.merge(infer_array_element_type(inner_array)?)?;
276            }
277            x => {
278                return Err(ArrowError::JsonError(format!(
279                    "Got non array element in nested array: {x:?}"
280                )));
281            }
282        }
283    }
284
285    Ok(InferredType::Array(Box::new(inner_ele_type)))
286}
287
288fn infer_struct_array_type(array: &[Value]) -> Result<InferredType, ArrowError> {
289    let mut field_types = HashMap::new();
290
291    for v in array {
292        match v {
293            Value::Object(map) => {
294                collect_field_types_from_object(&mut field_types, map)?;
295            }
296            _ => {
297                return Err(ArrowError::JsonError(format!(
298                    "Expected struct value for struct array, got: {v:?}"
299                )));
300            }
301        }
302    }
303
304    Ok(InferredType::Object(field_types))
305}
306
307fn infer_array_element_type(array: &[Value]) -> Result<InferredType, ArrowError> {
308    match array.iter().take(1).next() {
309        None => Ok(InferredType::Any), // empty array, return any type that can be updated later
310        Some(a) => match a {
311            Value::Array(_) => infer_nested_array_type(array),
312            Value::Object(_) => infer_struct_array_type(array),
313            _ => infer_scalar_array_type(array),
314        },
315    }
316}
317
318fn collect_field_types_from_object(
319    field_types: &mut HashMap<String, InferredType>,
320    map: &serde_json::map::Map<String, Value>,
321) -> Result<(), ArrowError> {
322    for (k, v) in map {
323        match v {
324            Value::Array(array) => {
325                let ele_type = infer_array_element_type(array)?;
326
327                if InferredType::is_none_or_any(field_types.get(k)) {
328                    match ele_type {
329                        InferredType::Scalar(_) => {
330                            field_types.insert(
331                                k.clone(),
332                                InferredType::Array(Box::new(InferredType::Scalar(HashSet::new()))),
333                            );
334                        }
335                        InferredType::Object(_) => {
336                            field_types.insert(
337                                k.clone(),
338                                InferredType::Array(Box::new(InferredType::Object(HashMap::new()))),
339                            );
340                        }
341                        InferredType::Any | InferredType::Array(_) => {
342                            // set inner type to any for nested array as well
343                            // so it can be updated properly from subsequent type merges
344                            field_types.insert(
345                                k.clone(),
346                                InferredType::Array(Box::new(InferredType::Any)),
347                            );
348                        }
349                    }
350                }
351
352                match field_types.get_mut(k).unwrap() {
353                    InferredType::Array(inner_type) => {
354                        inner_type.merge(ele_type)?;
355                    }
356                    // in case of column contains both scalar type and scalar array type, we
357                    // convert type of this column to scalar array.
358                    field_type @ InferredType::Scalar(_) => {
359                        field_type.merge(ele_type)?;
360                        *field_type = InferredType::Array(Box::new(field_type.clone()));
361                    }
362                    t => {
363                        return Err(ArrowError::JsonError(format!(
364                            "Expected array json type, found: {t:?}",
365                        )));
366                    }
367                }
368            }
369            Value::Bool(_) => {
370                set_object_scalar_field_type(field_types, k, DataType::Boolean)?;
371            }
372            Value::Null => {
373                // we treat json as nullable by default when inferring, so just
374                // mark existence of a field if it wasn't known before
375                if !field_types.contains_key(k) {
376                    field_types.insert(k.clone(), InferredType::Any);
377                }
378            }
379            Value::Number(n) => {
380                if n.is_i64() {
381                    set_object_scalar_field_type(field_types, k, DataType::Int64)?;
382                } else {
383                    set_object_scalar_field_type(field_types, k, DataType::Float64)?;
384                }
385            }
386            Value::String(_) => {
387                set_object_scalar_field_type(field_types, k, DataType::Utf8)?;
388            }
389            Value::Object(inner_map) => {
390                if matches!(
391                    field_types.get(k).unwrap_or(&InferredType::Any),
392                    InferredType::Any
393                ) {
394                    field_types.insert(k.clone(), InferredType::Object(HashMap::new()));
395                }
396                match field_types.get_mut(k).unwrap() {
397                    InferredType::Object(inner_field_types) => {
398                        collect_field_types_from_object(inner_field_types, inner_map)?;
399                    }
400                    t => {
401                        return Err(ArrowError::JsonError(format!(
402                            "Expected object json type, found: {t:?}",
403                        )));
404                    }
405                }
406            }
407        }
408    }
409
410    Ok(())
411}
412
413/// Infer the fields of a JSON file by reading all items from the JSON Value Iterator.
414///
415/// The following type coercion logic is implemented:
416/// * `Int64` and `Float64` are converted to `Float64`
417/// * Lists and scalars are coerced to a list of a compatible scalar
418/// * All other cases are coerced to `Utf8` (String)
419///
420/// Note that the above coercion logic is different from what Spark has, where it would default to
421/// String type in case of List and Scalar values appeared in the same field.
422///
423/// The reason we diverge here is because we don't have utilities to deal with JSON data once it's
424/// interpreted as Strings. We should match Spark's behavior once we added more JSON parsing
425/// kernels in the future.
426pub fn infer_json_schema_from_iterator<I, V>(value_iter: I) -> Result<Schema, ArrowError>
427where
428    I: Iterator<Item = Result<V, ArrowError>>,
429    V: Borrow<Value>,
430{
431    let mut field_types: HashMap<String, InferredType> = HashMap::new();
432
433    for record in value_iter {
434        match record?.borrow() {
435            Value::Object(map) => {
436                collect_field_types_from_object(&mut field_types, map)?;
437            }
438            value => {
439                return Err(ArrowError::JsonError(format!(
440                    "Expected JSON record to be an object, found {value:?}"
441                )));
442            }
443        }
444    }
445
446    generate_schema(field_types)
447}
448
449#[cfg(test)]
450mod tests {
451    use super::*;
452    use flate2::read::GzDecoder;
453    use std::fs::File;
454    use std::io::{BufReader, Cursor};
455
456    #[test]
457    fn test_json_infer_schema() {
458        let schema = Schema::new(vec![
459            Field::new("a", DataType::Int64, true),
460            Field::new("b", list_type_of(DataType::Float64), true),
461            Field::new("c", list_type_of(DataType::Boolean), true),
462            Field::new("d", list_type_of(DataType::Utf8), true),
463        ]);
464
465        let mut reader = BufReader::new(File::open("test/data/mixed_arrays.json").unwrap());
466        let (inferred_schema, n_rows) = infer_json_schema_from_seekable(&mut reader, None).unwrap();
467
468        assert_eq!(inferred_schema, schema);
469        assert_eq!(n_rows, 4);
470
471        let file = File::open("test/data/mixed_arrays.json.gz").unwrap();
472        let mut reader = BufReader::new(GzDecoder::new(&file));
473        let (inferred_schema, n_rows) = infer_json_schema(&mut reader, None).unwrap();
474
475        assert_eq!(inferred_schema, schema);
476        assert_eq!(n_rows, 4);
477    }
478
479    #[test]
480    fn test_row_limit() {
481        let mut reader = BufReader::new(File::open("test/data/basic.json").unwrap());
482
483        let (_, n_rows) = infer_json_schema_from_seekable(&mut reader, None).unwrap();
484        assert_eq!(n_rows, 12);
485
486        let (_, n_rows) = infer_json_schema_from_seekable(&mut reader, Some(5)).unwrap();
487        assert_eq!(n_rows, 5);
488    }
489
490    #[test]
491    fn test_json_infer_schema_nested_structs() {
492        let schema = Schema::new(vec![
493            Field::new(
494                "c1",
495                DataType::Struct(Fields::from(vec![
496                    Field::new("a", DataType::Boolean, true),
497                    Field::new(
498                        "b",
499                        DataType::Struct(vec![Field::new("c", DataType::Utf8, true)].into()),
500                        true,
501                    ),
502                ])),
503                true,
504            ),
505            Field::new("c2", DataType::Int64, true),
506            Field::new("c3", DataType::Utf8, true),
507        ]);
508
509        let inferred_schema = infer_json_schema_from_iterator(
510            vec![
511                Ok(serde_json::json!({"c1": {"a": true, "b": {"c": "text"}}, "c2": 1})),
512                Ok(serde_json::json!({"c1": {"a": false, "b": null}, "c2": 0})),
513                Ok(serde_json::json!({"c1": {"a": true, "b": {"c": "text"}}, "c3": "ok"})),
514            ]
515            .into_iter(),
516        )
517        .unwrap();
518
519        assert_eq!(inferred_schema, schema);
520    }
521
522    #[test]
523    fn test_json_infer_schema_struct_in_list() {
524        let schema = Schema::new(vec![
525            Field::new(
526                "c1",
527                list_type_of(DataType::Struct(Fields::from(vec![
528                    Field::new("a", DataType::Utf8, true),
529                    Field::new("b", DataType::Int64, true),
530                    Field::new("c", DataType::Boolean, true),
531                ]))),
532                true,
533            ),
534            Field::new("c2", DataType::Float64, true),
535            Field::new(
536                "c3",
537                // empty json array's inner types are inferred as null
538                list_type_of(DataType::Null),
539                true,
540            ),
541        ]);
542
543        let inferred_schema = infer_json_schema_from_iterator(
544            vec![
545                Ok(serde_json::json!({
546                    "c1": [{"a": "foo", "b": 100}], "c2": 1, "c3": [],
547                })),
548                Ok(serde_json::json!({
549                    "c1": [{"a": "bar", "b": 2}, {"a": "foo", "c": true}], "c2": 0, "c3": [],
550                })),
551                Ok(serde_json::json!({"c1": [], "c2": 0.5, "c3": []})),
552            ]
553            .into_iter(),
554        )
555        .unwrap();
556
557        assert_eq!(inferred_schema, schema);
558    }
559
560    #[test]
561    fn test_json_infer_schema_nested_list() {
562        let schema = Schema::new(vec![
563            Field::new("c1", list_type_of(list_type_of(DataType::Utf8)), true),
564            Field::new("c2", DataType::Float64, true),
565        ]);
566
567        let inferred_schema = infer_json_schema_from_iterator(
568            vec![
569                Ok(serde_json::json!({
570                    "c1": [],
571                    "c2": 12,
572                })),
573                Ok(serde_json::json!({
574                    "c1": [["a", "b"], ["c"]],
575                })),
576                Ok(serde_json::json!({
577                    "c1": [["foo"]],
578                    "c2": 0.11,
579                })),
580            ]
581            .into_iter(),
582        )
583        .unwrap();
584
585        assert_eq!(inferred_schema, schema);
586    }
587
588    #[test]
589    fn test_infer_json_schema_bigger_than_i64_max() {
590        let bigger_than_i64_max = (i64::MAX as i128) + 1;
591        let smaller_than_i64_min = (i64::MIN as i128) - 1;
592        let json = format!(
593            "{{ \"bigger_than_i64_max\": {bigger_than_i64_max}, \"smaller_than_i64_min\": {smaller_than_i64_min} }}",
594        );
595        let mut buf_reader = BufReader::new(json.as_bytes());
596        let (inferred_schema, _) = infer_json_schema(&mut buf_reader, Some(1)).unwrap();
597        let fields = inferred_schema.fields();
598
599        let (_, big_field) = fields.find("bigger_than_i64_max").unwrap();
600        assert_eq!(big_field.data_type(), &DataType::Float64);
601        let (_, small_field) = fields.find("smaller_than_i64_min").unwrap();
602        assert_eq!(small_field.data_type(), &DataType::Float64);
603    }
604
605    #[test]
606    fn test_coercion_scalar_and_list() {
607        assert_eq!(
608            list_type_of(DataType::Float64),
609            coerce_data_type(vec![&DataType::Float64, &list_type_of(DataType::Float64)])
610        );
611        assert_eq!(
612            list_type_of(DataType::Float64),
613            coerce_data_type(vec![&DataType::Float64, &list_type_of(DataType::Int64)])
614        );
615        assert_eq!(
616            list_type_of(DataType::Int64),
617            coerce_data_type(vec![&DataType::Int64, &list_type_of(DataType::Int64)])
618        );
619        // boolean and number are incompatible, return utf8
620        assert_eq!(
621            list_type_of(DataType::Utf8),
622            coerce_data_type(vec![&DataType::Boolean, &list_type_of(DataType::Float64)])
623        );
624    }
625
626    #[test]
627    fn test_invalid_json_infer_schema() {
628        let re = infer_json_schema_from_seekable(Cursor::new(b"}"), None);
629        assert_eq!(
630            re.err().unwrap().to_string(),
631            "Json error: Not valid JSON: expected value at line 1 column 1",
632        );
633    }
634
635    #[test]
636    fn test_null_field_inferred_as_null() {
637        let data = r#"
638            {"in":1,    "ni":null, "ns":null, "sn":"4",  "n":null, "an":[],   "na": null, "nas":null}
639            {"in":null, "ni":2,    "ns":"3",  "sn":null, "n":null, "an":null, "na": [],   "nas":["8"]}
640            {"in":1,    "ni":null, "ns":null, "sn":"4",  "n":null, "an":[],   "na": null, "nas":[]}
641        "#;
642        let (inferred_schema, _) =
643            infer_json_schema_from_seekable(Cursor::new(data), None).expect("infer");
644        let schema = Schema::new(vec![
645            Field::new("an", list_type_of(DataType::Null), true),
646            Field::new("in", DataType::Int64, true),
647            Field::new("n", DataType::Null, true),
648            Field::new("na", list_type_of(DataType::Null), true),
649            Field::new("nas", list_type_of(DataType::Utf8), true),
650            Field::new("ni", DataType::Int64, true),
651            Field::new("ns", DataType::Utf8, true),
652            Field::new("sn", DataType::Utf8, true),
653        ]);
654        assert_eq!(inferred_schema, schema);
655    }
656
657    #[test]
658    fn test_infer_from_null_then_object() {
659        let data = r#"
660            {"obj":null}
661            {"obj":{"foo":1}}
662        "#;
663        let (inferred_schema, _) =
664            infer_json_schema_from_seekable(Cursor::new(data), None).expect("infer");
665        let schema = Schema::new(vec![Field::new(
666            "obj",
667            DataType::Struct(std::iter::once(Field::new("foo", DataType::Int64, true)).collect()),
668            true,
669        )]);
670        assert_eq!(inferred_schema, schema);
671    }
672}