|
1 | 1 | use crate::parser::errors::JsonPathError;
|
2 | 2 | use crate::parser::model::{JpQuery, Segment, Selector};
|
3 | 3 | use crate::parser::{parse_json_path, Parsed};
|
4 |
| -use crate::query::QueryPath; |
| 4 | +use crate::query::{Queried, QueryPath}; |
| 5 | +use crate::JsonPath; |
5 | 6 | use serde_json::Value;
|
6 | 7 | use std::borrow::Cow;
|
7 | 8 | use std::fmt::Debug;
|
@@ -138,6 +139,39 @@ where
|
138 | 139 | {
|
139 | 140 | None
|
140 | 141 | }
|
| 142 | + |
| 143 | + /// Deletes all elements matching the given JSONPath |
| 144 | + /// |
| 145 | + /// # Arguments |
| 146 | + /// * `path` - JSONPath string specifying elements to delete |
| 147 | + /// |
| 148 | + /// # Returns |
| 149 | + /// * `Ok(usize)` - Number of elements deleted |
| 150 | + /// * `Err(JsonPathError)` - If the path is invalid or deletion fails |
| 151 | + /// |
| 152 | + /// # Examples |
| 153 | + /// ``` |
| 154 | + /// use serde_json::json; |
| 155 | + /// use jsonpath_rust::JsonPath; |
| 156 | + /// use crate::jsonpath_rust::query::queryable::Queryable; |
| 157 | + /// |
| 158 | + /// let mut data = json!({ |
| 159 | + /// "users": [ |
| 160 | + /// {"name": "Alice", "age": 30}, |
| 161 | + /// {"name": "Bob", "age": 25}, |
| 162 | + /// {"name": "Charlie", "age": 35} |
| 163 | + /// ] |
| 164 | + /// }); |
| 165 | + /// |
| 166 | + /// // Delete users older than 30 |
| 167 | + /// let deleted = data.delete_by_path("$.users[?(@.age > 30)]").unwrap(); |
| 168 | + /// assert_eq!(deleted, 1); |
| 169 | + /// ``` |
| 170 | + fn delete_by_path(&mut self, _path: &str) -> Queried<usize> { |
| 171 | + Err(JsonPathError::InvalidJsonPath( |
| 172 | + "Deletion not supported".to_string(), |
| 173 | + )) |
| 174 | + } |
141 | 175 | }
|
142 | 176 |
|
143 | 177 | impl Queryable for Value {
|
@@ -279,6 +313,168 @@ impl Queryable for Value {
|
279 | 313 | .ok()
|
280 | 314 | .and_then(|p| self.pointer_mut(p.as_str()))
|
281 | 315 | }
|
| 316 | + |
| 317 | + fn delete_by_path(&mut self, path: &str) -> Queried<usize> { |
| 318 | + let mut deletions = Vec::new(); |
| 319 | + for query_path in &self.query_only_path(path)? { |
| 320 | + if let Some(deletion_info) = parse_deletion_path(query_path)? { |
| 321 | + deletions.push(deletion_info); |
| 322 | + } |
| 323 | + } |
| 324 | + |
| 325 | + // Sort deletions to handle array indices correctly (delete from end to start) |
| 326 | + deletions.sort_by(|a, b| { |
| 327 | + b.path_depth() |
| 328 | + .cmp(&a.path_depth()) |
| 329 | + .then_with(|| match (a, b) { |
| 330 | + ( |
| 331 | + DeletionInfo::ArrayIndex { index: idx_a, .. }, |
| 332 | + DeletionInfo::ArrayIndex { index: idx_b, .. }, |
| 333 | + ) => idx_b.cmp(idx_a), |
| 334 | + _ => std::cmp::Ordering::Equal, |
| 335 | + }) |
| 336 | + }); |
| 337 | + |
| 338 | + // Perform deletions |
| 339 | + let deleted_count = deletions.iter().try_fold(0, |c, d| { |
| 340 | + execute_deletion(self, d).map(|deleted| if deleted { c + 1 } else { c }) |
| 341 | + })?; |
| 342 | + |
| 343 | + Ok(deleted_count) |
| 344 | + } |
| 345 | +} |
| 346 | + |
| 347 | +#[derive(Debug, Clone)] |
| 348 | +enum DeletionInfo { |
| 349 | + ObjectField { |
| 350 | + parent_path: String, |
| 351 | + field_name: String, |
| 352 | + }, |
| 353 | + ArrayIndex { |
| 354 | + parent_path: String, |
| 355 | + index: usize, |
| 356 | + }, |
| 357 | + Root, |
| 358 | +} |
| 359 | + |
| 360 | +impl DeletionInfo { |
| 361 | + fn path_depth(&self) -> usize { |
| 362 | + match self { |
| 363 | + DeletionInfo::Root => 0, |
| 364 | + DeletionInfo::ObjectField { parent_path, .. } |
| 365 | + | DeletionInfo::ArrayIndex { parent_path, .. } => parent_path.matches('/').count(), |
| 366 | + } |
| 367 | + } |
| 368 | +} |
| 369 | + |
| 370 | +fn parse_deletion_path(query_path: &str) -> Result<Option<DeletionInfo>, JsonPathError> { |
| 371 | + if query_path == "$" { |
| 372 | + return Ok(Some(DeletionInfo::Root)); |
| 373 | + } |
| 374 | + |
| 375 | + let JpQuery { segments } = parse_json_path(query_path)?; |
| 376 | + |
| 377 | + if segments.is_empty() { |
| 378 | + return Ok(None); |
| 379 | + } |
| 380 | + |
| 381 | + let mut parent_path = String::new(); |
| 382 | + let mut segments_iter = segments.iter().peekable(); |
| 383 | + |
| 384 | + while let Some(segment) = segments_iter.next() { |
| 385 | + if segments_iter.peek().is_some() { |
| 386 | + // Not the last segment, add to parent path |
| 387 | + match segment { |
| 388 | + Segment::Selector(Selector::Name(name)) => { |
| 389 | + parent_path.push_str(&format!("/{}", name.trim_matches(|c| c == '\''))); |
| 390 | + } |
| 391 | + Segment::Selector(Selector::Index(index)) => { |
| 392 | + parent_path.push_str(&format!("/{}", index)); |
| 393 | + } |
| 394 | + e => { |
| 395 | + return Err(JsonPathError::InvalidJsonPath(format!( |
| 396 | + "Unsupported segment to be deleted: {:?}", |
| 397 | + e |
| 398 | + ))); |
| 399 | + } |
| 400 | + } |
| 401 | + } else { |
| 402 | + match segment { |
| 403 | + Segment::Selector(Selector::Name(name)) => { |
| 404 | + let field_name = name.trim_matches(|c| c == '\'').to_string(); |
| 405 | + return Ok(Some(DeletionInfo::ObjectField { |
| 406 | + parent_path, |
| 407 | + field_name, |
| 408 | + })); |
| 409 | + } |
| 410 | + Segment::Selector(Selector::Index(index)) => { |
| 411 | + return Ok(Some(DeletionInfo::ArrayIndex { |
| 412 | + parent_path, |
| 413 | + index: *index as usize, |
| 414 | + })); |
| 415 | + } |
| 416 | + e => { |
| 417 | + return Err(JsonPathError::InvalidJsonPath(format!( |
| 418 | + "Unsupported segment to be deleted: {:?}", |
| 419 | + e |
| 420 | + ))); |
| 421 | + } |
| 422 | + } |
| 423 | + } |
| 424 | + } |
| 425 | + |
| 426 | + Ok(None) |
| 427 | +} |
| 428 | + |
| 429 | +fn execute_deletion(value: &mut Value, deletion: &DeletionInfo) -> Queried<bool> { |
| 430 | + match deletion { |
| 431 | + DeletionInfo::Root => { |
| 432 | + *value = Value::Null; |
| 433 | + Ok(true) |
| 434 | + } |
| 435 | + DeletionInfo::ObjectField { |
| 436 | + parent_path, |
| 437 | + field_name, |
| 438 | + } => { |
| 439 | + let parent = if parent_path.is_empty() { |
| 440 | + value |
| 441 | + } else { |
| 442 | + value.pointer_mut(parent_path).ok_or_else(|| { |
| 443 | + JsonPathError::InvalidJsonPath("Parent path not found".to_string()) |
| 444 | + })? |
| 445 | + }; |
| 446 | + |
| 447 | + if let Some(obj) = parent.as_object_mut() { |
| 448 | + Ok(obj.remove(field_name).is_some()) |
| 449 | + } else { |
| 450 | + Err(JsonPathError::InvalidJsonPath( |
| 451 | + "Parent is not an object".to_string(), |
| 452 | + )) |
| 453 | + } |
| 454 | + } |
| 455 | + DeletionInfo::ArrayIndex { parent_path, index } => { |
| 456 | + let parent = if parent_path.is_empty() { |
| 457 | + value |
| 458 | + } else { |
| 459 | + value.pointer_mut(parent_path).ok_or_else(|| { |
| 460 | + JsonPathError::InvalidJsonPath("Parent path not found".to_string()) |
| 461 | + })? |
| 462 | + }; |
| 463 | + |
| 464 | + if let Some(arr) = parent.as_array_mut() { |
| 465 | + if *index < arr.len() { |
| 466 | + arr.remove(*index); |
| 467 | + Ok(true) |
| 468 | + } else { |
| 469 | + Ok(false) // Index out of bounds |
| 470 | + } |
| 471 | + } else { |
| 472 | + Err(JsonPathError::InvalidJsonPath( |
| 473 | + "Parent is not an array".to_string(), |
| 474 | + )) |
| 475 | + } |
| 476 | + } |
| 477 | + } |
282 | 478 | }
|
283 | 479 |
|
284 | 480 | fn convert_js_path(path: &str) -> Parsed<String> {
|
@@ -310,7 +506,7 @@ mod tests {
|
310 | 506 | use crate::query::queryable::{convert_js_path, Queryable};
|
311 | 507 | use crate::query::Queried;
|
312 | 508 | use crate::JsonPath;
|
313 |
| - use serde_json::json; |
| 509 | + use serde_json::{json, Value}; |
314 | 510 |
|
315 | 511 | #[test]
|
316 | 512 | fn in_smoke() -> Queried<()> {
|
@@ -446,4 +642,116 @@ mod tests {
|
446 | 642 |
|
447 | 643 | Ok(())
|
448 | 644 | }
|
| 645 | + #[test] |
| 646 | + fn test_delete_object_field() { |
| 647 | + let mut data = json!({ |
| 648 | + "users": { |
| 649 | + "alice": {"age": 30}, |
| 650 | + "bob": {"age": 25} |
| 651 | + } |
| 652 | + }); |
| 653 | + |
| 654 | + let deleted = data.delete_by_path("$.users.alice").unwrap(); |
| 655 | + assert_eq!(deleted, 1); |
| 656 | + |
| 657 | + let expected = json!({ |
| 658 | + "users": { |
| 659 | + "bob": {"age": 25} |
| 660 | + } |
| 661 | + }); |
| 662 | + assert_eq!(data, expected); |
| 663 | + } |
| 664 | + |
| 665 | + #[test] |
| 666 | + fn test_delete_array_element() { |
| 667 | + let mut data = json!({ |
| 668 | + "numbers": [1, 2, 3, 4, 5] |
| 669 | + }); |
| 670 | + |
| 671 | + let deleted = data.delete_by_path("$.numbers[2]").unwrap(); |
| 672 | + assert_eq!(deleted, 1); |
| 673 | + |
| 674 | + let expected = json!({ |
| 675 | + "numbers": [1, 2, 4, 5] |
| 676 | + }); |
| 677 | + assert_eq!(data, expected); |
| 678 | + } |
| 679 | + |
| 680 | + #[test] |
| 681 | + fn test_delete_multiple_elements() { |
| 682 | + let mut data = json!({ |
| 683 | + "users": [ |
| 684 | + {"name": "Alice", "age": 30}, |
| 685 | + {"name": "Bob", "age": 25}, |
| 686 | + {"name": "Charlie", "age": 35}, |
| 687 | + {"name": "David", "age": 22} |
| 688 | + ] |
| 689 | + }); |
| 690 | + |
| 691 | + // Delete users older than 24 |
| 692 | + let deleted = data.delete_by_path("$.users[?(@.age > 24)]").unwrap(); |
| 693 | + assert_eq!(deleted, 3); |
| 694 | + |
| 695 | + let expected = json!({ |
| 696 | + "users": [ |
| 697 | + {"name": "David", "age": 22} |
| 698 | + ] |
| 699 | + }); |
| 700 | + assert_eq!(data, expected); |
| 701 | + } |
| 702 | + |
| 703 | + #[test] |
| 704 | + fn test_delete_nested_fields() { |
| 705 | + let mut data = json!({ |
| 706 | + "company": { |
| 707 | + "departments": { |
| 708 | + "engineering": {"budget": 100000}, |
| 709 | + "marketing": {"budget": 50000}, |
| 710 | + "hr": {"budget": 30000} |
| 711 | + } |
| 712 | + } |
| 713 | + }); |
| 714 | + |
| 715 | + let deleted = data |
| 716 | + .delete_by_path("$.company.departments.marketing") |
| 717 | + .unwrap(); |
| 718 | + assert_eq!(deleted, 1); |
| 719 | + |
| 720 | + let expected = json!({ |
| 721 | + "company": { |
| 722 | + "departments": { |
| 723 | + "engineering": {"budget": 100000}, |
| 724 | + "hr": {"budget": 30000} |
| 725 | + } |
| 726 | + } |
| 727 | + }); |
| 728 | + assert_eq!(data, expected); |
| 729 | + } |
| 730 | + |
| 731 | + #[test] |
| 732 | + fn test_delete_nonexistent_path() { |
| 733 | + let mut data = json!({ |
| 734 | + "test": "value" |
| 735 | + }); |
| 736 | + |
| 737 | + let deleted = data.delete_by_path("$.nonexistent").unwrap(); |
| 738 | + assert_eq!(deleted, 0); |
| 739 | + |
| 740 | + // Data should remain unchanged |
| 741 | + let expected = json!({ |
| 742 | + "test": "value" |
| 743 | + }); |
| 744 | + assert_eq!(data, expected); |
| 745 | + } |
| 746 | + |
| 747 | + #[test] |
| 748 | + fn test_delete_root() { |
| 749 | + let mut data = json!({ |
| 750 | + "test": "value" |
| 751 | + }); |
| 752 | + |
| 753 | + let deleted = data.delete_by_path("$").unwrap(); |
| 754 | + assert_eq!(deleted, 1); |
| 755 | + assert_eq!(data, Value::Null); |
| 756 | + } |
449 | 757 | }
|
0 commit comments