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 a value of a specified type `T` from a JSON object using either a direct name or a pointer path. (will do a new allocation)
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_insert`**: Inserts a value of type `T` into a JSON object at the specified name or pointer path, creating any missing objects along the way.
23/// - **`x_walk`**: Traverses all properties within the JSON value tree, applying a user-provided callback function on each property.
24/// - **`x_pretty`**: Returns a pretty-printed string representation of the JSON value.
25///
26/// # Usage
27///
28/// This trait is intended to be used with `serde_json::Value` objects. It is particularly
29/// useful when you need to manipulate JSON structures dynamically or when the structure
30/// of the JSON is not known at compile time.
31///
32/// ```rust
33/// use serde_json::{Value, Map};
34/// use serde::de::DeserializeOwned;
35/// use serde::Serialize;
36/// use your_crate::JsonValueExt;
37///
38/// fn example_usage(json: &mut Value) -> Result<(), Box<dyn std::error::Error>> {
39///     // Get a value from JSON
40///     let name: String = json.x_get("/name")?;
41///
42///     // Take a value from JSON, replacing it with `Null`
43///     let age: u32 = json.x_take("age")?;
44///
45///     // Insert a new value into JSON
46///     json.x_insert("city", "New York")?;
47///
48///     // Walk through the JSON properties
49///     json.x_walk(|parent_map, property_name| {
50///         println!("Property: {}", property_name);
51///         true // Continue traversal
52///     });
53///
54///     // Get a pretty-printed JSON string
55///     let pretty_json = json.x_pretty()?;
56///     println!("{}", pretty_json);
57///
58///     Ok(())
59/// }
60/// ```
61///
62/// This trait enhances the `serde_json::Value` API by adding more type-safe and convenient
63/// methods for manipulating JSON data in Rust.
64pub trait JsonValueExt {
65	fn x_new_object() -> Value;
66
67	fn x_contains<T: DeserializeOwned>(&self, name_or_pointer: &str) -> bool;
68
69	/// Returns an owned type `T` for a given name or pointer path.
70	/// - `name_or_pointer`: Can be a direct name or a pointer path (if it starts with '/').
71	fn x_get<T: DeserializeOwned>(&self, name_or_pointer: &str) -> Result<T>;
72
73	/// Returns a reference of type `T` (or value for copy type) for a given name or pointer path.
74	/// Use this one over `x_get` to avoid string allocation and get only the &str
75	/// - `name_or_pointer`: Can be a direct name or a pointer path (if it starts with '/').
76	fn x_get_as<'a, T: AsType<'a>>(&'a self, name_or_pointer: &str) -> Result<T>;
77
78	/// Returns a &str if present (shortcut for `x_get_as::<&str>(...)`)
79	fn x_get_str(&self, name_or_pointer: &str) -> Result<&str> {
80		self.x_get_as(name_or_pointer)
81	}
82
83	/// Returns an i64 if present (shortcut for `x_get_as::<i64>(...)`)
84	fn x_get_i64(&self, name_or_pointer: &str) -> Result<i64> {
85		self.x_get_as(name_or_pointer)
86	}
87
88	/// Returns an f64 if present (shortcut for `x_get_as::<f64>(...)`)
89	fn x_get_f64(&self, name_or_pointer: &str) -> Result<f64> {
90		self.x_get_as(name_or_pointer)
91	}
92
93	/// Returns a bool if present (shortcut for `x_get_as::<bool>(...)`)
94	fn x_get_bool(&self, name_or_pointer: &str) -> Result<bool> {
95		self.x_get_as(name_or_pointer)
96	}
97
98	/// Takes the value at the specified name or pointer path and replaces it with `Null`.
99	/// - `name_or_pointer`: Can be a direct name or a pointer path (if it starts with '/').
100	fn x_take<T: DeserializeOwned>(&mut self, name_or_pointer: &str) -> Result<T>;
101
102	/// Inserts a new value of type `T` at the specified name or pointer path.
103	/// This method creates missing `Value::Object` entries as needed.
104	/// - `name_or_pointer`: Can be a direct name or a pointer path (if it starts with '/').
105	fn x_insert<T: Serialize>(&mut self, name_or_pointer: &str, value: T) -> Result<()>;
106
107	/// Walks through all properties in the JSON value tree and calls the callback function on each.
108	/// - The callback signature is `(parent_map, property_name) -> bool`.
109	///   - Returns `false` to stop the traversal; returns `true` to continue.
110	///
111	/// Returns:
112	/// - `true` if the traversal completes without stopping early.
113	/// - `false` if the traversal is stopped early because the callback returned `false`.
114	fn x_walk<F>(&mut self, callback: F) -> bool
115	where
116		F: FnMut(&mut Map<String, Value>, &str) -> bool;
117
118	/// Returns a pretty-printed string representation of the JSON value.
119	fn x_pretty(&self) -> Result<String>;
120}
121
122impl JsonValueExt for Value {
123	fn x_new_object() -> Value {
124		Value::Object(Map::new())
125	}
126
127	fn x_contains<T: DeserializeOwned>(&self, name_or_pointer: &str) -> bool {
128		if name_or_pointer.starts_with('/') {
129			self.pointer(name_or_pointer).is_some()
130		} else {
131			self.get(name_or_pointer).is_some()
132		}
133	}
134
135	fn x_get<T: DeserializeOwned>(&self, name_or_pointer: &str) -> Result<T> {
136		let value = if name_or_pointer.starts_with('/') {
137			self.pointer(name_or_pointer)
138				.ok_or_else(|| JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))?
139		} else {
140			self.get(name_or_pointer)
141				.ok_or_else(|| JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))?
142		};
143
144		let value: T = serde_json::from_value(value.clone())
145			// first map_err to get the JsonValueExtError
146			.map_err(JsonValueExtError::from)
147			// and then, try to add more property information if possible
148			.map_err(|err| match err {
149				JsonValueExtError::ValueNotOfType(not_of_type) => JsonValueExtError::PropertyValueNotOfType {
150					name: name_or_pointer.to_string(),
151					not_of_type,
152				},
153				other => other,
154			})?;
155
156		Ok(value)
157	}
158
159	fn x_get_as<'a, T: AsType<'a>>(&'a self, name_or_pointer: &str) -> Result<T> {
160		let value = if name_or_pointer.starts_with('/') {
161			self.pointer(name_or_pointer)
162				.ok_or_else(|| JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))?
163		} else {
164			self.get(name_or_pointer)
165				.ok_or_else(|| JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))?
166		};
167
168		// add more error context when possible
169		T::from_value(value).map_err(|err| match err {
170			JsonValueExtError::ValueNotOfType(not_of_type) => JsonValueExtError::PropertyValueNotOfType {
171				name: name_or_pointer.to_string(),
172				not_of_type,
173			},
174			other => other,
175		})
176	}
177
178	fn x_take<T: DeserializeOwned>(&mut self, name_or_pointer: &str) -> Result<T> {
179		let value = if name_or_pointer.starts_with('/') {
180			self.pointer_mut(name_or_pointer)
181				.map(Value::take)
182				.ok_or_else(|| JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))?
183		} else {
184			self.get_mut(name_or_pointer)
185				.map(Value::take)
186				.ok_or_else(|| JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))?
187		};
188
189		let value: T = serde_json::from_value(value)?;
190		Ok(value)
191	}
192
193	fn x_insert<T: Serialize>(&mut self, name_or_pointer: &str, value: T) -> Result<()> {
194		let new_value = serde_json::to_value(value)?;
195
196		if !name_or_pointer.starts_with('/') {
197			match self {
198				Value::Object(map) => {
199					map.insert(name_or_pointer.to_string(), new_value);
200					Ok(())
201				}
202				_ => Err(JsonValueExtError::custom("Value is not an Object; cannot x_insert")),
203			}
204		} else {
205			let parts: Vec<&str> = name_or_pointer.split('/').skip(1).collect();
206			let mut current = self;
207
208			// -- Add the eventual missing parents
209			for &part in &parts[..parts.len() - 1] {
210				match current {
211					Value::Object(map) => {
212						current = map.entry(part).or_insert_with(|| json!({}));
213					}
214					_ => return Err(JsonValueExtError::custom("Path does not point to an Object")),
215				}
216			}
217
218			// -- Set the value at the last element
219			if let Some(&last_part) = parts.last() {
220				match current {
221					Value::Object(map) => {
222						map.insert(last_part.to_string(), new_value);
223						Ok(())
224					}
225					_ => Err(JsonValueExtError::custom("Path does not point to an Object")),
226				}
227			} else {
228				Err(JsonValueExtError::custom("Invalid path"))
229			}
230		}
231	}
232
233	fn x_pretty(&self) -> Result<String> {
234		let content = serde_json::to_string_pretty(self)?;
235		Ok(content)
236	}
237
238	/// Walks through all properties of a JSON value tree and calls the callback function on each property.
239	///
240	/// - The callback signature is `(parent_map, property_name) -> bool`.
241	///   - Return `false` from the callback to stop the traversal; return `true` to continue.
242	///
243	/// Returns:
244	/// - `true` if the traversal completed to the end without being stopped early.
245	/// - `false` if the traversal was stopped early because the callback returned `false`.
246	fn x_walk<F>(&mut self, mut callback: F) -> bool
247	where
248		F: FnMut(&mut Map<String, Value>, &str) -> bool,
249	{
250		let mut queue = VecDeque::new();
251		queue.push_back(self);
252
253		while let Some(current) = queue.pop_front() {
254			if let Value::Object(map) = current {
255				// Call the callback for each property name in the current map
256				for key in map.keys().cloned().collect::<Vec<_>>() {
257					let res = callback(map, &key);
258					if !res {
259						return false;
260					}
261				}
262
263				// Add all nested objects and arrays to the queue for further processing
264				for value in map.values_mut() {
265					if value.is_object() || value.is_array() {
266						queue.push_back(value);
267					}
268				}
269			} else if let Value::Array(arr) = current {
270				// If the current value is an array, add its elements to the queue
271				for value in arr.iter_mut() {
272					if value.is_object() || value.is_array() {
273						queue.push_back(value);
274					}
275				}
276			}
277		}
278		true
279	}
280}
281
282// region:    --- Error
283type Result<T> = core::result::Result<T, JsonValueExtError>;
284
285#[derive(Debug, derive_more::From)]
286pub enum JsonValueExtError {
287	Custom(String),
288
289	PropertyNotFound(String),
290
291	PropertyValueNotOfType {
292		name: String,
293		not_of_type: &'static str,
294	},
295
296	// -- AsType errors
297	ValueNotOfType(&'static str),
298
299	#[from]
300	SerdeJson(serde_json::Error),
301}
302
303impl JsonValueExtError {
304	pub(crate) fn custom(val: impl std::fmt::Display) -> Self {
305		Self::Custom(val.to_string())
306	}
307}
308
309// region:    --- Error Boilerplate
310
311impl core::fmt::Display for JsonValueExtError {
312	fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::result::Result<(), core::fmt::Error> {
313		write!(fmt, "{self:?}")
314	}
315}
316
317impl std::error::Error for JsonValueExtError {}
318
319// endregion: --- Error Boilerplate
320
321// endregion: --- Error