json-escape
A highly ergonomic, well-tested, no_std
library for streaming JSON string escaping and unescaping. It processes JSON strings with zero-copy slicing and no intermediate allocations, ensuring both high performance and RFC-compliant correctness, ideal for parsers, I/O operations, and memory-constrained environments. ✅
The core of the library is two iterator-based structs that enable its streaming nature:
Escape
: Lazily yields escaped string slices from an input&str
.Unescape
: Lazily yields unescaped byte slices from an input&[u8]
.
This streaming approach avoids allocating a single large String
or Vec<u8>
for the result, making it incredibly efficient for large data processing.
Key Features
- 🎯 Ergonomic & Intuitive: Simple-to-use functions like
escape_str
andunescape
return familiar iterators that integrate cleanly with Rust's ecosystem. - 🚀 Streaming & Iterator-based: Process data in chunks without buffering the entire result in memory, delivering high performance for large payloads.
- ✨ Zero-Copy Slicing: For sequences of characters that don't need modification, the iterators yield slices borrowed directly from the input.
- ✅ Correct & Compliant: Fully compliant with the JSON RFC for all escape sequences, including full support for UTF-16 surrogate pairs (
\uD83D\uDE00
for 😀). - 🧩
no_std
Compatible: Usable in embedded systems and other memory-constrained environments (with thealloc
feature for owned conversions). - ⚙️ Full Functionality: Implements
PartialEq
for convenient testing and providesstd::io::Read
integration (with thestd
feature) for plugging directly into libraries likeserde_json
.
Quick Start
Escaping a String
use escape_str;
use Cow;
let input = "Hello, \"world\"!\nThis is a backslash: \\";
let expected = r#"Hello, \"world\"!\nThis is a backslash: \\"#;
// escape_str returns an iterator. Collect it into a String.
let escaped_string: String = escape_str.collect;
assert_eq!;
// For efficiency, convert to a Cow<str>. This avoids allocation
// if the input string requires no escaping.
let cow: = escape_str.into;
assert!;
Unescaping a String
use unescape;
use Cow;
let input = r#"Emoji: \uD83D\uDE00 and a tab\t!"#;
let expected = "Emoji: 😀 and a tab\t!";
// unescape returns an iterator over Result<&[u8], _>.
// The `decode_utf8` helper collects and validates the output.
let decoded_cow: = unescape.decode_utf8.unwrap;
assert_eq!;
Performance
The library's design focuses on minimizing allocations, leading to superior performance, especially when dealing with large payloads or zero-copy streaming.
Benchmarks confirm that json-escape
is significantly faster than standard methods when avoiding unnecessary allocations.
Operation | Scenario | json-escape (Median Time) |
Comparison (e.g., serde_json or Main API) |
---|---|---|---|
Escaping (Collect to String) | No Escapes | $309.95\text{ ns}$ (Explicit API) | $338.38\text{ ns}$ (Main API) |
Unescaping (Iterate Only) | No Escapes | $54.999\text{ ns}$ (Explicit API) | $88.443\text{ ns}$ (Main API) |
Escaping (Iterate Only) | Dense Escapes | $236.56\text{ ns}$ (Explicit API) | $278.02\text{ ns}$ (Main API) |
Unescaping (Iterate Only) | Dense Escapes | $200.87\text{ ns}$ (Explicit API) | $501.67\text{ ns}$ (Main API) |
Unescaping (Decode UTF-8) | Unicode | $1.5011\text{ ms}$ (Explicit API) | $1.8918\text{ ms}$ (Main API) |
Escaping to String | Sparse Escapes | ~2.8% to ~8.8% Faster | serde_json::to_string |
Unescaping from Str | No Escapes | ~2.8% Faster | serde_json::from_str |
Key Takeaways
- Zero-Allocation Wins: For I/O-bound tasks using the
Write to Sink
orstd::io::Read
integrations, both theMain
andExplicit
APIs show near-identical, minimal overhead ($<159~\text{ps}$), which is the most efficient method for large data. Explicit
Module Advantage (Unescaping): The newexplicit
module significantly outperforms the main API in unescaping when used for pure iteration or in dense/sparse escape scenarios, sometimes being over 2x faster (e.g., Sparse or Dense Escapes). This is likely due to the structural clarity of its chunks simplifying internal logic.- Overall Speed:
json-escape
consistently outperforms the overhead of allocating and parsing with general-purpose tools likeserde_json
for simple string conversion tasks.
Fine-Grained Control with the explicit
Module
The new json_escape::explicit
module provides more detail and control by yielding chunk structs that explicitly separate the literal text from the escaped/unescaped character. This is useful for advanced debugging, logging, and custom stream processing.
Explicit Escaping
The Escape
iterator in this module yields an EscapedChunk<'a>
containing the literal()
slice and the escaped()
static string.
use escape_str;
let mut escaper = escape_str;
let chunk1 = escaper.next.unwrap;
assert_eq!;
assert_eq!;
let chunk2 = escaper.next.unwrap;
assert_eq!;
assert_eq!;
Explicit Unescaping
The Unescape
iterator yields an UnescapedChunk<'a>
containing the literal()
byte slice and the unescaped()
character.
use unescape;
let mut unescaper = unescape;
let chunk1 = unescaper.next.unwrap.unwrap;
assert_eq!;
assert_eq!;
let chunk2 = unescaper.next.unwrap.unwrap;
assert_eq!;
assert_eq!;
Advanced Usage: Zero-Allocation REST API Parsing
A common scenario in web services is receiving a JSON payload where one of the fields is another JSON object, escaped as a string.
json-escape
avoids the standard inefficient practice of allocating a new String
for the unescaped payload
by plugging its streaming Unescape
reader directly into serde_json
.
The json-escape
Solution: No Intermediate Allocation!
use unescape_quoted; // Use the crate root function
use Deserialize;
use RawValue;
// The inner payload we want to extract and parse.
// The outer structure. We use `&RawValue` for a zero-copy view.
Installation
Add this to your Cargo.toml
:
[]
= "0.1.2"
Feature Flags
alloc
(enabled by default): ProvidesCow
,String
, andVec
conversions.std
(enabled by default): Providesstd::io::Read
andstd::error::Error
implementations.
For no_std
environments without an allocator, use:
[]
= { = "*", = false }
License
This project is licensed under either of
- Apache License, Version 2.0, (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.