From fcd4b2d55ebca03767adf57976c9daf4ed38adc9 Mon Sep 17 00:00:00 2001 From: "cemre.mengu" Date: Sun, 30 Aug 2026 21:13:25 +0300 Subject: [PATCH] ignore null objects --- .../src/doc_mapper/doc_mapper_impl.rs | 111 ++++++++++++++++++ .../src/doc_mapper/mapping_tree.rs | 6 + 2 files changed, 117 insertions(+) diff --git a/quickwit/quickwit-doc-mapper/src/doc_mapper/doc_mapper_impl.rs b/quickwit/quickwit-doc-mapper/src/doc_mapper/doc_mapper_impl.rs index 087fe2839ab..d6a5f8a7f39 100644 --- a/quickwit/quickwit-doc-mapper/src/doc_mapper/doc_mapper_impl.rs +++ b/quickwit/quickwit-doc-mapper/src/doc_mapper/doc_mapper_impl.rs @@ -899,6 +899,117 @@ mod tests { Ok(()) } + #[test] + fn test_parse_document_with_null_object() { + use serde::Deserialize; + + let doc_mapper_json = json!({ + "field_mappings": [ + { + "name": "payload", + "type": "object", + "field_mappings": [ + { + "name": "head", + "type": "object", + "field_mappings": [ + {"name": "label", "type": "text"}, + { + "name": "repo", + "type": "object", + "field_mappings": [ + {"name": "name", "type": "text"} + ] + } + ] + } + ] + } + ] + }); + let doc_mapper = DocMapperBuilder::deserialize(doc_mapper_json) + .unwrap() + .try_build() + .unwrap(); + + // A `null` object is simply ignored, like a `null` leaf value. + let JsonValue::Object(json_obj) = json!({ + "payload": { + "head": { + "label": "main", + "repo": JsonValue::Null + } + } + }) else { + panic!("expected a JSON object"); + }; + let (_, document) = doc_mapper.doc_from_json_obj(json_obj, 0).unwrap(); + let schema = doc_mapper.schema(); + let label_field = schema.get_field("payload.head.label").unwrap(); + assert_eq!( + document + .get_first(label_field) + .and_then(|value| value.as_str()), + Some("main") + ); + + // The whole document can also be `null` all the way up. + let JsonValue::Object(json_obj) = json!({"payload": JsonValue::Null}) else { + panic!("expected a JSON object"); + }; + doc_mapper.doc_from_json_obj(json_obj, 0).unwrap(); + + // Non-null, non-object values are still rejected. + let JsonValue::Object(json_obj) = json!({"payload": {"head": {"repo": "quickwit"}}}) else { + panic!("expected a JSON object"); + }; + let error = doc_mapper.doc_from_json_obj(json_obj, 0).unwrap_err(); + assert!(matches!(error, DocParsingError::ValueError(field_path, _) + if field_path == "payload.head.repo")); + } + + #[test] + fn test_validate_document_with_null_object() { + use serde::Deserialize; + + let doc_mapper_json = json!({ + "mode": "strict", + "field_mappings": [ + { + "name": "payload", + "type": "object", + "field_mappings": [ + { + "name": "repo", + "type": "object", + "field_mappings": [ + {"name": "name", "type": "text"} + ] + } + ] + } + ] + }); + let doc_mapper = DocMapperBuilder::deserialize(doc_mapper_json) + .unwrap() + .try_build() + .unwrap(); + + let json_val: serde_json_borrow::Value = + serde_json::from_str(r#"{"payload": {"repo": null}}"#).unwrap(); + doc_mapper + .validate_json_obj(json_val.as_object().unwrap()) + .unwrap(); + + let json_val: serde_json_borrow::Value = + serde_json::from_str(r#"{"payload": {"repo": "quickwit"}}"#).unwrap(); + let error = doc_mapper + .validate_json_obj(json_val.as_object().unwrap()) + .unwrap_err(); + assert!(matches!(error, DocParsingError::ValueError(field_path, _) + if field_path == "payload.repo")); + } + #[test] fn test_timestamp_field_in_object_is_valid() { serde_json::from_str::( diff --git a/quickwit/quickwit-doc-mapper/src/doc_mapper/mapping_tree.rs b/quickwit/quickwit-doc-mapper/src/doc_mapper/mapping_tree.rs index 30b16fea4c8..f08c5c7a476 100644 --- a/quickwit/quickwit-doc-mapper/src/doc_mapper/mapping_tree.rs +++ b/quickwit/quickwit-doc-mapper/src/doc_mapper/mapping_tree.rs @@ -945,6 +945,9 @@ impl MappingTree { mapping_leaf.validate_from_json(json_value, field_path) } MappingTree::Node(mapping_node) => { + if json_value.is_null() { + return Ok(()); + } if let Some(json_obj) = json_value.as_object() { mapping_node.validate_from_json(json_obj, strict_mode, field_path) } else { @@ -970,6 +973,9 @@ impl MappingTree { mapping_leaf.doc_from_json(json_value, document, path) } MappingTree::Node(mapping_node) => { + if json_value.is_null() { + return Ok(()); + } if let JsonValue::Object(json_obj) = json_value { mapping_node.doc_from_json(json_obj, mode, document, path, dynamic_json_obj) } else {