json-steroids 0.2.0

Blazing fast JSON serialization and deserialization library, powered by procedural macros.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
# json-steroids 🚀

A high-performance, zero-copy JSON parsing and serialization library for Rust with derive macros for automatic implementation.

[![Rust](https://img.shields.io/badge/rust-1.70%2B-orange.svg)](https://www.rust-lang.org)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

## Features

- **Zero-copy parsing** - Strings without escape sequences are borrowed directly from input, avoiding unnecessary allocations
- **Fast serialization** - Pre-allocated buffers with efficient string escaping and number formatting
- **Derive macros** - Automatically generate serializers and deserializers for your types
- **Minimal dependencies** - Only uses `itoa` and `ryu` for fast number formatting
- **Full JSON support** - Handles all JSON types including Unicode escape sequences and surrogate pairs
- **Pretty printing** - Optional indented output for human-readable JSON
- **Dynamic values** - Parse JSON into a flexible `JsonValue` type when structure is unknown

## Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
json-steroids = "0.1.2"
```

## Quick Start

```rust
use json_steroids::{Json, to_string, from_str};

#[derive(Debug, Json, PartialEq)]
struct Person {
    name: String,
    age: u32,
    email: Option<String>,
}

fn main() {
    // Serialize
    let person = Person {
        name: "Alice".to_string(),
        age: 30,
        email: Some("alice@example.com".to_string()),
    };
    let json = to_string(&person);
    println!("{}", json);
    // Output: {"name":"Alice","age":30,"email":"alice@example.com"}

    // Deserialize
    let json_str = r#"{"name":"Bob","age":25,"email":null}"#;
    let person: Person = from_str(json_str).unwrap();
    println!("{:?}", person);
}
```

## Performance Benchmarks

Comprehensive benchmark comparison between json-steroids and serde_json. All benchmarks measured with Criterion on Apple M4 Max.

| Category | Benchmark | Description | json-steroids | serde_json | Result |
|----------|-----------|-------------|---------------|------------|--------|
| **🚀 Zero-Copy** | `borrowed_str_struct_deserialize` | Struct with 3 &str fields (zero-copy) | **35.9 ns** | 59.4 ns | ✅ **1.7x faster** |
| **🚀 Zero-Copy** | `cow_struct_deserialize` | Struct with 4 Cow<str> fields (zero-copy) | **67.1 ns** | 126.6 ns | ✅ **1.9x faster** |
| **🚀 Zero-Copy** | `zero_copy_vec_strings` | Vec of 4 Cow<str> strings (zero-copy) | **102.9 ns** | 274.6 ns | ✅ **2.7x faster** |
| **🚀 Zero-Copy** | `cow_struct_serialize` | Serialize struct with Cow<str> fields | **52.0 ns** | 95.9 ns | ✅ **1.8x faster** |
| **ðŸ“Ī Serialization** | `serialize_simple` | Simple struct (3 fields: String, i64, bool) | **21.9 ns** | 36.3 ns | ✅ **1.7x faster** |
| **ðŸ“Ī Serialization** | `serialize_complex` | Complex nested struct with arrays | **94.3 ns** | 162.3 ns | ✅ **1.7x faster** |
| **ðŸ“Ī Serialization** | `many_fields_serialize` | Struct with 15 mixed-type fields | **107.9 ns** | 203.0 ns | ✅ **1.9x faster** |
| **ðŸ“Ī Serialization** | `large_array_serialize` | Array of 1000 integers | **2.78 Ξs** | 2.92 Ξs | ✅ **1.05x faster** |
| **ðŸ“Ĩ Deserialization** | `deserialize_simple` | Simple struct (3 fields: String, i64, bool) | **41.7 ns** | 50.4 ns | ✅ **1.2x faster** |
| **ðŸ“Ĩ Deserialization** | `deserialize_complex` | Complex nested struct with arrays | **273.3 ns** | 365.5 ns | ✅ **1.3x faster** |
| **ðŸ“Ĩ Deserialization** | `many_fields_deserialize` | Struct with 15 mixed-type fields | **261.5 ns** | 352.4 ns | ✅ **1.3x faster** |
| **ðŸ“Ĩ Deserialization** | `large_array_deserialize` | Array of 1000 integers | **3.88 Ξs** | 6.48 Ξs | ✅ **1.7x faster** |
| **🔄 Round-Trip** | `roundtrip_complex` | Serialize + deserialize complex struct | **395.2 ns** | 533.8 ns | ✅ **1.4x faster** |
| **ðŸŽŊ Dynamic** | `parse_dynamic` | Parse to JsonValue (structure unknown) | **211.7 ns** | 330.5 ns | ✅ **1.6x faster** |
| **ðŸŽŊ Dynamic** | `deeply_nested_parse` | Parse deeply nested JSON (64+ levels) | **426.5 ns** | 666.2 ns | ✅ **1.6x faster** |
| **ðŸ“Ī Serialization** | `string_serialize_with_escapes` | String with escape sequences (\n, \t, etc.) | 46.4 ns | **43.4 ns** | ⚠ïļ 0.94x |
| **ðŸ“Ī Serialization** | `string_serialize_no_escapes` | Plain string without escapes | 30.3 ns | **28.8 ns** | ⚠ïļ 0.95x |

### Performance Summary

- ✅ **json-steroids wins: 14 of 17 benchmarks** (82%)
- 🚀 **1.7-2.7x faster** for zero-copy operations with `Cow<'de, str>` and `&'de str`
- ⚡ **21-88% faster** for typical serialize/deserialize workloads
- 📊 **67% faster** for large array deserialization
- ðŸŽŊ **Best use cases**: Zero-copy strings, structs, arrays, complex structures, dynamic parsing
- ⚠ïļ **serde_json wins**: String escaping (6%), simple strings (5%)

### Why Choose json-steroids?

**Optimized for real-world use cases:**
- 🚀 **Zero-copy string parsing** - Strings without escape sequences are borrowed directly (`Cow::Borrowed`)
- ðŸ“Ķ **Complex nested structures** - 20-35% faster serialization and deserialization
- 🔍 **Dynamic JSON parsing** - 60% faster when structure is unknown
- ðŸŽŊ **Type-safe numbers** - Specific methods for each integer/float type (no implicit conversions)
- ðŸ’ū **Memory efficient** - Pre-allocated buffers, minimal reallocations
- 🛠ïļ **Production ready** - Handles Unicode escapes, surrogate pairs, all JSON edge cases

> **Note**: Run `cargo bench` to measure performance on your hardware. Results may vary based on CPU architecture and workload patterns.

### Key Performance Features

- **Zero-copy string parsing** - Strings without escape sequences are borrowed directly, avoiding allocations
- **Cow<'de, str> support** - True zero-copy deserialization with `Cow::Borrowed` for strings without escapes
- **Fast number formatting** - Uses `itoa` and `ryu` for optimized integer and float serialization
- **Efficient memory management** - Pre-allocated buffers minimize reallocations
- **Optimized string escaping** - Fast-path detection for strings that don't need escaping
- **Minimal overhead** - Streamlined trait implementations with no unnecessary abstractions

### Zero-Copy Deserialization

json-steroids supports true zero-copy deserialization using `Cow<'de, str>`:

```rust
use json_steroids::from_str;
use std::borrow::Cow;

// Zero-copy: strings without escape sequences
let json = r#""hello world""#;
let result: Cow<str> = from_str(json).unwrap();
assert!(matches!(result, Cow::Borrowed(_))); // No allocation!

// In collections - zero-copy for all elements without escapes
let json = r#"["apple","banana","cherry"]"#;
let result: Vec<Cow<str>> = from_str(json).unwrap();
// All three elements are Cow::Borrowed - zero allocations!

// Automatic handling of escapes
let json = r#""hello\nworld""#;
let result: Cow<str> = from_str(json).unwrap();
assert!(matches!(result, Cow::Owned(_))); // Owned only when necessary
```

**Performance advantage**: json-steroids with `Cow<'de, str>` is ~30% faster than serde_json's zero-copy mode and ~2x faster for serialization!

### Running Benchmarks

To run benchmarks on your own system:

```bash
cargo bench
```

View the detailed HTML report:

```bash
open target/criterion/report/index.html
```

## Derive Macros

### `#[derive(Json)]`

The combined derive macro that implements both `JsonSerialize` and `JsonDeserialize`:

```rust
use json_steroids::Json;

#[derive(Json)]
struct User {
    id: u64,
    username: String,
    active: bool,
}
```

### `#[derive(JsonSerialize)]` and `#[derive(JsonDeserialize)]`

Use these when you only need one direction:

```rust
use json_steroids::{JsonSerialize, JsonDeserialize};

#[derive(JsonSerialize)]
struct LogEntry {
    timestamp: u64,
    message: String,
}

#[derive(JsonDeserialize)]
struct Config {
    host: String,
    port: u16,
}
```

### Field Renaming

Use the `#[json(rename = "...")]` attribute to customize field names in JSON:

```rust
use json_steroids::Json;

#[derive(Json)]
struct ApiResponse {
    #[json(rename = "statusCode")]
    status_code: u32,
    #[json(rename = "errorMessage")]
    error_message: Option<String>,
}
```

### Enum Support

Enums are fully supported with different representations:

```rust
use json_steroids::Json;

// Unit variants serialize as strings
#[derive(Json)]
enum Status {
    Active,    // "Active"
    Inactive,  // "Inactive"
    Pending,   // "Pending"
}

// Tuple and struct variants use object notation
#[derive(Json)]
enum Message {
    Text(String),                    // {"Text":["hello"]}
    Coordinates { x: i32, y: i32 },  // {"Coordinates":{"x":10,"y":20}}
}
```

## API Reference

### Serialization Functions

```rust
// Compact JSON output
pub fn to_string<T: JsonSerialize>(value: &T) -> String;

// Pretty-printed JSON with 2-space indentation
pub fn to_string_pretty<T: JsonSerialize>(value: &T) -> String;
```

### Deserialization Functions

```rust
// Parse from string slice
pub fn from_str<T: JsonDeserialize>(s: &str) -> Result<T>;

// Parse from bytes
pub fn from_bytes<T: JsonDeserialize>(bytes: &[u8]) -> Result<T>;
```

### Dynamic Parsing

When the JSON structure isn't known at compile time:

```rust
use json_steroids::{parse, JsonValue};

let json = r#"{"name": "test", "values": [1, 2, 3]}"#;
let value = parse(json).unwrap();

// Access fields using indexing
assert_eq!(value["name"].as_str(), Some("test"));
assert!(value["values"].is_array());
assert_eq!(value["values"][0].as_i64(), Some(1));

// Check types
assert!(value.is_object());
assert!(value["missing"].is_null()); // Missing fields return null
```

### JsonValue Type

The `JsonValue` enum represents any JSON value:

```rust
pub enum JsonValue {
    Null,
    Bool(bool),
    Integer(i64),
    Float(f64),
    String(String),
    Array(Vec<JsonValue>),
    Object(Vec<(String, JsonValue)>),
}
```

Methods available on `JsonValue`:
- Type checking: `is_null()`, `is_bool()`, `is_number()`, `is_string()`, `is_array()`, `is_object()`
- Value extraction: `as_bool()`, `as_i64()`, `as_u64()`, `as_f64()`, `as_str()`, `as_array()`, `as_object()`
- Ownership: `into_string()`, `into_array()`, `into_object()`
- Indexing: `value["key"]` for objects, `value[0]` for arrays

## Supported Types

### Primitives
- Booleans: `bool`
- Integers: `i8`, `i16`, `i32`, `i64`, `isize`, `u8`, `u16`, `u32`, `u64`, `usize`
- Floats: `f32`, `f64`

### Strings
- `String`
- `&str` (serialize only)
- `Cow<str>`

### Collections
- `Vec<T>`
- `[T; N]` (arrays, serialize only)
- `HashMap<K, V>` (K must be string-like)
- `BTreeMap<K, V>` (K must be string-like)

### Wrapper Types
- `Option<T>` - Serializes as `null` when `None`
- `Box<T>`

### Tuples
Tuples up to 8 elements are supported and serialize as JSON arrays:

```rust
let tuple = (1, "hello", true);
let json = to_string(&tuple); // [1,"hello",true]
```

## Error Handling

The library provides detailed error messages:

```rust
use json_steroids::{from_str, JsonError};

let result: Result<i32, _> = from_str("not a number");
match result {
    Ok(value) => println!("Parsed: {}", value),
    Err(JsonError::ExpectedToken(expected, pos)) => {
        println!("Expected {} at position {}", expected, pos);
    }
    Err(e) => println!("Error: {}", e),
}
```

Error types include:
- `UnexpectedEnd` - Input ended unexpectedly
- `UnexpectedChar(char, usize)` - Unexpected character at position
- `ExpectedChar(char, usize)` - Expected specific character
- `ExpectedToken(&str, usize)` - Expected token (e.g., "string", "number")
- `InvalidNumber(usize)` - Invalid number format
- `InvalidEscape(usize)` - Invalid escape sequence
- `InvalidUnicode(usize)` - Invalid Unicode escape
- `InvalidUtf8` - Invalid UTF-8 encoding
- `MissingField(String)` - Required field missing during deserialization
- `UnknownVariant(String)` - Unknown enum variant
- `TypeMismatch` - Type mismatch during deserialization
- `NestingTooDeep(usize)` - JSON nesting exceeds maximum depth (128)

## Performance

json-steroids is designed for high performance:

### Zero-Copy Parsing
Strings that don't contain escape sequences are borrowed directly from the input buffer using `Cow<str>`, avoiding allocation:

```rust
// This string has no escapes - zero allocation!
let json = r#"{"name": "hello world"}"#;

// This string has escapes - allocation needed to unescape
let json = r#"{"name": "hello\nworld"}"#;
```

### Fast Number Formatting
Uses the `itoa` and `ryu` crates for extremely fast integer and floating-point formatting.

### Efficient String Escaping
The serializer uses a fast path that checks if escaping is needed before processing:

```rust
// Fast path - no escaping needed
let s = "hello world";

// Slow path - escaping required
let s = "hello\nworld";
```

### Pre-allocated Buffers
The `JsonWriter` pre-allocates buffer space to minimize reallocations during serialization.

## Architecture

```
json-steroids/
├── src/
│   ├── lib.rs       # Public API and re-exports
│   ├── parser.rs    # Zero-copy JSON parser
│   ├── writer.rs    # Fast JSON serializer
│   ├── value.rs     # Dynamic JsonValue type
│   ├── traits.rs    # JsonSerialize/JsonDeserialize traits + impls
│   └── error.rs     # Error types
└── json-steroids-derive/
    └── src/
        └── lib.rs   # Procedural macros
```

## Examples

### Nested Structures

```rust
use json_steroids::Json;

#[derive(Json)]
struct Address {
    street: String,
    city: String,
    country: String,
}

#[derive(Json)]
struct Company {
    name: String,
    address: Address,
    employees: Vec<String>,
}

let company = Company {
    name: "Acme Corp".to_string(),
    address: Address {
        street: "123 Main St".to_string(),
        city: "Springfield".to_string(),
        country: "USA".to_string(),
    },
    employees: vec!["Alice".to_string(), "Bob".to_string()],
};

let json = to_string(&company);
```

### Working with Optional Fields

```rust
use json_steroids::{Json, from_str};

#[derive(Json, Debug)]
struct UserProfile {
    username: String,
    bio: Option<String>,
    age: Option<u32>,
}

// Missing optional fields default to None
let json = r#"{"username": "alice"}"#;
let profile: UserProfile = from_str(json).unwrap();
assert!(profile.bio.is_none());
assert!(profile.age.is_none());

// Explicit null also becomes None
let json = r#"{"username": "bob", "bio": null, "age": 25}"#;
let profile: UserProfile = from_str(json).unwrap();
assert!(profile.bio.is_none());
assert_eq!(profile.age, Some(25));
```

### Pretty Printing

```rust
use json_steroids::{Json, to_string_pretty};

#[derive(Json)]
struct Config {
    debug: bool,
    port: u16,
}

let config = Config { debug: true, port: 8080 };
let json = to_string_pretty(&config);
// Output:
// {
//   "debug": true,
//   "port": 8080
// }
```

### Custom Serialization with JsonWriter

For advanced use cases, you can use `JsonWriter` directly:

```rust
use json_steroids::JsonWriter;

let mut writer = JsonWriter::new();
writer.begin_object();
writer.write_key("name");
writer.write_string("custom");
writer.write_comma();
writer.write_key("values");
writer.begin_array();
writer.write_i64(1);
writer.write_comma();
writer.write_i64(2);
writer.end_array();
writer.end_object();

let json = writer.into_string();
// {"name":"custom","values":[1,2]}
```

## Running Benchmarks

```bash
cargo bench
```

## Running Tests

```bash
cargo test
```

## License

This project is licensed under the MIT License - see the LICENSE file for details.

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.