@@ -2,6 +2,7 @@ use crate::parser::errors::JsonPathError;
22use crate :: parser:: model:: { JpQuery , Segment , Selector } ;
33use crate :: parser:: { parse_json_path, Parsed } ;
44use crate :: query:: QueryPath ;
5+ use crate :: JsonPath ;
56use serde_json:: Value ;
67use std:: borrow:: Cow ;
78use std:: fmt:: Debug ;
@@ -304,13 +305,233 @@ fn convert_js_path(path: &str) -> Parsed<String> {
304305 Ok ( path)
305306}
306307
308+ pub trait QueryableDeletable : Queryable {
309+ /// Deletes all elements matching the given JSONPath
310+ ///
311+ /// # Arguments
312+ /// * `path` - JSONPath string specifying elements to delete
313+ ///
314+ /// # Returns
315+ /// * `Ok(usize)` - Number of elements deleted
316+ /// * `Err(JsonPathError)` - If the path is invalid or deletion fails
317+ ///
318+ /// # Examples
319+ /// ```
320+ /// use serde_json::json;
321+ /// use crate::jsonpath_rust::query::queryable::QueryableDeletable;
322+ /// use jsonpath_rust::JsonPath;
323+ ///
324+ /// let mut data = json!({
325+ /// "users": [
326+ /// {"name": "Alice", "age": 30},
327+ /// {"name": "Bob", "age": 25},
328+ /// {"name": "Charlie", "age": 35}
329+ /// ]
330+ /// });
331+ ///
332+ /// // Delete users older than 30
333+ /// let deleted = data.delete_by_path("$.users[?(@.age > 30)]").unwrap();
334+ /// assert_eq!(deleted, 1);
335+ /// ```
336+ fn delete_by_path ( & mut self , path : & str ) -> Result < usize , JsonPathError > ;
337+
338+ /// Deletes a single element at the given path
339+ /// Returns true if an element was deleted, false otherwise
340+ fn delete_single ( & mut self , path : & str ) -> Result < bool , JsonPathError > ;
341+ }
342+
343+ impl QueryableDeletable for Value {
344+ fn delete_by_path ( & mut self , path : & str ) -> Result < usize , JsonPathError > {
345+
346+ let matching_paths = self . query_only_path ( path)
347+ . map_err ( |_| JsonPathError :: InvalidJsonPath ( "Failed to query path" . to_string ( ) ) ) ?;
348+
349+ if matching_paths. is_empty ( ) {
350+ return Ok ( 0 ) ;
351+ }
352+
353+ let mut deletions = Vec :: new ( ) ;
354+ for query_path in & matching_paths {
355+ if let Some ( deletion_info) = parse_deletion_path ( query_path) ? {
356+ deletions. push ( deletion_info) ;
357+ }
358+ }
359+
360+ // Sort deletions to handle array indices correctly (delete from end to start)
361+ deletions. sort_by ( |a, b| {
362+ // First sort by path depth (deeper paths first)
363+ let depth_cmp = b. path_depth ( ) . cmp ( & a. path_depth ( ) ) ;
364+ if depth_cmp != std:: cmp:: Ordering :: Equal {
365+ return depth_cmp;
366+ }
367+
368+ // Then by array index (higher indices first)
369+ match ( a, b) {
370+ ( DeletionInfo :: ArrayIndex { index : idx_a, .. } ,
371+ DeletionInfo :: ArrayIndex { index : idx_b, .. } ) => {
372+ idx_b. cmp ( idx_a)
373+ }
374+ _ => std:: cmp:: Ordering :: Equal
375+ }
376+ } ) ;
377+
378+ // Perform deletions
379+ let mut deleted_count = 0 ;
380+ for deletion in deletions {
381+ if execute_deletion ( self , & deletion) ? {
382+ deleted_count += 1 ;
383+ }
384+ }
385+
386+ Ok ( deleted_count)
387+ }
388+
389+ fn delete_single ( & mut self , path : & str ) -> Result < bool , JsonPathError > {
390+ if let Some ( deletion_info) = parse_deletion_path ( path) ? {
391+ execute_deletion ( self , & deletion_info)
392+ } else {
393+ Ok ( false )
394+ }
395+ }
396+ }
397+
398+ #[ derive( Debug , Clone ) ]
399+ enum DeletionInfo {
400+ ObjectField {
401+ parent_path : String ,
402+ field_name : String ,
403+ } ,
404+ ArrayIndex {
405+ parent_path : String ,
406+ index : usize ,
407+ } ,
408+ Root ,
409+ }
410+
411+ impl DeletionInfo {
412+ fn path_depth ( & self ) -> usize {
413+ match self {
414+ DeletionInfo :: Root => 0 ,
415+ DeletionInfo :: ObjectField { parent_path, .. } |
416+ DeletionInfo :: ArrayIndex { parent_path, .. } => {
417+ parent_path. matches ( '/' ) . count ( )
418+ }
419+ }
420+ }
421+ }
422+
423+ fn parse_deletion_path ( query_path : & str ) -> Result < Option < DeletionInfo > , JsonPathError > {
424+ if query_path == "$" {
425+ return Ok ( Some ( DeletionInfo :: Root ) ) ;
426+ }
427+
428+ let JpQuery { segments } = parse_json_path ( query_path) ?;
429+
430+ if segments. is_empty ( ) {
431+ return Ok ( None ) ;
432+ }
433+
434+ let mut parent_path = String :: new ( ) ;
435+ let mut segments_iter = segments. iter ( ) . peekable ( ) ;
436+
437+ while let Some ( segment) = segments_iter. next ( ) {
438+ if segments_iter. peek ( ) . is_some ( ) {
439+ // Not the last segment, add to parent path
440+ match segment {
441+ Segment :: Selector ( Selector :: Name ( name) ) => {
442+ parent_path. push_str ( & format ! ( "/{}" , name. trim_matches( |c| c == '\'' ) ) ) ;
443+ }
444+ Segment :: Selector ( Selector :: Index ( index) ) => {
445+ parent_path. push_str ( & format ! ( "/{}" , index) ) ;
446+ }
447+ _ => {
448+ return Err ( JsonPathError :: InvalidJsonPath (
449+ "Unsupported segment type for deletion" . to_string ( )
450+ ) ) ;
451+ }
452+ }
453+ } else {
454+ match segment {
455+ Segment :: Selector ( Selector :: Name ( name) ) => {
456+ let field_name = name. trim_matches ( |c| c == '\'' ) . to_string ( ) ;
457+ return Ok ( Some ( DeletionInfo :: ObjectField {
458+ parent_path,
459+ field_name,
460+ } ) ) ;
461+ }
462+ Segment :: Selector ( Selector :: Index ( index) ) => {
463+ return Ok ( Some ( DeletionInfo :: ArrayIndex {
464+ parent_path,
465+ index : * index as usize ,
466+ } ) ) ;
467+ }
468+ _ => {
469+ return Err ( JsonPathError :: InvalidJsonPath (
470+ "Unsupported final segment for deletion" . to_string ( )
471+ ) ) ;
472+ }
473+ }
474+ }
475+ }
476+
477+ Ok ( None )
478+ }
479+
480+ fn execute_deletion ( value : & mut Value , deletion : & DeletionInfo ) -> Result < bool , JsonPathError > {
481+ match deletion {
482+ DeletionInfo :: Root => {
483+ * value = Value :: Null ;
484+ Ok ( true )
485+ }
486+ DeletionInfo :: ObjectField { parent_path, field_name } => {
487+ let parent = if parent_path. is_empty ( ) {
488+ value
489+ } else {
490+ value. pointer_mut ( parent_path) . ok_or_else ( || {
491+ JsonPathError :: InvalidJsonPath ( "Parent path not found" . to_string ( ) )
492+ } ) ?
493+ } ;
494+
495+ if let Some ( obj) = parent. as_object_mut ( ) {
496+ Ok ( obj. remove ( field_name) . is_some ( ) )
497+ } else {
498+ Err ( JsonPathError :: InvalidJsonPath (
499+ "Parent is not an object" . to_string ( )
500+ ) )
501+ }
502+ }
503+ DeletionInfo :: ArrayIndex { parent_path, index } => {
504+ let parent = if parent_path. is_empty ( ) {
505+ value
506+ } else {
507+ value. pointer_mut ( parent_path) . ok_or_else ( || {
508+ JsonPathError :: InvalidJsonPath ( "Parent path not found" . to_string ( ) )
509+ } ) ?
510+ } ;
511+
512+ if let Some ( arr) = parent. as_array_mut ( ) {
513+ if * index < arr. len ( ) {
514+ arr. remove ( * index) ;
515+ Ok ( true )
516+ } else {
517+ Ok ( false ) // Index out of bounds
518+ }
519+ } else {
520+ Err ( JsonPathError :: InvalidJsonPath (
521+ "Parent is not an array" . to_string ( )
522+ ) )
523+ }
524+ }
525+ }
526+ }
527+
307528#[ cfg( test) ]
308529mod tests {
309530 use crate :: parser:: Parsed ;
310- use crate :: query:: queryable:: { convert_js_path, Queryable } ;
531+ use crate :: query:: queryable:: { convert_js_path, Queryable , QueryableDeletable } ;
311532 use crate :: query:: Queried ;
312533 use crate :: JsonPath ;
313- use serde_json:: json;
534+ use serde_json:: { json, Value } ;
314535
315536 #[ test]
316537 fn in_smoke ( ) -> Queried < ( ) > {
@@ -446,4 +667,114 @@ mod tests {
446667
447668 Ok ( ( ) )
448669 }
670+ #[ test]
671+ fn test_delete_object_field ( ) {
672+ let mut data = json ! ( {
673+ "users" : {
674+ "alice" : { "age" : 30 } ,
675+ "bob" : { "age" : 25 }
676+ }
677+ } ) ;
678+
679+ let deleted = data. delete_by_path ( "$.users.alice" ) . unwrap ( ) ;
680+ assert_eq ! ( deleted, 1 ) ;
681+
682+ let expected = json ! ( {
683+ "users" : {
684+ "bob" : { "age" : 25 }
685+ }
686+ } ) ;
687+ assert_eq ! ( data, expected) ;
688+ }
689+
690+ #[ test]
691+ fn test_delete_array_element ( ) {
692+ let mut data = json ! ( {
693+ "numbers" : [ 1 , 2 , 3 , 4 , 5 ]
694+ } ) ;
695+
696+ let deleted = data. delete_by_path ( "$.numbers[2]" ) . unwrap ( ) ;
697+ assert_eq ! ( deleted, 1 ) ;
698+
699+ let expected = json ! ( {
700+ "numbers" : [ 1 , 2 , 4 , 5 ]
701+ } ) ;
702+ assert_eq ! ( data, expected) ;
703+ }
704+
705+ #[ test]
706+ fn test_delete_multiple_elements ( ) {
707+ let mut data = json ! ( {
708+ "users" : [
709+ { "name" : "Alice" , "age" : 30 } ,
710+ { "name" : "Bob" , "age" : 25 } ,
711+ { "name" : "Charlie" , "age" : 35 } ,
712+ { "name" : "David" , "age" : 22 }
713+ ]
714+ } ) ;
715+
716+ // Delete users older than 24
717+ let deleted = data. delete_by_path ( "$.users[?(@.age > 24)]" ) . unwrap ( ) ;
718+ assert_eq ! ( deleted, 3 ) ;
719+
720+ let expected = json ! ( {
721+ "users" : [
722+ { "name" : "David" , "age" : 22 }
723+ ]
724+ } ) ;
725+ assert_eq ! ( data, expected) ;
726+ }
727+
728+ #[ test]
729+ fn test_delete_nested_fields ( ) {
730+ let mut data = json ! ( {
731+ "company" : {
732+ "departments" : {
733+ "engineering" : { "budget" : 100000 } ,
734+ "marketing" : { "budget" : 50000 } ,
735+ "hr" : { "budget" : 30000 }
736+ }
737+ }
738+ } ) ;
739+
740+ let deleted = data. delete_by_path ( "$.company.departments.marketing" ) . unwrap ( ) ;
741+ assert_eq ! ( deleted, 1 ) ;
742+
743+ let expected = json ! ( {
744+ "company" : {
745+ "departments" : {
746+ "engineering" : { "budget" : 100000 } ,
747+ "hr" : { "budget" : 30000 }
748+ }
749+ }
750+ } ) ;
751+ assert_eq ! ( data, expected) ;
752+ }
753+
754+ #[ test]
755+ fn test_delete_nonexistent_path ( ) {
756+ let mut data = json ! ( {
757+ "test" : "value"
758+ } ) ;
759+
760+ let deleted = data. delete_by_path ( "$.nonexistent" ) . unwrap ( ) ;
761+ assert_eq ! ( deleted, 0 ) ;
762+
763+ // Data should remain unchanged
764+ let expected = json ! ( {
765+ "test" : "value"
766+ } ) ;
767+ assert_eq ! ( data, expected) ;
768+ }
769+
770+ #[ test]
771+ fn test_delete_root ( ) {
772+ let mut data = json ! ( {
773+ "test" : "value"
774+ } ) ;
775+
776+ let deleted = data. delete_single ( "$" ) . unwrap ( ) ;
777+ assert_eq ! ( deleted, true ) ;
778+ assert_eq ! ( data, Value :: Null ) ;
779+ }
449780}
0 commit comments