Skip to main content

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	/// Merges another JSON object into this one (shallow merge).
77	/// - If `self` is not an Object, returns error.
78	/// - If `other` is Null, does nothing.
79	/// - If `other` is not an Object, returns error.
80	fn x_merge(&mut self, other: Value) -> Result<()>;
81
82	/// Walks through all properties in the JSON value tree and calls the callback function on each.
83	/// - The callback signature is `(parent_map, property_name) -> bool`.
84	///   - Returns `false` to stop the traversal; returns `true` to continue.
85	///
86	/// Returns:
87	/// - `true` if the traversal completes without stopping early.
88	/// - `false` if the traversal was stopped early because the callback returned `false`.
89	fn x_walk<F>(&mut self, callback: F) -> bool
90	where
91		F: FnMut(&mut Map<String, Value>, &str) -> bool;
92
93	/// Returns a pretty-printed string representation of the JSON value.
94	fn x_pretty(&self) -> Result<String>;
95}
96
97impl JsonValueExt for Value {
98	fn x_new_object() -> Value {
99		Value::Object(Map::new())
100	}
101
102	fn x_contains<T: DeserializeOwned>(&self, name_or_pointer: &str) -> bool {
103		if name_or_pointer.starts_with('/') {
104			self.pointer(name_or_pointer).is_some()
105		} else {
106			self.get(name_or_pointer).is_some()
107		}
108	}
109
110	fn x_get<T: DeserializeOwned>(&self, name_or_pointer: &str) -> Result<T> {
111		let value = if name_or_pointer.starts_with('/') {
112			self.pointer(name_or_pointer)
113				.ok_or_else(|| JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))?
114		} else {
115			self.get(name_or_pointer)
116				.ok_or_else(|| JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))?
117		};
118
119		let value: T =
120			serde_json::from_value(value.clone())
121				.map_err(JsonValueExtError::from)
122				.map_err(|err| match err {
123					JsonValueExtError::ValueNotOfType(not_of_type) => JsonValueExtError::PropertyValueNotOfType {
124						name: name_or_pointer.to_string(),
125						not_of_type,
126					},
127					other => other,
128				})?;
129
130		Ok(value)
131	}
132
133	fn x_get_as<'a, T: AsType<'a>>(&'a self, name_or_pointer: &str) -> Result<T> {
134		let value = if name_or_pointer.starts_with('/') {
135			self.pointer(name_or_pointer)
136				.ok_or_else(|| JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))?
137		} else {
138			self.get(name_or_pointer)
139				.ok_or_else(|| JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))?
140		};
141
142		T::from_value(value).map_err(|err| match err {
143			JsonValueExtError::ValueNotOfType(not_of_type) => JsonValueExtError::PropertyValueNotOfType {
144				name: name_or_pointer.to_string(),
145				not_of_type,
146			},
147			other => other,
148		})
149	}
150
151	fn x_take<T: DeserializeOwned>(&mut self, name_or_pointer: &str) -> Result<T> {
152		let value = if name_or_pointer.starts_with('/') {
153			self.pointer_mut(name_or_pointer)
154				.map(Value::take)
155				.ok_or_else(|| JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))?
156		} else {
157			self.get_mut(name_or_pointer)
158				.map(Value::take)
159				.ok_or_else(|| JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))?
160		};
161
162		let value: T = serde_json::from_value(value)?;
163		Ok(value)
164	}
165
166	fn x_remove<T: DeserializeOwned>(&mut self, name_or_pointer: &str) -> Result<T> {
167		if !name_or_pointer.starts_with('/') {
168			match self {
169				Value::Object(map) => {
170					let removed = map
171						.remove(name_or_pointer)
172						.ok_or_else(|| JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))?;
173					let value: T = serde_json::from_value(removed)?;
174					Ok(value)
175				}
176				_ => Err(JsonValueExtError::custom("Value is not an Object; cannot x_remove")),
177			}
178		} else {
179			let parts: Vec<&str> = name_or_pointer.split('/').skip(1).collect();
180			if parts.is_empty() {
181				return Err(JsonValueExtError::custom("Invalid path"));
182			}
183			let mut current = self;
184			for &part in &parts[..parts.len() - 1] {
185				match current {
186					Value::Object(map) => {
187						current = map
188							.get_mut(part)
189							.ok_or_else(|| JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))?;
190					}
191					Value::Array(arr) => {
192						let index: usize = part
193							.parse()
194							.map_err(|_| JsonValueExtError::custom("Invalid array index in pointer"))?;
195						if index < arr.len() {
196							current = &mut arr[index];
197						} else {
198							return Err(JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()));
199						}
200					}
201					_ => return Err(JsonValueExtError::custom("Path does not point to an Object or Array")),
202				}
203			}
204			let last_part = parts
205				.last()
206				.ok_or_else(|| JsonValueExtError::custom("Last element not found"))?;
207			match current {
208				Value::Object(map) => {
209					let removed = map
210						.remove(*last_part)
211						.ok_or_else(|| JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))?;
212					let value: T = serde_json::from_value(removed)?;
213					Ok(value)
214				}
215				Value::Array(arr) => {
216					let index: usize = last_part
217						.parse()
218						.map_err(|_| JsonValueExtError::custom("Invalid array index in pointer"))?;
219					if index < arr.len() {
220						let removed = arr.remove(index);
221						let value: T = serde_json::from_value(removed)?;
222						Ok(value)
223					} else {
224						Err(JsonValueExtError::PropertyNotFound(name_or_pointer.to_string()))
225					}
226				}
227				_ => Err(JsonValueExtError::custom("Path does not point to an Object or Array")),
228			}
229		}
230	}
231
232	fn x_insert<T: Serialize>(&mut self, name_or_pointer: &str, value: T) -> Result<()> {
233		let new_value = serde_json::to_value(value)?;
234
235		if !name_or_pointer.starts_with('/') {
236			match self {
237				Value::Object(map) => {
238					map.insert(name_or_pointer.to_string(), new_value);
239					Ok(())
240				}
241				_ => Err(JsonValueExtError::custom("Value is not an Object; cannot x_insert")),
242			}
243		} else {
244			let parts: Vec<&str> = name_or_pointer.split('/').skip(1).collect();
245			let mut current = self;
246
247			// -- Add the eventual missing parents
248			for &part in &parts[..parts.len() - 1] {
249				match current {
250					Value::Object(map) => {
251						current = map.entry(part).or_insert_with(|| json!({}));
252					}
253					_ => return Err(JsonValueExtError::custom("Path does not point to an Object")),
254				}
255			}
256
257			// -- Set the value at the last element
258			if let Some(&last_part) = parts.last() {
259				match current {
260					Value::Object(map) => {
261						map.insert(last_part.to_string(), new_value);
262						Ok(())
263					}
264					_ => Err(JsonValueExtError::custom("Path does not point to an Object")),
265				}
266			} else {
267				Err(JsonValueExtError::custom("Invalid path"))
268			}
269		}
270	}
271
272	fn x_merge(&mut self, other: Value) -> Result<()> {
273		if other.is_null() {
274			return Ok(());
275		}
276
277		let other_map = match other {
278			Value::Object(map) => map,
279			_ => return Err(JsonValueExtError::custom("Other value is not an Object; cannot x_merge")),
280		};
281
282		match self {
283			Value::Object(map) => {
284				map.extend(other_map);
285				Ok(())
286			}
287			_ => Err(JsonValueExtError::custom("Value is not an Object; cannot x_merge")),
288		}
289	}
290
291	fn x_pretty(&self) -> Result<String> {
292		let content = serde_json::to_string_pretty(self)?;
293		Ok(content)
294	}
295
296	/// Walks through all properties of a JSON value tree and calls the callback function on each property.
297	///
298	/// - The callback signature is `(parent_map, property_name) -> bool`.
299	///   - Return `false` from the callback to stop the traversal; return `true` to continue.
300	///
301	/// Returns:
302	/// - `true` if the traversal completed to the end without being stopped early.
303	/// - `false` if the traversal was stopped early because the callback returned `false`.
304	fn x_walk<F>(&mut self, mut callback: F) -> bool
305	where
306		F: FnMut(&mut Map<String, Value>, &str) -> bool,
307	{
308		let mut queue = VecDeque::new();
309		queue.push_back(self);
310
311		while let Some(current) = queue.pop_front() {
312			if let Value::Object(map) = current {
313				// Call the callback for each property name in the current map
314				for key in map.keys().cloned().collect::<Vec<_>>() {
315					let res = callback(map, &key);
316					if !res {
317						return false;
318					}
319				}
320
321				// Add all nested objects and arrays to the queue for further processing
322				for value in map.values_mut() {
323					if value.is_object() || value.is_array() {
324						queue.push_back(value);
325					}
326				}
327			} else if let Value::Array(arr) = current {
328				// If the current value is an array, add its elements to the queue
329				for value in arr.iter_mut() {
330					if value.is_object() || value.is_array() {
331						queue.push_back(value);
332					}
333				}
334			}
335		}
336		true
337	}
338}
339
340// region:    --- Error
341type Result<T> = core::result::Result<T, JsonValueExtError>;
342
343#[derive(Debug, derive_more::From)]
344pub enum JsonValueExtError {
345	Custom(String),
346
347	PropertyNotFound(String),
348
349	PropertyValueNotOfType {
350		name: String,
351		not_of_type: &'static str,
352	},
353
354	// -- AsType errors
355	ValueNotOfType(&'static str),
356
357	#[from]
358	SerdeJson(serde_json::Error),
359}
360
361impl JsonValueExtError {
362	pub(crate) fn custom(val: impl std::fmt::Display) -> Self {
363		Self::Custom(val.to_string())
364	}
365}
366
367// region:    --- Error Boilerplate
368
369impl core::fmt::Display for JsonValueExtError {
370	fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::result::Result<(), core::fmt::Error> {
371		write!(fmt, "{self:?}")
372	}
373}
374
375impl std::error::Error for JsonValueExtError {}
376
377// endregion: --- Error Boilerplate
378
379// endregion: --- Error