1use serde::Serialize;
7use serde_json::Value;
8use std::collections::HashSet;
9
10#[derive(Debug, Clone, Default)]
24pub struct DumpOptions {
25 pub include_fields: HashSet<String>,
28 pub exclude_fields: HashSet<String>,
30 pub exclude_none: bool,
32 pub indent: Option<usize>,
40 pub recursive: bool,
43}
44
45impl DumpOptions {
46 pub fn new() -> Self {
48 Self::default()
49 }
50
51 pub fn include(mut self, fields: &[&str]) -> Self {
53 self.include_fields = fields.iter().map(|s| s.to_string()).collect();
54 self
55 }
56
57 pub fn exclude(mut self, fields: &[&str]) -> Self {
59 self.exclude_fields = fields.iter().map(|s| s.to_string()).collect();
60 self
61 }
62
63 pub fn exclude_none(mut self, yes: bool) -> Self {
65 self.exclude_none = yes;
66 self
67 }
68
69
70 pub fn indent(mut self, spaces: usize) -> Self {
72 self.indent = Some(spaces);
73 self
74 }
75
76 pub fn filter_value(&self, value: &mut Value) {
81 if self.recursive {
82 self.filter_value_recursive(value, 0);
83 } else {
84 self.filter_value_top_level(value);
85 }
86 }
87
88 fn filter_value_top_level(&self, value: &mut Value) {
90 if let Value::Object(ref mut map) = value {
91 let keys_to_remove: Vec<String> = map
92 .keys()
93 .filter(|key| {
94 if !self.include_fields.is_empty() && !self.include_fields.contains(*key) {
95 return true;
96 }
97 if self.exclude_fields.contains(*key) {
98 return true;
99 }
100 if self.exclude_none {
101 if let Some(val) = map.get(*key) {
102 if val.is_null() {
103 return true;
104 }
105 }
106 }
107 false
108 })
109 .cloned()
110 .collect();
111
112 for key in keys_to_remove {
113 map.remove(&key);
114 }
115 }
116 }
117
118 pub fn recursive(mut self, yes: bool) -> Self {
120 self.recursive = yes;
121 self
122 }
123
124 fn filter_value_recursive(&self, value: &mut Value, depth: usize) {
126 const MAX_DEPTH: usize = 128;
128 if depth > MAX_DEPTH {
129 return;
130 }
131
132 if let Value::Object(ref mut map) = value {
133 let keys_to_remove: Vec<String> = map
135 .keys()
136 .filter(|key| {
137 if !self.include_fields.is_empty() && !self.include_fields.contains(*key) {
139 return true;
140 }
141 if self.exclude_fields.contains(*key) {
143 return true;
144 }
145 if self.exclude_none {
147 if let Some(val) = map.get(*key) {
148 if val.is_null() {
149 return true;
150 }
151 }
152 }
153 false
154 })
155 .cloned()
156 .collect();
157
158 for key in keys_to_remove {
159 map.remove(&key);
160 }
161
162 for (_, v) in map.iter_mut() {
164 self.filter_value_recursive(v, depth + 1);
165 }
166 } else if let Value::Array(ref mut arr) = value {
167 for item in arr.iter_mut() {
169 self.filter_value_recursive(item, depth + 1);
170 }
171 }
172 }
173}
174
175pub trait Dump: Serialize {
179 fn dump(&self) -> Result<Value, serde_json::Error> {
181 serde_json::to_value(self)
182 }
183
184 fn dump_with(&self, options: &DumpOptions) -> Result<Value, serde_json::Error> {
186 let mut value = serde_json::to_value(self)?;
187 options.filter_value(&mut value);
188 Ok(value)
189 }
190
191 fn dump_json(&self) -> Result<String, serde_json::Error> {
193 serde_json::to_string(self)
194 }
195
196 fn dump_json_with(&self, options: &DumpOptions) -> Result<String, serde_json::Error> {
198 let mut value = serde_json::to_value(self)?;
199 options.filter_value(&mut value);
200 if let Some(indent) = options.indent {
201 let buf = Vec::new();
203 let indent_bytes = " ".repeat(indent).into_bytes();
204 let formatter = serde_json::ser::PrettyFormatter::with_indent(&indent_bytes);
205 let mut ser = serde_json::Serializer::with_formatter(buf, formatter);
206 serde::Serialize::serialize(&value, &mut ser)
207 .map_err(serde_json::Error::from)?;
208 Ok(String::from_utf8(ser.into_inner()).unwrap())
210 } else {
211 serde_json::to_string(&value)
212 }
213 }
214}
215
216impl<T: Serialize> Dump for T {}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222 use serde_json::json;
223
224 #[test]
225 fn test_dump_options_exclude() {
226 let opts = DumpOptions::new().exclude(&["password", "secret"]);
227 let mut value = json!({"name": "alice", "password": "hidden", "secret": "key"});
228 opts.filter_value(&mut value);
229 assert!(value.get("name").is_some());
230 assert!(value.get("password").is_none());
231 assert!(value.get("secret").is_none());
232 }
233
234 #[test]
235 fn test_dump_options_include() {
236 let opts = DumpOptions::new().include(&["name", "email"]);
237 let mut value = json!({"name": "alice", "email": "a@b.com", "age": 30});
238 opts.filter_value(&mut value);
239 assert!(value.get("name").is_some());
240 assert!(value.get("email").is_some());
241 assert!(value.get("age").is_none());
242 }
243
244 #[test]
245 fn test_dump_options_exclude_none() {
246 let opts = DumpOptions::new().exclude_none(true);
247 let mut value = json!({"name": "alice", "bio": null, "age": 30});
248 opts.filter_value(&mut value);
249 assert!(value.get("name").is_some());
250 assert!(value.get("bio").is_none());
251 assert!(value.get("age").is_some());
252 }
253
254 #[test]
255 fn test_dump_options_combined() {
256 let opts = DumpOptions::new()
257 .exclude(&["password"])
258 .exclude_none(true);
259 let mut value = json!({"name": "alice", "password": "x", "bio": null});
260 opts.filter_value(&mut value);
261 assert!(value.get("name").is_some());
262 assert!(value.get("password").is_none());
263 assert!(value.get("bio").is_none());
264 }
265
266 #[test]
267 fn test_dump_trait_on_serialize() {
268 #[derive(serde::Serialize)]
269 struct User {
270 name: String,
271 age: u32,
272 }
273 let user = User {
274 name: "alice".to_string(),
275 age: 30,
276 };
277 let value = user.dump().unwrap();
278 assert_eq!(value["name"], "alice");
279 assert_eq!(value["age"], 30);
280 }
281
282 #[test]
283 fn test_dump_with_exclude() {
284 #[derive(serde::Serialize)]
285 struct User {
286 name: String,
287 password: String,
288 }
289 let user = User {
290 name: "alice".to_string(),
291 password: "secret".to_string(),
292 };
293 let opts = DumpOptions::new().exclude(&["password"]);
294 let value = user.dump_with(&opts).unwrap();
295 assert!(value.get("name").is_some());
296 assert!(value.get("password").is_none());
297 }
298
299 #[test]
300 fn test_dump_json_compact() {
301 #[derive(serde::Serialize)]
302 struct Item {
303 name: String,
304 }
305 let item = Item {
306 name: "test".to_string(),
307 };
308 let json = item.dump_json().unwrap();
309 assert_eq!(json, r#"{"name":"test"}"#);
310 }
311
312 #[test]
313 fn test_dump_json_with_indent() {
314 #[derive(serde::Serialize)]
315 struct Item {
316 name: String,
317 }
318 let item = Item {
319 name: "test".to_string(),
320 };
321 let opts = DumpOptions::new().indent(2);
322 let json = item.dump_json_with(&opts).unwrap();
323 assert!(json.contains("\n"));
324 assert!(json.contains(" "));
325 }
326}