andygrove commented on code in PR #4907:
URL: https://github.com/apache/datafusion-comet/pull/4907#discussion_r3591172953
##########
native/spark-expr/src/string_funcs/get_json_object.rs:
##########
@@ -268,38 +269,151 @@ fn evaluate_path(json_str: &str, path: &ParsedPath) ->
Option<String> {
1 => {
// Single wildcard match: Spark preserves JSON serialization format
// (strings keep their quotes, numbers don't)
- let s = serde_json::to_string(results[0]).ok()?;
- if s == "null" {
+ if results[0].is_null() {
None
} else {
- Some(s)
+ serde_json::to_string(results[0]).ok()
}
}
- _ => {
- // Multiple results: wrap in JSON array
- let arr = Value::Array(results.into_iter().cloned().collect());
- Some(arr.to_string())
+ // Multiple results: wrap in JSON array. A slice of `&Value` serializes
+ // as a JSON array, so no clone into an owned `Value::Array` is needed.
+ _ => serde_json::to_string(&results).ok(),
+ }
+}
+
+/// Evaluation for paths without wildcards.
+///
+/// Descends into the document while it is being parsed, so only the matched
+/// subtree is materialized as a `Value`; everything else is skipped by the
+/// parser without allocating. The whole document is still consumed, so
+/// malformed JSON anywhere in the input yields no match, as a full parse
would.
+fn extract_no_wildcard(json_str: &str, segments: &[PathSegment]) ->
Option<Value> {
+ let mut de = serde_json::Deserializer::from_str(json_str);
+ let found = PathSeed { segments }.deserialize(&mut de).ok()?;
+ de.end().ok()?;
+ found
+}
+
+/// Deserializes the value at `segments`, discarding everything else.
+struct PathSeed<'a> {
+ segments: &'a [PathSegment],
+}
+
+impl<'de> DeserializeSeed<'de> for PathSeed<'_> {
+ type Value = Option<Value>;
+
+ fn deserialize<D: Deserializer<'de>>(self, deserializer: D) ->
Result<Self::Value, D::Error> {
+ if self.segments.is_empty() {
+ return Value::deserialize(deserializer).map(Some);
}
+ deserializer.deserialize_any(SegmentVisitor {
+ segments: self.segments,
+ })
}
}
-/// Fast-path evaluation for paths without wildcards.
-/// Returns a reference to the matched value, or None if no match.
-fn evaluate_no_wildcard<'a>(value: &'a Value, segments: &[PathSegment]) ->
Option<&'a Value> {
- if segments.is_empty() {
- return Some(value);
+/// Applies `segments[0]` to the value being visited. A value whose shape does
+/// not match the segment (an index into an object, say) is skipped and
reported
+/// as no match rather than as an error, matching a lookup on a parsed
document.
+struct SegmentVisitor<'a> {
+ segments: &'a [PathSegment],
+}
+
+impl<'de> Visitor<'de> for SegmentVisitor<'_> {
+ type Value = Option<Value>;
+
+ fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str("a JSON value")
}
- match &segments[0] {
- PathSegment::Field(name) => {
- let child = value.as_object()?.get(name)?;
- evaluate_no_wildcard(child, &segments[1..])
+ fn visit_bool<E>(self, _: bool) -> Result<Self::Value, E> {
+ Ok(None)
+ }
+
+ fn visit_i64<E>(self, _: i64) -> Result<Self::Value, E> {
+ Ok(None)
+ }
+
+ fn visit_u64<E>(self, _: u64) -> Result<Self::Value, E> {
+ Ok(None)
+ }
+
+ fn visit_f64<E>(self, _: f64) -> Result<Self::Value, E> {
+ Ok(None)
+ }
+
+ fn visit_str<E>(self, _: &str) -> Result<Self::Value, E> {
+ Ok(None)
+ }
+
+ fn visit_unit<E>(self) -> Result<Self::Value, E> {
+ Ok(None)
+ }
+
+ fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value,
A::Error> {
+ let PathSegment::Field(name) = &self.segments[0] else {
+ IgnoredAny.visit_map(map)?;
+ return Ok(None);
+ };
+
+ let mut found = None;
+ // Every entry is visited so that a duplicated key resolves to its last
+ // occurrence, as it would in a parsed object.
Review Comment:
I filed https://github.com/apache/datafusion-comet/issues/4947
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]