nojson/lib.rs
1//! A flexible Rust JSON library with no dependencies and no macros.
2//!
3//! `nojson` is a flexible and ergonomic JSON library for Rust that offers a balance between the type-safety of Rust and the dynamic nature of JSON.
4//! Unlike [`serde`](https://crates.io/crates/serde), which typically requires one-to-one mapping between Rust types and JSON structures (or other serialization formats),
5//! `nojson` provides a toolbox approach that allows you to leverage both type-level programming and imperative code flexibility.
6//!
7//! ## Features
8//!
9//! - **No strict one-to-one type mapping required** - Mix type-level programming with imperative flexibility as needed
10//! - **Clean parsing error messages** with position information for better debugging
11//! - **Customizable validation** - Add application-specific validation rules with proper error context
12//! - **Flexible formatting options** including pretty-printing with customizable indentation
13//! - **Low-level access** to the JSON structure when needed
14//! - **High-level conveniences** for common JSON operations
15//!
16//! ## Core Design Principles
17//!
18//! - A toolbox rather than a monolithic framework
19//! - Gain the benefits of both type-level programming and imperative code
20//! - Easy to add custom validations with rich error context
21//! - Error messages that precisely indicate the problematic position in the JSON text
22//!
23//! ## Getting Started
24//!
25//! ### Parsing JSON with Strong Typing
26//!
27//! The [`Json<T>`] wrapper allows parsing JSON text into Rust types that implement `TryFrom<RawJsonValue<'_, '_>>`:
28//!
29//! ```
30//! use nojson::Json;
31//!
32//! fn main() -> Result<(), nojson::JsonParseError> {
33//! // Parse a JSON array into a typed Rust array
34//! let text = "[1, null, 2]";
35//! let value: Json<[Option<u32>; 3]> = text.parse()?;
36//! assert_eq!(value.0, [Some(1), None, Some(2)]);
37//! Ok(())
38//! }
39//! ```
40//!
41//! ### Generating JSON
42//!
43//! The [`DisplayJson`] trait allows converting Rust types to JSON:
44//!
45//! ```
46//! use nojson::Json;
47//!
48//! // Generate a JSON array from a Rust array
49//! let value = [Some(1), None, Some(2)];
50//! assert_eq!(Json(value).to_string(), "[1,null,2]");
51//! ```
52//!
53//! ### In-place JSON Generation with Formatting
54//!
55//! The [`json()`] function provides a convenient way to generate JSON with custom formatting:
56//!
57//! ```
58//! use nojson::json;
59//!
60//! // Compact JSON
61//! let compact = json(|f| f.value([1, 2, 3]));
62//! assert_eq!(compact.to_string(), "[1,2,3]");
63//!
64//! // Pretty-printed JSON with custom indentation
65//! let pretty = json(|f| {
66//! f.set_indent_size(2);
67//! f.set_spacing(true);
68//! f.array(|f| {
69//! f.element(1)?;
70//! f.element(2)?;
71//! f.element(3)
72//! })
73//! });
74//!
75//! assert_eq!(
76//! format!("\n{}", pretty),
77//! r#"
78//! [
79//! 1,
80//! 2,
81//! 3
82//! ]"#
83//! );
84//! ```
85//!
86//! ### Custom Types
87//!
88//! Implementing [`DisplayJson`] and `TryFrom<RawJsonValue<'_, '_>>` for your own types:
89//!
90//! ```
91//! use nojson::{DisplayJson, Json, JsonFormatter, JsonParseError, RawJsonValue};
92//!
93//! struct Person {
94//! name: String,
95//! age: u32,
96//! }
97//!
98//! impl DisplayJson for Person {
99//! fn fmt(&self, f: &mut JsonFormatter<'_, '_>) -> std::fmt::Result {
100//! f.object(|f| {
101//! f.member("name", &self.name)?;
102//! f.member("age", self.age)
103//! })
104//! }
105//! }
106//!
107//! impl<'text, 'raw> TryFrom<RawJsonValue<'text, 'raw>> for Person {
108//! type Error = JsonParseError;
109//!
110//! fn try_from(value: RawJsonValue<'text, 'raw>) -> Result<Self, Self::Error> {
111//! let name = value.to_member("name")?.required()?;
112//! let age = value.to_member("age")?.required()?;
113//! Ok(Person {
114//! name: name.try_into()?,
115//! age: age.try_into()?,
116//! })
117//! }
118//! }
119//!
120//! fn main() -> Result<(), JsonParseError> {
121//! // Parse JSON to Person
122//! let json_text = r#"{"name":"Alice","age":30}"#;
123//! let person: Json<Person> = json_text.parse()?;
124//!
125//! // Generate JSON from Person
126//! assert_eq!(Json(&person.0).to_string(), json_text);
127//!
128//! Ok(())
129//! }
130//! ```
131//!
132//! ## Advanced Features
133//!
134//! ### Custom Validations
135//!
136//! You can add custom validations using [`RawJsonValue::invalid()`]:
137//!
138//! ```
139//! use nojson::{JsonParseError, RawJson, RawJsonValue};
140//!
141//! fn parse_positive_number(text: &str) -> Result<u32, JsonParseError> {
142//! let json = RawJson::parse(text)?;
143//! let raw_value = json.value();
144//!
145//! let num: u32 = raw_value.as_number_str()?
146//! .parse()
147//! .map_err(|e| raw_value.invalid(e))?;
148//!
149//! if num == 0 {
150//! return Err(raw_value.invalid("Expected a positive number, got 0"));
151//! }
152//!
153//! Ok(num)
154//! }
155//! ```
156//!
157//! ### Error Handling with Context
158//!
159//! Rich error information helps with debugging:
160//!
161//! ```
162//! use nojson::{JsonParseError, RawJson};
163//!
164//! let text = r#"{"invalid": 123e++}"#;
165//! let result = RawJson::parse(text);
166//!
167//! if let Err(error) = result {
168//! println!("Error: {}", error);
169//!
170//! // Get line and column information
171//! if let Some((line, column)) = error.get_line_and_column_numbers(text) {
172//! println!("At line {}, column {}", line, column);
173//! }
174//!
175//! // Get the full line with the error
176//! if let Some(line_text) = error.get_line(text) {
177//! println!("Line content: {}", line_text);
178//! }
179//! }
180//! ```
181#![warn(missing_docs)]
182
183mod display_json;
184mod format;
185mod kind;
186mod parse;
187mod parse_error;
188mod raw;
189mod try_from_impls;
190
191use std::{fmt::Display, str::FromStr};
192
193pub use display_json::DisplayJson;
194pub use format::{JsonArrayFormatter, JsonFormatter, JsonObjectFormatter};
195pub use kind::JsonValueKind;
196pub use raw::{JsonParseError, RawJson, RawJsonMember, RawJsonValue};
197
198/// A marker struct that enables JSON parsing and generation through the [`FromStr`] and [`Display`] traits.
199///
200/// This provides a convenient way to work with JSON, but if you need more fine-grained control,
201/// consider using [`RawJson`] (for JSON parsing) and [`json()`] (for JSON generation) instead.
202///
203/// # Examples
204///
205/// Parsing JSON text:
206/// ```
207/// use nojson::Json;
208///
209/// # fn main() -> Result<(), nojson::JsonParseError> {
210/// // Since the `[Option<u32>; 3]` type implements `TryFrom<RawJsonValue<'_, '_>>`,
211/// // you can use the `std::str::parse()` method to parse JSON by wrapping the type with `Json`.
212/// let text = "[1, null, 2]";
213/// let value: Json<[Option<u32>; 3]> = text.parse()?;
214/// assert_eq!(value.0, [Some(1), None, Some(2)]);
215/// # Ok(())
216/// # }
217/// ```
218///
219/// Generating JSON from a Rust type:
220/// ```
221/// use nojson::Json;
222///
223/// # fn main() -> Result<(), nojson::JsonParseError> {
224/// // Since the `[Option<u32>; 3]` type also implements the `DisplayJson` trait,
225/// // you can use the `std::fmt::Display::to_string()` method to
226/// // generate JSON by wrapping the type with `Json`.
227/// let value = [Some(1), None, Some(2)];
228/// assert_eq!(Json(value).to_string(), "[1,null,2]");
229/// # Ok(())
230/// # }
231/// ```
232#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
233pub struct Json<T>(#[allow(missing_docs)] pub T);
234
235impl<T: DisplayJson> Display for Json<T> {
236 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237 let mut fmt = JsonFormatter::new(f);
238 self.0.fmt(&mut fmt)?;
239 Ok(())
240 }
241}
242
243impl<T> FromStr for Json<T>
244where
245 T: for<'text, 'raw> TryFrom<RawJsonValue<'text, 'raw>, Error = JsonParseError>,
246{
247 type Err = JsonParseError;
248
249 fn from_str(s: &str) -> Result<Self, Self::Err> {
250 let raw = RawJson::parse(s)?;
251 raw.value().try_into().map(Self)
252 }
253}
254
255/// Similiar to [`Json`], but can be used for pretty-printing and in-place JSON generation purposes.
256///
257/// # Examples
258///
259/// ## Basic usage
260///
261/// ```
262/// use nojson::json;
263///
264/// // Standard JSON serialization (compact)
265/// let compact = json(|f| f.value([1, 2, 3]));
266/// assert_eq!(compact.to_string(), "[1,2,3]");
267/// ```
268///
269/// ## Pretty printing with custom indentation
270///
271/// ```
272/// use nojson::json;
273///
274/// // Pretty-printed JSON with 2-space indentation
275/// let pretty = json(|f| {
276/// f.set_indent_size(2);
277/// f.set_spacing(true);
278/// f.value([1, 2, 3])
279/// });
280///
281/// assert_eq!(
282/// format!("\n{}", pretty),
283/// r#"
284/// [
285/// 1,
286/// 2,
287/// 3
288/// ]"#
289/// );
290/// ```
291///
292/// ## Mixing formatting styles
293///
294/// ```
295/// use nojson::{json, DisplayJson};
296///
297/// // You can nest formatters with different settings
298/// let mixed = json(|f| {
299/// f.set_indent_size(2);
300/// f.set_spacing(true);
301/// f.value([
302/// &vec![1] as &dyn DisplayJson,
303/// &json(|f| {
304/// f.set_indent_size(0);
305/// f.value(vec![2, 3])
306/// }),
307/// ])
308/// });
309///
310/// assert_eq!(
311/// format!("\n{}", mixed),
312/// r#"
313/// [
314/// [
315/// 1
316/// ],
317/// [2, 3]
318/// ]"#
319/// );
320/// ```
321pub fn json<F>(f: F) -> impl DisplayJson + Display
322where
323 F: Fn(&mut JsonFormatter<'_, '_>) -> std::fmt::Result,
324{
325 InplaceJson(f)
326}
327
328struct InplaceJson<F>(F);
329
330impl<F> Display for InplaceJson<F>
331where
332 F: Fn(&mut JsonFormatter<'_, '_>) -> std::fmt::Result,
333{
334 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335 write!(f, "{}", Json(self))
336 }
337}
338
339impl<F> DisplayJson for InplaceJson<F>
340where
341 F: Fn(&mut JsonFormatter<'_, '_>) -> std::fmt::Result,
342{
343 fn fmt(&self, f: &mut JsonFormatter<'_, '_>) -> std::fmt::Result {
344 self.0(f)
345 }
346}