value_ext/json/
json_value_ext.rs

1use crate::AsType;
2use serde::de::DeserializeOwned;
3use serde::Serialize;
4use serde_json::{json, Map, Value};
5use std::collections::VecDeque;
6
7/// Extension trait for working with JSON values in a more convenient way.
8///
9/// `JsonValueExt` offers convenient methods for interacting with `serde_json::Value` objects,
10/// simplifying tasks like getting, taking, inserting, traversing, and pretty-printing JSON data
11/// while ensuring type safety with Serde's serialization and deserialization.
12///
13/// # Provided Methods
14///
15/// - **`x_get`**: Returns an owned value of a specified type `T` from a JSON object using either a direct name or a pointer path.
16/// - **`x_get_as`**: Returns a reference of a specified type `T` from a JSON object using either a direct name or a pointer path.
17/// - **`x_get_str`**: Returns a `&str` from a JSON object using either a direct name or a pointer path.
18/// - **`x_get_i64`**: Returns an `i64` from a JSON object using either a direct name or a pointer path.
19/// - **`x_get_f64`**: Returns an `f64` from a JSON object using either a direct name or a pointer path.
20/// - **`x_get_bool`**: Returns a `bool` from a JSON object using either a direct name or a pointer path.
21/// - **`x_take`**: Takes a value from a JSON object using a specified name or pointer path, replacing it with `Null`.
22/// - **`x_remove`**: Removes the value at the specified name or pointer path from the JSON object and returns it,
23///                 leaving no placeholder in the object (unlike `x_take`).
24/// - **`x_insert`**: Inserts a new value of type `T` into a JSON object at the specified name or pointer path,
25///                   creating any missing objects along the way.
26/// - **`x_walk`**: Traverses all properties in the JSON value tree and calls the callback function on each.
27/// - **`x_pretty`**: Returns a pretty-printed string representation of the JSON value.
28pub trait JsonValueExt {
29	fn x_new_object() -> Value;
30
31	fn x_contains<T: DeserializeOwned>(&self, name_or_pointer: &str) -> bool;
32
33	/// Returns an owned type `T` for a given name or pointer path.
34	/// Note: This will create a clone of the matched Value.
35	/// - `name_or_pointer`: Can be a direct name or a pointer path (if it starts with '/').
36	fn x_get<T: DeserializeOwned>(&self, name_or_pointer: &str) -> Result<T>;
37
38	/// Returns a reference of type `T` (or a copy for copy types) for a given name or pointer path.
39	/// Use this one over `x_get` to avoid string allocation.
40	/// - `name_or_pointer`: Can be a direct name or a pointer path (if it starts with '/').
41	fn x_get_as<'a, T: AsType<'a>>(&'a self, name_or_pointer: &str) -> Result<T>;
42
43	/// Returns a &str if present (shortcut for `x_get_as::<&str>(...)`)
44	fn x_get_str(&self, name_or_pointer: &str) -> Result<&str> {
45		self.x_get_as(name_or_pointer)
46	}
47
48	/// Returns an i64 if present (shortcut for `x_get_as::<i64>(...)`)
49	fn x_get_i64(&self, name_or_pointer: &str) -> Result<i64> {
50		self.x_get_as(name_or_pointer)
51	}
52
53	/// Returns an f64 if present (shortcut for `x_get_as::<f64>(...)`)
54	fn x_get_f64(&self, name_or_pointer: &str) -> Result<f64> {
55		self.x_get_as(name_or_pointer)
56	}
57
58	/// Returns a bool if present (shortcut for `x_get_as::<bool>(...)`)
59	fn x_get_bool(&self, name_or_pointer: &str) -> Result<bool> {
60		self.x_get_as(name_or_pointer)
61	}
62
63	/// Takes the value at the specified name or pointer path and replaces it with `Null`.
64	/// - `name_or_pointer`: Can be a direct name or a pointer path (if it starts with '/').
65	fn x_take<T: DeserializeOwned>(&mut self, name_or_pointer: &str) -> Result<T>;
66
67	/// Removes the value at the specified name or pointer path from the JSON object
68	/// and returns it without leaving a placeholder, unlike `x_take`.
69	fn x_remove<T: DeserializeOwned>(&mut self, name_or_pointer: &str) -> Result<T>;
70
71	/// Inserts a new value of type `T` at the specified name or pointer path.
72	/// This method creates missing `Value::Object` entries as needed.
73	/// - `name_or_pointer`: Can be a direct name or a pointer path (if it starts with '/').
74	fn x_insert<T: Serialize>(&mut self, name_or_pointer: &str, value: T) -> Result<()>;
75
76	/// Walks through all properties in the JSON value tree and calls the callback function on each.
77	/// - The callback signature is `(parent_map, property_name) -> bool`.
78	///   - Returns `false` to stop the traversal; returns `true` to continue.
79	///
80	/// Returns:
81	/// - `true` if the traversal completes without stopping early.
82	/// - `false` if the traversal was stopped early because the callback returned `false`.
83	fn x_walk<F>(&mut self, callback: F) -> bool
84	where
85		F: FnMut(&mut Map<String, Value>, &str) -> bool;
86
87	/// Returns a pretty-printed string representation of the JSON value.
88	fn x_pretty(&self) -> Result<String>;
89}
90
91impl JsonValueExt for Value {
92	fn x_new_object() -> Value {
93		Value::Object(Map::new())
94	}
95
96	fn x_contains<T: DeserializeOwned>(&self, name_or_pointer: &str) -> bool {
97		if name_or_pointer.starts_with('/') {
98			self.pointer(name_or_pointer).is_some()
99		} else {
100			self.get(name_or_pointer).is_some()
101		}
102	}
103
104	fn x_get<T: DeserializeOwned>(&self, name_or_pointer: &str) -> Result<T> {
105		let value = if name_or_pointer.starts_with('/') {
106			self.pointer(name_or_pointer)
107				.ok_or_else(|| JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))?
108		} else {
109			self.get(name_or_pointer)
110				.ok_or_else(|| JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))?
111		};
112
113		let value: T =
114			serde_json::from_value(value.clone())
115				.map_err(JsonValueExtError::from)
116				.map_err(|err| match err {
117					JsonValueExtError::ValueNotOfType(not_of_type) => JsonValueExtError::PropertyValueNotOfType {
118						name: name_or_pointer.to_string(),
119						not_of_type,
120					},
121					other => other,
122				})?;
123
124		Ok(value)
125	}
126
127	fn x_get_as<'a, T: AsType<'a>>(&'a self, name_or_pointer: &str) -> Result<T> {
128		let value = if name_or_pointer.starts_with('/') {
129			self.pointer(name_or_pointer)
130				.ok_or_else(|| JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))?
131		} else {
132			self.get(name_or_pointer)
133				.ok_or_else(|| JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))?
134		};
135
136		T::from_value(value).map_err(|err| match err {
137			JsonValueExtError::ValueNotOfType(not_of_type) => JsonValueExtError::PropertyValueNotOfType {
138				name: name_or_pointer.to_string(),
139				not_of_type,
140			},
141			other => other,
142		})
143	}
144
145	fn x_take<T: DeserializeOwned>(&mut self, name_or_pointer: &str) -> Result<T> {
146		let value = if name_or_pointer.starts_with('/') {
147			self.pointer_mut(name_or_pointer)
148				.map(Value::take)
149				.ok_or_else(|| JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))?
150		} else {
151			self.get_mut(name_or_pointer)
152				.map(Value::take)
153				.ok_or_else(|| JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))?
154		};
155
156		let value: T = serde_json::from_value(value)?;
157		Ok(value)
158	}
159
160	fn x_remove<T: DeserializeOwned>(&mut self, name_or_pointer: &str) -> Result<T> {
161		if !name_or_pointer.starts_with('/') {
162			match self {
163				Value::Object(map) => {
164					let removed = map
165						.remove(name_or_pointer)
166						.ok_or_else(|| JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))?;
167					let value: T = serde_json::from_value(removed)?;
168					Ok(value)
169				}
170				_ => Err(JsonValueExtError::custom("Value is not an Object; cannot x_remove")),
171			}
172		} else {
173			let parts: Vec<&str> = name_or_pointer.split('/').skip(1).collect();
174			if parts.is_empty() {
175				return Err(JsonValueExtError::custom("Invalid path"));
176			}
177			let mut current = self;
178			for &part in &parts[..parts.len() - 1] {
179				match current {
180					Value::Object(map) => {
181						current = map
182							.get_mut(part)
183							.ok_or_else(|| JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))?;
184					}
185					Value::Array(arr) => {
186						let index: usize = part
187							.parse()
188							.map_err(|_| JsonValueExtError::custom("Invalid array index in pointer"))?;
189						if index < arr.len() {
190							current = &mut arr[index];
191						} else {
192							return Err(JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()));
193						}
194					}
195					_ => return Err(JsonValueExtError::custom("Path does not point to an Object or Array")),
196				}
197			}
198			let last_part = parts
199				.last()
200				.ok_or_else(|| JsonValueExtError::custom("Last element not found"))?;
201			match current {
202				Value::Object(map) => {
203					let removed = map
204						.remove(*last_part)
205						.ok_or_else(|| JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))?;
206					let value: T = serde_json::from_value(removed)?;
207					Ok(value)
208				}
209				Value::Array(arr) => {
210					let index: usize = last_part
211						.parse()
212						.map_err(|_| JsonValueExtError::custom("Invalid array index in pointer"))?;
213					if index < arr.len() {
214						let removed = arr.remove(index);
215						let value: T = serde_json::from_value(removed)?;
216						Ok(value)
217					} else {
218						Err(JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))
219					}
220				}
221				_ => Err(JsonValueExtError::custom("Path does not point to an Object or Array")),
222			}
223		}
224	}
225
226	fn x_insert<T: Serialize>(&mut self, name_or_pointer: &str, value: T) -> Result<()> {
227		let new_value = serde_json::to_value(value)?;
228
229		if !name_or_pointer.starts_with('/') {
230			match self {
231				Value::Object(map) => {
232					map.insert(name_or_pointer.to_string(), new_value);
233					Ok(())
234				}
235				_ => Err(JsonValueExtError::custom("Value is not an Object; cannot x_insert")),
236			}
237		} else {
238			let parts: Vec<&str> = name_or_pointer.split('/').skip(1).collect();
239			let mut current = self;
240
241			// -- Add the eventual missing parents
242			for &part in &parts[..parts.len() - 1] {
243				match current {
244					Value::Object(map) => {
245						current = map.entry(part).or_insert_with(|| json!({}));
246					}
247					_ => return Err(JsonValueExtError::custom("Path does not point to an Object")),
248				}
249			}
250
251			// -- Set the value at the last element
252			if let Some(&last_part) = parts.last() {
253				match current {
254					Value::Object(map) => {
255						map.insert(last_part.to_string(), new_value);
256						Ok(())
257					}
258					_ => Err(JsonValueExtError::custom("Path does not point to an Object")),
259				}
260			} else {
261				Err(JsonValueExtError::custom("Invalid path"))
262			}
263		}
264	}
265
266	fn x_pretty(&self) -> Result<String> {
267		let content = serde_json::to_string_pretty(self)?;
268		Ok(content)
269	}
270
271	/// Walks through all properties of a JSON value tree and calls the callback function on each property.
272	///
273	/// - The callback signature is `(parent_map, property_name) -> bool`.
274	///   - Return `false` from the callback to stop the traversal; return `true` to continue.
275	///
276	/// Returns:
277	/// - `true` if the traversal completed to the end without being stopped early.
278	/// - `false` if the traversal was stopped early because the callback returned `false`.
279	fn x_walk<F>(&mut self, mut callback: F) -> bool
280	where
281		F: FnMut(&mut Map<String, Value>, &str) -> bool,
282	{
283		let mut queue = VecDeque::new();
284		queue.push_back(self);
285
286		while let Some(current) = queue.pop_front() {
287			if let Value::Object(map) = current {
288				// Call the callback for each property name in the current map
289				for key in map.keys().cloned().collect::<Vec<_>>() {
290					let res = callback(map, &key);
291					if !res {
292						return false;
293					}
294				}
295
296				// Add all nested objects and arrays to the queue for further processing
297				for value in map.values_mut() {
298					if value.is_object() || value.is_array() {
299						queue.push_back(value);
300					}
301				}
302			} else if let Value::Array(arr) = current {
303				// If the current value is an array, add its elements to the queue
304				for value in arr.iter_mut() {
305					if value.is_object() || value.is_array() {
306						queue.push_back(value);
307					}
308				}
309			}
310		}
311		true
312	}
313}
314
315// region:    --- Error
316type Result<T> = core::result::Result<T, JsonValueExtError>;
317
318#[derive(Debug, derive_more::From)]
319pub enum JsonValueExtError {
320	Custom(String),
321
322	PropertyNotFound(String),
323
324	PropertyValueNotOfType {
325		name: String,
326		not_of_type: &'static str,
327	},
328
329	// -- AsType errors
330	ValueNotOfType(&'static str),
331
332	#[from]
333	SerdeJson(serde_json::Error),
334}
335
336impl JsonValueExtError {
337	pub(crate) fn custom(val: impl std::fmt::Display) -> Self {
338		Self::Custom(val.to_string())
339	}
340}
341
342// region:    --- Error Boilerplate
343
344impl core::fmt::Display for JsonValueExtError {
345	fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::result::Result<(), core::fmt::Error> {
346		write!(fmt, "{self:?}")
347	}
348}
349
350impl std::error::Error for JsonValueExtError {}
351
352// endregion: --- Error Boilerplate
353
354// endregion: --- Error