From f0ebdbc0d1d18970fb2307e37783e94efd87c485 Mon Sep 17 00:00:00 2001 From: Mohammad Dashti Date: Thu, 13 Nov 2025 20:04:43 -0800 Subject: [PATCH 1/3] Fixed the deserialization issue --- src/aggregation/agg_req.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/aggregation/agg_req.rs b/src/aggregation/agg_req.rs index e5dfed85a5..306b73e589 100644 --- a/src/aggregation/agg_req.rs +++ b/src/aggregation/agg_req.rs @@ -78,9 +78,25 @@ impl TryFrom for Aggregation { fn try_from(value: AggregationForDeserialization) -> serde_json::Result { let AggregationForDeserialization { - aggs_remaining_json, - sub_aggregation, + mut aggs_remaining_json, + mut sub_aggregation, } = value; + + // Extract nested "aggs" from inside the aggregation variant + // E.g., {"terms": {"field": "x", "aggs": {...}}} -> extract "aggs" from inside "terms" + if let Some(obj) = aggs_remaining_json.as_object_mut() { + for (_, agg_config) in obj.iter_mut() { + if let Some(config_obj) = agg_config.as_object_mut() { + if let Some(nested_aggs) = config_obj.remove("aggs") { + // Parse nested aggs as Aggregations + if let Ok(nested) = serde_json::from_value::(nested_aggs) { + sub_aggregation = nested; + } + } + } + } + } + let agg: AggregationVariants = serde_json::from_value(aggs_remaining_json)?; Ok(Aggregation { agg, From 29a34f06bdaffc78be054a150e2d8794cef7936b Mon Sep 17 00:00:00 2001 From: Mohammad Dashti Date: Fri, 14 Nov 2025 08:24:30 -0800 Subject: [PATCH 2/3] Added tests. --- src/aggregation/agg_req.rs | 116 ++++++++++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 2 deletions(-) diff --git a/src/aggregation/agg_req.rs b/src/aggregation/agg_req.rs index 306b73e589..73d82f72d6 100644 --- a/src/aggregation/agg_req.rs +++ b/src/aggregation/agg_req.rs @@ -81,7 +81,7 @@ impl TryFrom for Aggregation { mut aggs_remaining_json, mut sub_aggregation, } = value; - + // Extract nested "aggs" from inside the aggregation variant // E.g., {"terms": {"field": "x", "aggs": {...}}} -> extract "aggs" from inside "terms" if let Some(obj) = aggs_remaining_json.as_object_mut() { @@ -96,7 +96,7 @@ impl TryFrom for Aggregation { } } } - + let agg: AggregationVariants = serde_json::from_value(aggs_remaining_json)?; Ok(Aggregation { agg, @@ -402,4 +402,116 @@ mod tests { .collect() ) } + + #[test] + fn test_nested_aggs_deserialization() { + // Test that nested "aggs" fields are properly deserialized + let json = serde_json::json!({ + "terms": { + "field": "category", + "aggs": { + "brand_breakdown": { + "terms": { + "field": "brand" + } + } + } + } + }); + + let agg: Aggregation = serde_json::from_value(json).unwrap(); + + // Verify the main aggregation was deserialized + assert!(matches!(agg.agg, AggregationVariants::Terms(_))); + if let AggregationVariants::Terms(terms) = &agg.agg { + assert_eq!(terms.field, "category"); + } + + // Verify nested aggregation was extracted + assert_eq!(agg.sub_aggregation.len(), 1); + assert!(agg.sub_aggregation.contains_key("brand_breakdown")); + + // Verify the nested aggregation structure + let brand_agg = agg.sub_aggregation.get("brand_breakdown").unwrap(); + assert!(matches!(brand_agg.agg, AggregationVariants::Terms(_))); + if let AggregationVariants::Terms(terms) = &brand_agg.agg { + assert_eq!(terms.field, "brand"); + } + } + + #[test] + fn test_triple_nested_aggs_deserialization() { + // Test triple-nested aggregations + let json = serde_json::json!({ + "terms": { + "field": "category", + "aggs": { + "brand_breakdown": { + "terms": { + "field": "brand", + "aggs": { + "rating_breakdown": { + "terms": { + "field": "rating" + } + } + } + } + } + } + } + }); + + let agg: Aggregation = serde_json::from_value(json).unwrap(); + + // Verify first level + assert_eq!(agg.sub_aggregation.len(), 1); + let brand_agg = agg.sub_aggregation.get("brand_breakdown").unwrap(); + + // Verify second level + assert_eq!(brand_agg.sub_aggregation.len(), 1); + let rating_agg = brand_agg.sub_aggregation.get("rating_breakdown").unwrap(); + + // Verify third level + assert!(matches!(rating_agg.agg, AggregationVariants::Terms(_))); + if let AggregationVariants::Terms(terms) = &rating_agg.agg { + assert_eq!(terms.field, "rating"); + } + } + + #[test] + fn test_nested_aggs_with_metrics() { + // Test nested aggregations with metric sub-aggregations + let json = serde_json::json!({ + "terms": { + "field": "category", + "aggs": { + "avg_price": { + "avg": { + "field": "price" + } + }, + "max_rating": { + "max": { + "field": "rating" + } + } + } + } + }); + + let agg: Aggregation = serde_json::from_value(json).unwrap(); + + // Verify nested aggregations were extracted + assert_eq!(agg.sub_aggregation.len(), 2); + assert!(agg.sub_aggregation.contains_key("avg_price")); + assert!(agg.sub_aggregation.contains_key("max_rating")); + + // Verify the metric types + let avg_agg = agg.sub_aggregation.get("avg_price").unwrap(); + assert!(matches!(avg_agg.agg, AggregationVariants::Average(_))); + + let max_agg = agg.sub_aggregation.get("max_rating").unwrap(); + assert!(matches!(max_agg.agg, AggregationVariants::Max(_))); + } } From 6daee3317408a157f393e84c514ed4afe7e8b418 Mon Sep 17 00:00:00 2001 From: Mohammad Dashti Date: Mon, 17 Nov 2025 11:05:30 -0800 Subject: [PATCH 3/3] improved code. --- src/aggregation/agg_req.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/aggregation/agg_req.rs b/src/aggregation/agg_req.rs index 73d82f72d6..5f28a736ff 100644 --- a/src/aggregation/agg_req.rs +++ b/src/aggregation/agg_req.rs @@ -30,6 +30,7 @@ use std::collections::HashSet; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; +use serde_json::Value; use super::bucket::{ DateHistogramAggregationReq, HistogramAggregation, RangeAggregation, TermsAggregation, @@ -85,15 +86,16 @@ impl TryFrom for Aggregation { // Extract nested "aggs" from inside the aggregation variant // E.g., {"terms": {"field": "x", "aggs": {...}}} -> extract "aggs" from inside "terms" if let Some(obj) = aggs_remaining_json.as_object_mut() { - for (_, agg_config) in obj.iter_mut() { - if let Some(config_obj) = agg_config.as_object_mut() { - if let Some(nested_aggs) = config_obj.remove("aggs") { - // Parse nested aggs as Aggregations - if let Ok(nested) = serde_json::from_value::(nested_aggs) { - sub_aggregation = nested; - } - } - } + if let Some(nested) = + obj.values_mut() + .filter_map(Value::as_object_mut) + .find_map(|config| { + config + .remove("aggs") + .and_then(|nested| serde_json::from_value::(nested).ok()) + }) + { + sub_aggregation = nested; } }