1use crate::encode::normalize::normalize_json_value;
2use crate::options::{EncodeReplacer, PathSegment};
3use crate::{JsonArray, JsonObject, JsonValue};
4
5pub fn apply_replacer(root: &JsonValue, replacer: &EncodeReplacer) -> JsonValue {
6 let replaced_root = replacer("", root, &[]);
7 if let Some(value) = replaced_root {
8 let normalized = normalize_json_value(value);
9 return transform_children(normalized, replacer, &[]);
10 }
11
12 transform_children(root.clone(), replacer, &[])
13}
14
15fn transform_children(
16 value: JsonValue,
17 replacer: &EncodeReplacer,
18 path: &[PathSegment],
19) -> JsonValue {
20 match value {
21 JsonValue::Object(entries) => JsonValue::Object(transform_object(entries, replacer, path)),
22 JsonValue::Array(values) => JsonValue::Array(transform_array(values, replacer, path)),
23 JsonValue::Primitive(value) => JsonValue::Primitive(value),
24 }
25}
26
27fn transform_object(
28 entries: JsonObject,
29 replacer: &EncodeReplacer,
30 path: &[PathSegment],
31) -> JsonObject {
32 let mut result = Vec::new();
33
34 for (key, value) in entries {
35 let mut next_path = path.to_vec();
36 next_path.push(PathSegment::Key(key.clone()));
37
38 let replacement = replacer(&key, &value, &next_path);
39 if let Some(next_value) = replacement {
40 let normalized = normalize_json_value(next_value);
41 let transformed = transform_children(normalized, replacer, &next_path);
42 result.push((key, transformed));
43 }
44 }
45
46 result
47}
48
49fn transform_array(
50 values: JsonArray,
51 replacer: &EncodeReplacer,
52 path: &[PathSegment],
53) -> JsonArray {
54 let mut result = Vec::new();
55
56 for (idx, value) in values.into_iter().enumerate() {
57 let mut next_path = path.to_vec();
58 next_path.push(PathSegment::Index(idx));
59
60 let key = idx.to_string();
61 let replacement = replacer(&key, &value, &next_path);
62 if let Some(next_value) = replacement {
63 let normalized = normalize_json_value(next_value);
64 let transformed = transform_children(normalized, replacer, &next_path);
65 result.push(transformed);
66 }
67 }
68
69 result
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use crate::StringOrNumberOrBoolOrNull;
76 use std::sync::Arc;
77 use std::sync::atomic::{AtomicUsize, Ordering};
78
79 fn s(v: &str) -> JsonValue {
80 JsonValue::Primitive(StringOrNumberOrBoolOrNull::String(v.to_string()))
81 }
82
83 fn n(v: f64) -> JsonValue {
84 JsonValue::Primitive(StringOrNumberOrBoolOrNull::Number(v))
85 }
86
87 fn identity_replacer() -> EncodeReplacer {
88 Arc::new(|_key, value, _path| Some(value.clone()))
89 }
90
91 #[test]
92 fn identity_replacer_returns_equivalent_tree_for_primitive() {
93 let root = n(42.0);
94 let out = apply_replacer(&root, &identity_replacer());
95 assert_eq!(out, n(42.0));
96 }
97
98 #[test]
99 fn identity_replacer_returns_equivalent_tree_for_object() {
100 let root = JsonValue::Object(vec![("a".into(), n(1.0)), ("b".into(), s("hi"))]);
101 let out = apply_replacer(&root, &identity_replacer());
102 assert_eq!(out, root);
103 }
104
105 #[test]
106 fn returning_none_for_root_falls_back_to_original_transform() {
107 let replacer: EncodeReplacer = Arc::new(|key, value, _path| {
108 if key.is_empty() {
109 None
110 } else {
111 Some(value.clone())
112 }
113 });
114 let root = JsonValue::Object(vec![("a".into(), n(1.0))]);
115 let out = apply_replacer(&root, &replacer);
116 assert_eq!(out, root);
117 }
118
119 #[test]
120 fn replacer_can_drop_object_entries_by_returning_none() {
121 let replacer: EncodeReplacer = Arc::new(|key, value, _path| {
122 if key == "drop" {
123 None
124 } else {
125 Some(value.clone())
126 }
127 });
128 let root = JsonValue::Object(vec![("keep".into(), n(1.0)), ("drop".into(), n(2.0))]);
129 let out = apply_replacer(&root, &replacer);
130 assert_eq!(out, JsonValue::Object(vec![("keep".into(), n(1.0))]));
131 }
132
133 #[test]
134 fn replacer_can_drop_array_elements_by_returning_none() {
135 let replacer: EncodeReplacer = Arc::new(|_key, value, path| {
136 let PathSegment::Index(idx) = path.last()? else {
137 return Some(value.clone());
138 };
139 if *idx == 1 { None } else { Some(value.clone()) }
140 });
141 let root = JsonValue::Array(vec![n(1.0), n(2.0), n(3.0)]);
142 let out = apply_replacer(&root, &replacer);
143 assert_eq!(out, JsonValue::Array(vec![n(1.0), n(3.0)]));
144 }
145
146 #[test]
147 fn replacer_can_transform_values() {
148 let replacer: EncodeReplacer = Arc::new(|_key, value, _path| {
149 if let JsonValue::Primitive(StringOrNumberOrBoolOrNull::Number(num)) = value {
150 Some(JsonValue::from(num * 2.0))
151 } else {
152 Some(value.clone())
153 }
154 });
155 let root = JsonValue::Array(vec![n(1.0), n(2.0)]);
156 let out = apply_replacer(&root, &replacer);
157 assert_eq!(out, JsonValue::Array(vec![n(2.0), n(4.0)]));
158 }
159
160 #[test]
161 fn replacer_normalizes_nan_returned_from_replacement() {
162 let replacer: EncodeReplacer =
163 Arc::new(|_key, _value, _path| Some(JsonValue::from(f64::NAN)));
164 let root = n(1.0);
165 let out = apply_replacer(&root, &replacer);
166 assert!(matches!(
167 out,
168 JsonValue::Primitive(StringOrNumberOrBoolOrNull::Null)
169 ));
170 }
171
172 #[test]
173 fn replacer_receives_path_segments() {
174 let collected: Arc<std::sync::Mutex<Vec<Vec<PathSegment>>>> =
175 Arc::new(std::sync::Mutex::new(Vec::new()));
176 let collected_inner = collected.clone();
177 let replacer: EncodeReplacer = Arc::new(move |_key, value, path| {
178 collected_inner.lock().unwrap().push(path.to_vec());
179 Some(value.clone())
180 });
181 let root = JsonValue::Object(vec![("arr".into(), JsonValue::Array(vec![n(1.0), n(2.0)]))]);
182 let _ = apply_replacer(&root, &replacer);
183 let paths: Vec<Vec<PathSegment>> = collected.lock().unwrap().clone();
186 assert!(paths.iter().any(Vec::is_empty));
188 assert!(
189 paths
190 .iter()
191 .any(|p| p.len() == 1 && matches!(&p[0], PathSegment::Key(k) if k == "arr"))
192 );
193 assert!(paths.iter().any(|p| {
194 p.last()
195 .is_some_and(|seg| matches!(seg, PathSegment::Index(0)))
196 }));
197 assert!(paths.iter().any(|p| {
198 p.last()
199 .is_some_and(|seg| matches!(seg, PathSegment::Index(1)))
200 }));
201 }
202
203 #[test]
204 fn replacer_call_counts_are_reasonable() {
205 let count = Arc::new(AtomicUsize::new(0));
206 let count_inner = count.clone();
207 let replacer: EncodeReplacer = Arc::new(move |_key, value, _path| {
208 count_inner.fetch_add(1, Ordering::SeqCst);
209 Some(value.clone())
210 });
211 let root = JsonValue::Object(vec![
212 ("a".into(), n(1.0)),
213 ("b".into(), JsonValue::Array(vec![n(2.0), n(3.0)])),
214 ]);
215 let _ = apply_replacer(&root, &replacer);
216 let n_calls = count.load(Ordering::SeqCst);
217 assert_eq!(n_calls, 5);
219 }
220}