1use serde_json::Value;
11
12pub struct DotArrayAccessor {
14 missing_keys: Vec<String>,
15}
16
17impl DotArrayAccessor {
18 pub fn new() -> Self {
20 Self {
21 missing_keys: Vec::new(),
22 }
23 }
24
25 pub fn get<'a>(&mut self, data: &'a Value, key: &str) -> Option<&'a Value> {
31 if !key.contains('.') {
33 if let Some(obj) = data.as_object() {
34 if !obj.contains_key(key) {
35 self.missing_keys.push(key.to_string());
36 return None;
37 }
38 return obj.get(key);
39 } else {
40 self.missing_keys.push(key.to_string());
41 return None;
42 }
43 }
44
45 let segments: Vec<&str> = key.split('.').collect();
47 let mut current = data;
48
49 for segment in segments {
50 match current.get(segment) {
51 Some(next) => current = next,
52 None => {
53 self.missing_keys.push(key.to_string());
54 return None;
55 }
56 }
57 }
58
59 Some(current)
60 }
61
62 pub fn get_missing_keys(&self) -> &[String] {
64 &self.missing_keys
65 }
66
67 pub fn clear_missing_keys(&mut self) {
69 self.missing_keys.clear();
70 }
71
72 pub fn set(data: &mut Value, key: &str, value: Value) {
78 if !key.contains('.') {
80 if let Some(obj) = data.as_object_mut() {
81 obj.insert(key.to_string(), value);
82 } else {
83 let mut new_obj = serde_json::Map::new();
85 new_obj.insert(key.to_string(), value);
86 *data = Value::Object(new_obj);
87 }
88 return;
89 }
90
91 let segments: Vec<&str> = key.split('.').collect();
93
94 if !data.is_object() {
96 *data = Value::Object(serde_json::Map::new());
97 }
98
99 let mut current = data;
100
101 let last_idx = segments.len() - 1;
102
103 for (i, segment) in segments.iter().enumerate() {
104 if i == last_idx {
105 if let Some(obj) = current.as_object_mut() {
107 obj.insert(segment.to_string(), value);
108 }
109 return;
110 }
111
112 {
115 let obj = current.as_object_mut().expect("current must be an object");
116 if !obj.contains_key(*segment) || !obj[*segment].is_object() {
117 obj.insert(segment.to_string(), Value::Object(serde_json::Map::new()));
118 }
119 }
120
121 current = current.get_mut(*segment).expect("segment must exist");
123 }
124 }
125
126 pub fn merge(data: &mut Value, key: &str, value: Value) {
134 if !key.contains('.') {
136 if let Some(obj) = data.as_object_mut() {
138 let should_merge = if let Some(existing) = obj.get(key) {
139 existing.is_object() && value.is_object()
140 } else {
141 false
142 };
143
144 if should_merge {
145 if let Some(value_obj) = value.as_object() {
147 for (k, v) in value_obj {
148 if let Some(existing) = obj.get_mut(key) {
149 let should_recurse = if let Some(existing_obj) = existing.as_object() {
150 existing_obj.contains_key(k) && existing_obj[k].is_object() && v.is_object()
151 } else {
152 false
153 };
154
155 if should_recurse {
156 Self::merge(existing, k, v.clone());
158 } else {
159 if let Some(existing_obj_mut) = existing.as_object_mut() {
161 existing_obj_mut.insert(k.clone(), v.clone());
162 }
163 }
164 }
165 }
166 }
167 return;
168 }
169 obj.insert(key.to_string(), value);
171 } else {
172 let mut new_obj = serde_json::Map::new();
174 new_obj.insert(key.to_string(), value);
175 *data = Value::Object(new_obj);
176 }
177 return;
178 }
179
180 let segments: Vec<&str> = key.split('.').collect();
182 let first_segment = segments[0];
183 let remaining_key = segments[1..].join(".");
184
185 if !data.is_object() {
187 *data = Value::Object(serde_json::Map::new());
188 }
189
190 if let Some(obj) = data.as_object_mut() {
192 if !obj.contains_key(first_segment) {
193 obj.insert(first_segment.to_string(), Value::Object(serde_json::Map::new()));
194 }
195
196 if let Some(next) = obj.get_mut(first_segment) {
198 Self::merge(next, &remaining_key, value);
199 }
200 }
201 }
202
203 pub fn has(data: &Value, key: &str) -> bool {
209 if !key.contains('.') {
211 if let Some(obj) = data.as_object() {
212 return obj.contains_key(key);
213 }
214 return false;
215 }
216
217 let segments: Vec<&str> = key.split('.').collect();
219 let mut current = data;
220
221 for segment in segments {
222 match current.get(segment) {
223 Some(next) => current = next,
224 None => return false,
225 }
226 }
227
228 true
229 }
230
231 pub fn unset(data: &mut Value, key: &str) {
235 if !key.contains('.') {
237 if let Some(obj) = data.as_object_mut() {
238 obj.remove(key);
239 }
240 return;
241 }
242
243 let segments: Vec<&str> = key.split('.').collect();
245 let mut current = data;
246
247 for (i, segment) in segments.iter().enumerate() {
248 let is_last = i == segments.len() - 1;
249
250 if is_last {
251 if let Some(obj) = current.as_object_mut() {
253 obj.remove(*segment);
254 }
255 return;
256 }
257
258 if !current.is_object() {
261 return;
262 }
263
264 let has_next = if let Some(obj) = current.as_object() {
265 obj.contains_key(*segment) && obj.get(*segment).map_or(false, |v| v.is_object())
266 } else {
267 false
268 };
269
270 if !has_next {
271 return;
273 }
274
275 current = current.get_mut(*segment).unwrap();
277 }
278 }
279}
280
281impl Default for DotArrayAccessor {
282 fn default() -> Self {
283 Self::new()
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290 use serde_json::json;
291
292 #[test]
293 fn test_get_simple_key() {
294 let mut accessor = DotArrayAccessor::new();
295 let data = json!({
296 "name": "Alice"
297 });
298
299 let result = accessor.get(&data, "name");
300 assert!(result.is_some());
301 assert_eq!(result.unwrap(), &json!("Alice"));
302 assert_eq!(accessor.get_missing_keys().len(), 0);
303 }
304
305 #[test]
306 fn test_get_nested_key() {
307 let mut accessor = DotArrayAccessor::new();
308 let data = json!({
309 "user": {
310 "profile": {
311 "name": "Alice"
312 }
313 }
314 });
315
316 let result = accessor.get(&data, "user.profile.name");
317 assert!(result.is_some());
318 assert_eq!(result.unwrap(), &json!("Alice"));
319 assert_eq!(accessor.get_missing_keys().len(), 0);
320 }
321
322 #[test]
323 fn test_get_missing_key_tracking() {
324 let mut accessor = DotArrayAccessor::new();
325 let data = json!({
326 "user": {
327 "name": "Alice"
328 }
329 });
330
331 let result = accessor.get(&data, "user.age");
332 assert!(result.is_none());
333 assert_eq!(accessor.get_missing_keys(), vec!["user.age"]);
334
335 let result2 = accessor.get(&data, "user.email");
337 assert!(result2.is_none());
338 assert_eq!(accessor.get_missing_keys(), vec!["user.age", "user.email"]);
339
340 accessor.clear_missing_keys();
342 assert_eq!(accessor.get_missing_keys().len(), 0);
343 }
344
345 #[test]
346 fn test_set_simple_key() {
347 let mut data = json!({});
348 DotArrayAccessor::set(&mut data, "name", json!("Alice"));
349
350 assert_eq!(data, json!({"name": "Alice"}));
351 }
352
353 #[test]
354 fn test_set_nested_key() {
355 let mut data = json!({});
356 DotArrayAccessor::set(&mut data, "user.profile.name", json!("Alice"));
357
358 assert_eq!(data, json!({
359 "user": {
360 "profile": {
361 "name": "Alice"
362 }
363 }
364 }));
365 }
366
367 #[test]
368 fn test_set_overwrites_existing() {
369 let mut data = json!({
370 "user": {
371 "name": "Alice"
372 }
373 });
374
375 DotArrayAccessor::set(&mut data, "user.name", json!("Bob"));
376
377 assert_eq!(data["user"]["name"], json!("Bob"));
378 }
379
380 #[test]
381 fn test_unset_simple_key() {
382 let mut data = json!({
383 "name": "Alice",
384 "age": 30
385 });
386
387 DotArrayAccessor::unset(&mut data, "name");
388
389 assert_eq!(data, json!({"age": 30}));
390 }
391
392 #[test]
393 fn test_unset_nested_key() {
394 let mut data = json!({
395 "user": {
396 "profile": {
397 "name": "Alice",
398 "age": 30
399 }
400 }
401 });
402
403 DotArrayAccessor::unset(&mut data, "user.profile.name");
404
405 assert_eq!(data, json!({
406 "user": {
407 "profile": {
408 "age": 30
409 }
410 }
411 }));
412 }
413
414 #[test]
415 fn test_unset_nonexistent() {
416 let mut data = json!({
417 "user": {
418 "name": "Alice"
419 }
420 });
421
422 DotArrayAccessor::unset(&mut data, "user.age");
424 DotArrayAccessor::unset(&mut data, "unknown.path");
425
426 assert_eq!(data, json!({
427 "user": {
428 "name": "Alice"
429 }
430 }));
431 }
432
433 #[test]
434 fn test_merge_simple_key() {
435 let mut data = json!({
436 "name": "Alice"
437 });
438
439 DotArrayAccessor::merge(&mut data, "age", json!(30));
440
441 assert_eq!(data, json!({
442 "name": "Alice",
443 "age": 30
444 }));
445 }
446
447 #[test]
448 fn test_merge_overwrites_scalar() {
449 let mut data = json!({
450 "name": "Alice"
451 });
452
453 DotArrayAccessor::merge(&mut data, "name", json!("Bob"));
454
455 assert_eq!(data, json!({
456 "name": "Bob"
457 }));
458 }
459
460 #[test]
461 fn test_merge_overwrites_list() {
462 let mut data = json!({
463 "tags": ["php", "web"]
464 });
465
466 DotArrayAccessor::merge(&mut data, "tags", json!(["api", "rest"]));
467
468 assert_eq!(data, json!({
469 "tags": ["api", "rest"]
470 }));
471 }
472
473 #[test]
474 fn test_merge_nested_objects() {
475 let mut data = json!({
476 "user": {
477 "name": "Alice",
478 "profile": {
479 "age": 25
480 }
481 }
482 });
483
484 DotArrayAccessor::merge(&mut data, "user", json!({
485 "email": "alice@example.com",
486 "profile": {
487 "age": 30,
488 "city": "Tokyo"
489 }
490 }));
491
492 assert_eq!(data, json!({
493 "user": {
494 "name": "Alice",
495 "email": "alice@example.com",
496 "profile": {
497 "age": 30,
498 "city": "Tokyo"
499 }
500 }
501 }));
502 }
503
504 #[test]
505 fn test_merge_with_dot_notation() {
506 let mut data = json!({
507 "connection": {
508 "driver": "postgres",
509 "charset": "UTF8"
510 }
511 });
512
513 DotArrayAccessor::merge(&mut data, "connection", json!({
514 "host": "localhost",
515 "port": 5432
516 }));
517
518 assert_eq!(data, json!({
519 "connection": {
520 "driver": "postgres",
521 "charset": "UTF8",
522 "host": "localhost",
523 "port": 5432
524 }
525 }));
526 }
527
528 #[test]
529 fn test_merge_creates_path() {
530 let mut data = json!({});
531
532 DotArrayAccessor::merge(&mut data, "user.profile.name", json!("Alice"));
533
534 assert_eq!(data, json!({
535 "user": {
536 "profile": {
537 "name": "Alice"
538 }
539 }
540 }));
541 }
542
543 #[test]
544 fn test_has_simple_key() {
545 let data = json!({
546 "name": "Alice",
547 "age": 30
548 });
549
550 assert!(DotArrayAccessor::has(&data, "name"));
551 assert!(DotArrayAccessor::has(&data, "age"));
552 assert!(!DotArrayAccessor::has(&data, "email"));
553 }
554
555 #[test]
556 fn test_has_nested_key() {
557 let data = json!({
558 "user": {
559 "profile": {
560 "name": "Alice",
561 "age": 30
562 }
563 }
564 });
565
566 assert!(DotArrayAccessor::has(&data, "user"));
567 assert!(DotArrayAccessor::has(&data, "user.profile"));
568 assert!(DotArrayAccessor::has(&data, "user.profile.name"));
569 assert!(DotArrayAccessor::has(&data, "user.profile.age"));
570 assert!(!DotArrayAccessor::has(&data, "user.profile.email"));
571 assert!(!DotArrayAccessor::has(&data, "user.settings"));
572 assert!(!DotArrayAccessor::has(&data, "unknown"));
573 }
574
575 #[test]
576 fn test_has_with_null_value() {
577 let data = json!({
578 "user": {
579 "name": "Alice",
580 "deleted_at": null
581 }
582 });
583
584 assert!(DotArrayAccessor::has(&data, "user.deleted_at"));
586 assert!(!DotArrayAccessor::has(&data, "user.created_at"));
587 }
588
589 #[test]
590 fn test_has_with_non_object_value() {
591 let data = json!({
592 "tags": ["php", "rust"],
593 "count": 42
594 });
595
596 assert!(DotArrayAccessor::has(&data, "tags"));
598 assert!(DotArrayAccessor::has(&data, "count"));
599
600 assert!(!DotArrayAccessor::has(&data, "tags.0"));
602 }
603
604 #[test]
605 fn test_has_empty_key() {
606 let data = json!({
607 "user": {
608 "name": "Alice"
609 }
610 });
611
612 assert!(!DotArrayAccessor::has(&data, ""));
614 }
615}