Skip to main content

value_ext/json/
json_value_ext.rs

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