This is an automated email from the ASF dual-hosted git repository.
Jefffrey pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git
The following commit(s) were added to refs/heads/main by this push:
new 1d4437018c fix(arrow-json): validate map value nullability (#10475)
1d4437018c is described below
commit 1d4437018c145a8595210609e89815d13dde8d9b
Author: subotac <[email protected]>
AuthorDate: Thu Jul 30 05:42:45 2026 +0300
fix(arrow-json): validate map value nullability (#10475)
> AI disclosure: Codex assisted with issue analysis and running local
test
# Which issue does this PR close?
- Closes #6391.
# Rationale for this change
The JSON map decoder constructed map entries with an unchecked
`StructArray` constructor. This bypassed child field nullability
validation and allowed a null value even when the map value field was
non-nullable.
# What changes are included in this PR?
- Construct map entries with `StructArray::try_new_with_length` so
existing schema validation rejects unmasked nulls.
- Add a regression test for a null JSON map value with a non-nullable
value field.
# Are these changes tested?
Yes:
- `cargo +stable fmt --all -- --check`
- `cargo test -p arrow-json --all-features`
- `cargo clippy --workspace --all-targets --all-features -- -D warnings`
- `git diff --check`
# Are there any user-facing changes?
Yes. Reading a null JSON map value against a non-nullable value field
now returns an error instead of producing an array that violates its
schema. There are no public API changes.
---
arrow-json/src/reader/map_array.rs | 15 ++++++---------
arrow-json/src/reader/mod.rs | 25 +++++++++++++++++++++++++
2 files changed, 31 insertions(+), 9 deletions(-)
diff --git a/arrow-json/src/reader/map_array.rs
b/arrow-json/src/reader/map_array.rs
index 396b60b162..abb035193c 100644
--- a/arrow-json/src/reader/map_array.rs
+++ b/arrow-json/src/reader/map_array.rs
@@ -130,15 +130,12 @@ impl ArrayDecoder for MapArrayDecoder {
let key_array = self.keys.decode(tape, &key_pos)?;
let value_array = self.values.decode(tape, &value_pos)?;
- // SAFETY: fields/arrays match the schema, lengths are equal, no nulls
- let entries = unsafe {
- StructArray::new_unchecked_with_length(
- self.key_value_fields.clone(),
- vec![key_array, value_array],
- None,
- key_pos.len(),
- )
- };
+ let entries = StructArray::try_new_with_length(
+ self.key_value_fields.clone(),
+ vec![key_array, value_array],
+ None,
+ key_pos.len(),
+ )?;
let nulls = nulls.as_mut().and_then(|x| x.finish());
// SAFETY: offsets are built monotonically starting from 0
diff --git a/arrow-json/src/reader/mod.rs b/arrow-json/src/reader/mod.rs
index ea4cbe2664..bc95f9562b 100644
--- a/arrow-json/src/reader/mod.rs
+++ b/arrow-json/src/reader/mod.rs
@@ -1325,6 +1325,31 @@ mod tests {
assert_eq!(formatter.value(2).to_string(), "{c: null, a: [baz]}");
}
+ #[test]
+ fn test_map_non_nullable_value() {
+ let map = Field::new_map(
+ "map",
+ Field::MAP_ENTRIES_FIELD_DEFAULT_NAME,
+ Field::new(Field::MAP_KEY_FIELD_DEFAULT_NAME, DataType::Utf8,
false),
+ Field::new(Field::MAP_VALUE_FIELD_DEFAULT_NAME, DataType::Utf8,
false),
+ false,
+ false,
+ );
+ let schema = Arc::new(Schema::new(vec![map]));
+ let buf = r#"{"map": {"key": null}}"#;
+
+ let err = ReaderBuilder::new(schema)
+ .build(Cursor::new(buf.as_bytes()))
+ .unwrap()
+ .read()
+ .unwrap_err();
+
+ assert_eq!(
+ err.to_string(),
+ "Invalid argument error: Found unmasked nulls for non-nullable
StructArray field \"value\""
+ );
+ }
+
#[test]
fn test_not_coercing_primitive_into_string_without_flag() {
let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Utf8,
true)]));