1use serde_json::Value;
20
21pub fn path_get(value: &Value, path: &str) -> Option<Value> {
24 if path.is_empty() {
25 return Some(value.clone());
26 }
27 let mut current = value;
28 for part in path.split('.') {
29 match current {
30 Value::Object(map) => {
31 current = map.get(part)?;
32 }
33 Value::Array(arr) => {
34 let idx: usize = part.parse().ok()?;
35 current = arr.get(idx)?;
36 }
37 _ => return None,
38 }
39 }
40 Some(current.clone())
41}
42
43pub fn path_has(value: &Value, path: &str) -> bool {
45 path_get(value, path).is_some()
46}
47
48pub fn path_set(target: &mut Value, path: &str, new_value: Value) {
56 if path.is_empty() {
57 return;
58 }
59 let parts: Vec<&str> = path.split('.').collect();
60 let mut current = target;
61
62 for part in &parts[..parts.len() - 1] {
64 if let Ok(idx) = part.parse::<usize>() {
66 if let Value::Array(arr) = current {
67 while arr.len() <= idx {
68 arr.push(Value::Null);
69 }
70 current = &mut arr[idx];
71 continue;
72 }
73 }
74 if !current.is_object() {
76 *current = Value::Object(serde_json::Map::new());
77 }
78 if let Value::Object(map) = current {
79 if !map.contains_key(*part) {
80 map.insert(part.to_string(), Value::Object(serde_json::Map::new()));
81 }
82 current = map.get_mut(*part).unwrap();
83 }
84 }
85
86 let last = parts[parts.len() - 1];
88 if let Ok(idx) = last.parse::<usize>() {
89 if let Value::Array(arr) = current {
90 while arr.len() <= idx {
91 arr.push(Value::Null);
92 }
93 arr[idx] = new_value;
94 return;
95 }
96 }
97 if !current.is_object() {
98 *current = Value::Object(serde_json::Map::new());
99 }
100 if let Value::Object(map) = current {
101 map.insert(last.to_string(), new_value);
102 }
103}
104
105pub fn path_delete(target: &mut Value, path: &str) -> bool {
110 if path.is_empty() {
111 return false;
112 }
113 let parts: Vec<&str> = path.split('.').collect();
114 let mut current = target;
115
116 for part in &parts[..parts.len() - 1] {
117 if let Ok(idx) = part.parse::<usize>() {
118 if let Value::Array(arr) = current {
119 if let Some(next) = arr.get_mut(idx) {
120 current = next;
121 continue;
122 }
123 return false;
124 }
125 }
126 match current {
127 Value::Object(map) => match map.get_mut(*part) {
128 Some(next) => current = next,
129 None => return false,
130 },
131 _ => return false,
132 }
133 }
134
135 let last = parts[parts.len() - 1];
136 if let Ok(idx) = last.parse::<usize>() {
137 if let Value::Array(arr) = current {
138 if idx < arr.len() {
139 arr.remove(idx);
140 return true;
141 }
142 return false;
143 }
144 }
145 if let Value::Object(map) = current {
146 return map.remove(last).is_some();
147 }
148 false
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use serde_json::json;
155
156 #[test]
159 fn get_nested_object() {
160 let v = json!({"user": {"name": "Alice", "age": 30}});
161 assert_eq!(path_get(&v, "user.name"), Some(json!("Alice")));
162 assert_eq!(path_get(&v, "user.age"), Some(json!(30)));
163 }
164
165 #[test]
166 fn get_array_index() {
167 let v = json!({"items": ["a", "b", "c"]});
168 assert_eq!(path_get(&v, "items.1"), Some(json!("b")));
169 assert_eq!(path_get(&v, "items.10"), None);
170 }
171
172 #[test]
173 fn get_missing_returns_none() {
174 let v = json!({"a": 1});
175 assert_eq!(path_get(&v, "b"), None);
176 assert_eq!(path_get(&v, "a.b"), None); }
178
179 #[test]
180 fn get_empty_path_returns_root() {
181 let v = json!({"a": 1});
182 assert_eq!(path_get(&v, ""), Some(v.clone()));
183 }
184
185 #[test]
188 fn has_matches_get() {
189 let v = json!({"user": {"name": "Alice"}});
190 assert!(path_has(&v, "user"));
191 assert!(path_has(&v, "user.name"));
192 assert!(!path_has(&v, "user.age"));
193 assert!(!path_has(&v, "other"));
194 }
195
196 #[test]
199 fn set_creates_intermediate_objects() {
200 let mut v = json!({});
201 path_set(&mut v, "a.b.c", json!(42));
202 assert_eq!(v, json!({"a": {"b": {"c": 42}}}));
203 }
204
205 #[test]
206 fn set_overwrites_existing() {
207 let mut v = json!({"a": 1});
208 path_set(&mut v, "a", json!(2));
209 assert_eq!(v, json!({"a": 2}));
210 }
211
212 #[test]
213 fn set_extends_array_with_nulls() {
214 let mut v = json!({"items": [1, 2]});
215 path_set(&mut v, "items.5", json!("X"));
216 assert_eq!(v, json!({"items": [1, 2, null, null, null, "X"]}));
217 }
218
219 #[test]
220 fn set_numeric_segment_on_object_is_key_not_index() {
221 let mut v = json!({});
224 path_set(&mut v, "0", json!("x"));
225 assert_eq!(v, json!({"0": "x"}));
226 }
227
228 #[test]
229 fn set_past_nine_uses_full_decimal() {
230 let mut v = json!({"items": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]});
233 path_set(&mut v, "items.10", json!("ten"));
234 assert_eq!(v["items"], json!([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "ten"]));
235 }
236
237 #[test]
238 fn set_on_non_object_replaces_with_object() {
239 let mut v = json!("scalar");
240 path_set(&mut v, "a.b", json!(1));
241 assert_eq!(v, json!({"a": {"b": 1}}));
242 }
243
244 #[test]
245 fn set_empty_path_is_noop() {
246 let mut v = json!({"a": 1});
247 path_set(&mut v, "", json!(99));
248 assert_eq!(v, json!({"a": 1}));
249 }
250
251 #[test]
254 fn delete_object_key() {
255 let mut v = json!({"a": 1, "b": 2});
256 assert!(path_delete(&mut v, "a"));
257 assert_eq!(v, json!({"b": 2}));
258 }
259
260 #[test]
261 fn delete_array_index_splices() {
262 let mut v = json!({"items": ["a", "b", "c"]});
263 assert!(path_delete(&mut v, "items.1"));
264 assert_eq!(v, json!({"items": ["a", "c"]}));
265 }
266
267 #[test]
268 fn delete_missing_returns_false() {
269 let mut v = json!({"a": 1});
270 assert!(!path_delete(&mut v, "b"));
271 assert!(!path_delete(&mut v, "a.nested"));
272 assert_eq!(v, json!({"a": 1}));
273 }
274
275 #[test]
276 fn delete_nested() {
277 let mut v = json!({"user": {"name": "Alice", "age": 30}});
278 assert!(path_delete(&mut v, "user.age"));
279 assert_eq!(v, json!({"user": {"name": "Alice"}}));
280 }
281
282 #[test]
283 fn delete_array_out_of_bounds() {
284 let mut v = json!({"items": ["a"]});
285 assert!(!path_delete(&mut v, "items.5"));
286 }
287}