Skip to main content

json_steroids/
lib.rs

1//! # json-steroids
2//!
3//! A high-performance, zero-copy JSON parsing library for Rust.
4//!
5//! ## Features
6//! - Zero-copy string parsing where possible
7//! - Efficient SIMD-friendly parsing (when available)
8//! - Derive macros for automatic serialization/deserialization
9//! - Minimal allocations
10//!
11//! ## Example
12//!
13//! ```rust,ignore
14//! use json_steroids::{Json, JsonSerialize, JsonDeserialize, to_string, from_str};
15//!
16//! #[derive(Debug, Json, PartialEq)]
17//! struct Person {
18//!     name: String,
19//!     age: u32,
20//! }
21//!
22//! let person = Person { name: "Alice".to_string(), age: 30 };
23//! let json = to_string(&person);
24//! let parsed: Person = from_str(&json).unwrap();
25//! assert_eq!(person, parsed);
26//! ```
27
28mod error;
29mod parser;
30mod traits;
31mod value;
32pub mod writer;
33
34pub use error::{JsonError, Result};
35pub use parser::JsonParser;
36pub use traits::{JsonDeserialize, JsonSerialize};
37pub use value::JsonValue;
38pub use writer::JsonWriter;
39
40// Re-export derive macros
41pub use json_steroids_derive::{Json, JsonDeserialize, JsonSerialize};
42
43/// Serialize a value to a JSON string
44#[inline]
45pub fn to_string<T: JsonSerialize>(value: &T) -> String {
46    let mut writer = JsonWriter::new();
47    value.json_serialize(&mut writer);
48    writer.into_string()
49}
50
51/// Serialize a value to a JSON string with pretty printing
52#[inline]
53pub fn to_string_pretty<T: JsonSerialize>(value: &T) -> String {
54    let mut writer = JsonWriter::with_indent(2);
55    value.json_serialize(&mut writer);
56    writer.into_string()
57}
58
59/// Deserialize a value from a JSON string
60#[inline]
61pub fn from_str<'de, T: JsonDeserialize<'de>>(s: &'de str) -> Result<T> {
62    let mut parser = JsonParser::new(s);
63    T::json_deserialize(&mut parser)
64}
65
66/// Deserialize a value from JSON bytes
67#[inline]
68pub fn from_bytes<'de, T: JsonDeserialize<'de>>(bytes: &'de [u8]) -> Result<T> {
69    let s = std::str::from_utf8(bytes).map_err(|_| JsonError::InvalidUtf8)?;
70    from_str(s)
71}
72
73/// Parse JSON into a dynamic value
74#[inline]
75pub fn parse(s: &str) -> Result<JsonValue> {
76    let mut parser = JsonParser::new(s);
77    parser.parse_value()
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[derive(Debug, PartialEq, Json)]
85    struct SimpleStruct {
86        name: String,
87        value: i64,
88        active: bool,
89    }
90
91    #[derive(Debug, PartialEq, Json)]
92    struct NestedStruct {
93        id: u32,
94        data: SimpleStruct,
95    }
96
97    #[derive(Debug, PartialEq, Json)]
98    struct WithOption {
99        required: String,
100        optional: Option<i32>,
101    }
102
103    #[derive(Debug, PartialEq, Json)]
104    struct WithVec {
105        items: Vec<i32>,
106    }
107
108    #[derive(Debug, PartialEq, Json)]
109    enum Status {
110        Active,
111        Inactive,
112        Pending,
113    }
114
115    #[derive(Debug, PartialEq, Json)]
116    enum Message {
117        Text(String),
118        Number(i64),
119        Data { x: i32, y: i32 },
120    }
121
122    #[test]
123    fn test_simple_struct_roundtrip() {
124        let original = SimpleStruct {
125            name: "test".to_string(),
126            value: 42,
127            active: true,
128        };
129        let json = to_string(&original);
130        let parsed: SimpleStruct = from_str(&json).unwrap();
131        assert_eq!(original, parsed);
132    }
133
134    #[test]
135    fn test_nested_struct_roundtrip() {
136        let original = NestedStruct {
137            id: 1,
138            data: SimpleStruct {
139                name: "nested".to_string(),
140                value: 100,
141                active: false,
142            },
143        };
144        let json = to_string(&original);
145        let parsed: NestedStruct = from_str(&json).unwrap();
146        assert_eq!(original, parsed);
147    }
148
149    #[test]
150    fn test_option_some() {
151        let original = WithOption {
152            required: "hello".to_string(),
153            optional: Some(42),
154        };
155        let json = to_string(&original);
156        let parsed: WithOption = from_str(&json).unwrap();
157        assert_eq!(original, parsed);
158    }
159
160    #[test]
161    fn test_option_none() {
162        let original = WithOption {
163            required: "hello".to_string(),
164            optional: None,
165        };
166        let json = to_string(&original);
167        let parsed: WithOption = from_str(&json).unwrap();
168        assert_eq!(original, parsed);
169    }
170
171    #[test]
172    fn test_vec_roundtrip() {
173        let original = WithVec {
174            items: vec![1, 2, 3, 4, 5],
175        };
176        let json = to_string(&original);
177        let parsed: WithVec = from_str(&json).unwrap();
178        assert_eq!(original, parsed);
179    }
180
181    #[test]
182    fn test_unit_enum() {
183        let original = Status::Active;
184        let json = to_string(&original);
185        assert_eq!(json, r#""Active""#);
186        let parsed: Status = from_str(&json).unwrap();
187        assert_eq!(original, parsed);
188    }
189
190    #[test]
191    fn test_tuple_enum() {
192        let original = Message::Text("hello".to_string());
193        let json = to_string(&original);
194        let parsed: Message = from_str(&json).unwrap();
195        assert_eq!(original, parsed);
196    }
197
198    #[test]
199    fn test_struct_enum() {
200        let original = Message::Data { x: 10, y: 20 };
201        let json = to_string(&original);
202        let parsed: Message = from_str(&json).unwrap();
203        assert_eq!(original, parsed);
204    }
205
206    #[test]
207    fn test_primitives() {
208        assert_eq!(to_string(&42i32), "42");
209        assert_eq!(to_string(&3.14f64), "3.14");
210        assert_eq!(to_string(&true), "true");
211        assert_eq!(to_string(&"hello"), r#""hello""#);
212    }
213
214    #[test]
215    fn test_parse_primitives() {
216        assert_eq!(from_str::<i32>("42").unwrap(), 42);
217        assert_eq!(from_str::<f64>("3.14").unwrap(), 3.14);
218        assert_eq!(from_str::<bool>("true").unwrap(), true);
219        assert_eq!(from_str::<String>(r#""hello""#).unwrap(), "hello");
220    }
221
222    #[test]
223    fn test_escape_sequences() {
224        let s = "hello\nworld\t\"test\"\\path";
225        let json = to_string(&s);
226        let parsed: String = from_str(&json).unwrap();
227        assert_eq!(s, parsed);
228    }
229
230    #[test]
231    fn test_unicode() {
232        let s = "こんにちは 🦀 emoji";
233        let json = to_string(&s);
234        let parsed: String = from_str(&json).unwrap();
235        assert_eq!(s, parsed);
236    }
237
238    #[test]
239    fn test_dynamic_value() {
240        let json = r#"{"name": "test", "values": [1, 2, 3], "nested": {"a": true}}"#;
241        let value = parse(json).unwrap();
242
243        assert!(value.is_object());
244        assert_eq!(value["name"].as_str(), Some("test"));
245        assert!(value["values"].is_array());
246        assert_eq!(value["nested"]["a"].as_bool(), Some(true));
247    }
248
249    #[test]
250    fn test_pretty_print() {
251        let original = SimpleStruct {
252            name: "test".to_string(),
253            value: 42,
254            active: true,
255        };
256        let json = to_string_pretty(&original);
257        assert!(json.contains('\n'));
258        let parsed: SimpleStruct = from_str(&json).unwrap();
259        assert_eq!(original, parsed);
260    }
261}