nextjson 0.1.3

A dependency-free, no_std JSON and CBOR library with a schema-driven, visitor-free design.
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
//! # NextJson
//!
//! A dependency-free, `no_std + alloc` JSON and CBOR library for Rust.
//!
//! The public native contracts are [`NsonSerialize::nextencode`],
//! [`NsonDeserialize::nextdecode_into`], [`nextencode`], and [`nextdecode`].
//! JSON and the JSON-compatible CBOR profile can also be relayed through the
//! format-neutral [`cross_format::EventSink`] protocol without constructing an
//! intermediate [`Value`] tree.
//!
//! ## Quick start
//!
//! ```rust
//! let expected = (7_u64, "NextJson", vec![1_i32, 2, 3]);
//! let json = nextjson::nextencode(&expected)?;
//! let actual: (u64, &str, Vec<i32>) = nextjson::nextdecode(&json)?;
//! assert_eq!(actual, expected);
//! # Ok::<(), nextjson::Error>(())
//! ```
//!
//! ## Cross-format relay
//!
//! ```rust
//! use nextjson::cross_format;
//!
//! let source = br#"{"name":"NextJson","values":[1,2,3]}"#;
//! let cbor = cross_format::json_to_cbor(source)?;
//! let json = cross_format::cbor_to_json(&cbor)?;
//! let value: nextjson::Value = nextjson::nextdecode(&json)?;
//! assert_eq!(value["name"], nextjson::Value::from("NextJson"));
//! # Ok::<(), nextjson::Error>(())
//! ```
//!
//! ## Zero-copy boundary
//!
//! Unescaped JSON strings and definite-length CBOR text strings borrow their
//! input ranges. Escaped JSON and indefinite-length CBOR text must materialize
//! decoded UTF-8. Encoding always writes new output bytes. The library does not
//! describe those required copies as zero-copy.
//!
//! ## Features
//!
//! - `std` (default): standard I/O adapters and standard-library integrations.
//! - `derive` (default): repository-owned `NsonSerialize` and
//!   `NsonDeserialize` procedural macros.
//!
//! Disabling default features leaves a `core + alloc` implementation. The
//! complete workspace dependency graph contains only `nextjson` and the local,
//! optional `nextjson-derive` crate.
//!
//! ## Architecture
//!
//! NextJson uses schema-driven derives, a unified token stream, checked decode
//! slots, and a format-neutral [`cross_format::EventSink`] protocol. The whole
//! workspace build graph contains only its two local crates.
//!
//! The following properties are implemented directly in this repository and
//! are enforced by its tests and build configuration:
//!
//! 1. **Direct dual contract** - `NsonSerialize::nextencode` writes bytes
//!    directly; `NsonDeserialize::nextdecode_into` decodes into a caller-provided
//!    checked nextdecode slot, supporting memory reuse without a placeholder value.
//! 2. **Compile-time schema** - every type carries `const SCHEMA: TypeSchema`,
//!    a runtime-introspectable metadata tree (usable for JSON Schema generation,
//!    validation, and tooling).
//! 3. **Unified token stream** - the byte-stream lexer and the content-replay
//!    reader share identical nextdecode primitives, so internally-tagged,
//!    adjacently-tagged, and untagged enums plus `Value` round-trips reuse one
//!    engine.
//! 4. **Lazy single-token lookahead** - the parser lexes one token at a time;
//!    unescaped strings borrow the input with zero allocation; integer parsing
//!    is hand-rolled with overflow detection.
//! 5. **Safety boundary** - the library is `#![deny(unsafe_code)]`, including
//!    nextdecode slots and partial-initialization cleanup. `no_std` is fully
//!    supported: the core uses only `core` + `alloc`, with `std`-only types
//!    behind the `std` feature.
//! 6. **Streaming cross-format relay** - JSON and the JSON-compatible CBOR
//!    profile exchange borrowed structural events without an intermediate
//!    [`Value`].
//! 7. **One validated event protocol** - every format encoder and both
//!    cross-format sinks validate container / key / value ordering through a
//!    single shared state machine, parameterized only by whether the wire
//!    format has explicit array separators (JSON does, CBOR does not). The
//!    byte lexer additionally serves typed scalar reads (`number`, `string`,
//!    `bool`, `Option` dispatch) directly from the source byte, so the token
//!    stream stays available for content replay without taxing the hot path.
//!
//! ## Safety and resource limits
//!
//! This crate denies unsafe Rust. Decode slots use checked state, numeric
//! conversions use checked arithmetic, and decoders cap nesting at 128 by
//! default.
//! Applications must still enforce total input bytes, collection sizes, CPU
//! time, and output quotas. `from_slice` / `from_str` operate on a complete
//! in-memory input; `from_reader` (std) pulls incrementally from any
//! `std::io::Read` source.
//!
//! See the repository's [English README], [Chinese README], [safety model], and
//! [benchmark protocol] for the complete supported surface and reproducibility
//! requirements.

#![no_std]
#![deny(unsafe_code)]
#![deny(missing_docs)]
#![doc(html_root_url = "https://docs.rs/nextjson")]

extern crate alloc;

#[cfg(feature = "std")]
extern crate std;

#[cfg(feature = "derive")]
pub use nextjson_derive::{NsonDeserialize, NsonSerialize};

pub use crate::bytes::Bytes;
pub use crate::de::{
    DecodeConfig, DecodeSlot, Decoder, FormatDecoder, NsonDeserialize, OptionTag, Token,
};
pub use crate::encoding::{EncodeConfig, Encoder, FastEncoder};
pub use crate::error::{Error, FormatError, Result};
pub use crate::map::Map;
pub use crate::number::Number;
pub use crate::schema::{
    EnumSchema, FieldSchema, NsonSchema, StructSchema, TypeSchema, VariantSchema,
};
pub use crate::ser::{FormatEncoder, NsonSerialize};
#[cfg(feature = "std")]
pub use crate::stream::StreamDecoder;
pub use crate::value::Value;
pub use crate::write::Write;

mod bytes;
pub mod cross_format;
pub mod de;
pub mod encoding;
pub mod error;
mod event_state;
pub mod formats;
mod json_schema;
mod lex;
pub mod map;
mod number;
#[doc(hidden)]
pub mod private;
mod schema;
mod ser;
#[cfg(feature = "std")]
pub mod stream;
mod value;
mod write;

/// Private re-exports used by macro-generated code.
#[doc(hidden)]
pub mod __private {
    pub use alloc::borrow::Cow;
    pub use alloc::boxed::Box;
    pub use alloc::collections::{BTreeMap, BTreeSet, VecDeque};
    pub use alloc::format;
    pub use alloc::string::{String, ToString};
    pub use alloc::vec;
    pub use alloc::vec::Vec;
}

use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;

// ---------------------------------------------------------------------------
// Top-level serialization entry points
// ---------------------------------------------------------------------------

/// Encode a value into a compact JSON byte vector using the native NextJson
/// data model.
///
/// This is the canonical native encoding entry point. Unescaped string data is
/// copied directly into the output buffer without an intermediate JSON value.
///
/// The top-level entry points use the trusted [`FastEncoder`] variant: the
/// caller's `NsonSerialize` implementation (typically derived) is trusted to
/// follow the event protocol, skipping per-value validation for throughput.
/// Hand-written implementations that emit events out of order produce
/// malformed JSON instead of an error; use the validated [`Encoder`] directly
/// when that guarantee matters.
pub fn nextencode<T: NsonSerialize + ?Sized>(value: &T) -> Result<Vec<u8>> {
    let mut encoder = FastEncoder::for_vec(EncodeConfig::compact());
    NsonSerialize::nextencode(value, &mut encoder)?;
    encoder.finish_vec()
}

/// Serialize a value into a compact JSON string.
pub fn to_string<T: NsonSerialize + ?Sized>(value: &T) -> Result<String> {
    let bytes = nextencode(value)?;
    String::from_utf8(bytes).map_err(|e| Error::custom(format!("invalid utf-8: {e}")))
}

/// Serialize a value into a compact JSON byte vector.
pub fn to_vec<T: NsonSerialize + ?Sized>(value: &T) -> Result<Vec<u8>> {
    nextencode(value)
}

/// Serialize a value into a pretty-printed JSON string.
pub fn to_string_pretty<T: NsonSerialize + ?Sized>(value: &T) -> Result<String> {
    let bytes = to_vec_pretty(value)?;
    String::from_utf8(bytes).map_err(|e| Error::custom(format!("invalid utf-8: {e}")))
}

/// Serialize a value into a pretty-printed JSON byte vector.
pub fn to_vec_pretty<T: NsonSerialize + ?Sized>(value: &T) -> Result<Vec<u8>> {
    let mut encoder = FastEncoder::for_vec(EncodeConfig::pretty());
    NsonSerialize::nextencode(value, &mut encoder)?;
    encoder.finish_vec()
}

/// Serialize a value to any `Write` sink.
pub fn to_writer<W, T>(writer: W, value: &T) -> Result<()>
where
    W: Write,
    T: NsonSerialize + ?Sized,
{
    let mut encoder = FastEncoder::new(writer);
    NsonSerialize::nextencode(value, &mut encoder)?;
    encoder.finish()?;
    Ok(())
}

/// Serialize a value to any `Write` sink with pretty printing.
pub fn to_writer_pretty<W, T>(writer: W, value: &T) -> Result<()>
where
    W: Write,
    T: NsonSerialize + ?Sized,
{
    let mut encoder = FastEncoder::with_config(writer, EncodeConfig::pretty());
    NsonSerialize::nextencode(value, &mut encoder)?;
    encoder.finish()?;
    Ok(())
}

/// Serialize a value to a `std::io::Write` sink (requires the `std` feature).
#[cfg(feature = "std")]
pub fn to_io_writer<W, T>(writer: W, value: &T) -> Result<()>
where
    W: std::io::Write,
    T: NsonSerialize + ?Sized,
{
    let mut encoder = FastEncoder::new(crate::write::StdWriter(writer));
    NsonSerialize::nextencode(value, &mut encoder)?;
    encoder.finish()?;
    Ok(())
}

// ---------------------------------------------------------------------------
// Top-level deserialization entry points
// ---------------------------------------------------------------------------

/// Decode one complete JSON value using the native NextJson data model.
///
/// The input lifetime is preserved, so implementations may borrow unescaped
/// strings directly from `input` without allocation.
pub fn nextdecode<'de, T: NsonDeserialize<'de>>(input: &'de [u8]) -> Result<T> {
    let mut decoder = Decoder::new(input);
    let value = T::nextdecode(&mut decoder)?;
    decoder.end()?;
    Ok(value)
}

/// Deserialize from a `&str`. The `'de` lifetime allows types to borrow input.
pub fn from_str<'de, T: NsonDeserialize<'de>>(s: &'de str) -> Result<T> {
    nextdecode(s.as_bytes())
}

/// Deserialize from a `&[u8]`. The `'de` lifetime allows types to borrow input.
pub fn from_slice<'de, T: NsonDeserialize<'de>>(slice: &'de [u8]) -> Result<T> {
    nextdecode(slice)
}

/// Deserialize from a `std::io::Read` (requires the `std` feature).
///
/// The input is pulled incrementally (see [`StreamDecoder`]), so decoding
/// starts before the whole payload has arrived. The target type must be
/// deserializable for any lifetime (owned): borrowed inputs (`&str`, `&[u8]`,
/// `nextjson::Bytes`) cannot come from a stream.
#[cfg(feature = "std")]
pub fn from_reader<R, T>(reader: R) -> Result<T>
where
    R: std::io::Read,
    T: for<'de> NsonDeserialize<'de>,
{
    let mut decoder = StreamDecoder::new(reader);
    let value = T::nextdecode(&mut decoder)?;
    decoder.end()?;
    Ok(value)
}

// ---------------------------------------------------------------------------
// Value conversion entry points
// ---------------------------------------------------------------------------

/// Convert any serializable value into a [`Value`].
pub fn to_value<T: NsonSerialize + ?Sized>(value: &T) -> Result<Value> {
    from_slice(&to_vec(value)?)
}

/// Convert a [`Value`] into any type (owned, deserializable for any lifetime).
pub fn from_value<T>(value: Value) -> Result<T>
where
    T: for<'de> NsonDeserialize<'de>,
{
    private::nextdecode_value(value)
}

/// Get the compile-time [`TypeSchema`] of a type (runtime-introspectable).
pub fn schema_of<T: NsonSchema>() -> TypeSchema {
    T::SCHEMA
}

/// Generate a JSON Schema (draft-07 style) for any [`NsonSchema`] type.
pub fn to_json_schema<T: NsonSchema>() -> Value {
    json_schema::from_schema(T::SCHEMA)
}

#[macro_export]
/// The `json!` macro: build a [`Value`] with JSON-like syntax.
///
/// Supports nested objects / arrays, `null` / `true` / `false`, literals, bare
/// identifier keys, expression interpolation, and trailing commas:
///
/// ```rust
/// use nextjson::json;
/// let code = 200;
/// let v = json!({
///     "code": code,
///     "ok": (code == 200),
///     "nested": { "a": [1, 2.5, null], "b": true },
///     "list": [1, 2, 3,],
/// });
/// ```
macro_rules! json {
    ($($json:tt)+) => {
        $crate::json_internal!($($json)+)
    };
}

#[macro_export]
#[doc(hidden)]
macro_rules! json_internal {
    // ---------------- main rules ----------------
    (null) => {
        $crate::Value::Null
    };
    (true) => {
        $crate::Value::Bool(true)
    };
    (false) => {
        $crate::Value::Bool(false)
    };
    ([]) => {
        $crate::Value::Array($crate::__private::Vec::new())
    };
    ([$($tt:tt)+]) => {
        $crate::Value::Array($crate::json_internal!(@array [] $($tt)+))
    };
    ({}) => {
        $crate::Value::Object($crate::Map::new())
    };
    ({ $($tt:tt)+ }) => {
        $crate::Value::Object({
            let mut __object = $crate::Map::new();
            $crate::json_internal!(@object __object () ($($tt)+));
            __object
        })
    };
    ($other:expr) => {
        $crate::to_value(&$other).expect("json! interpolation: value must be serializable")
    };

    // ---------------- @array: TT muncher ----------------
    (@array [$($elems:expr,)*]) => {
        $crate::__private::vec![$($elems,)*]
    };
    (@array [$($elems:expr),*]) => {
        $crate::__private::vec![$($elems),*]
    };
    (@array [$($elems:expr,)*] null $($rest:tt)*) => {
        $crate::json_internal!(@array [$($elems,)* $crate::json_internal!(null)] $($rest)*)
    };
    (@array [$($elems:expr,)*] true $($rest:tt)*) => {
        $crate::json_internal!(@array [$($elems,)* $crate::json_internal!(true)] $($rest)*)
    };
    (@array [$($elems:expr,)*] false $($rest:tt)*) => {
        $crate::json_internal!(@array [$($elems,)* $crate::json_internal!(false)] $($rest)*)
    };
    (@array [$($elems:expr,)*] [$($sub:tt)*] $($rest:tt)*) => {
        $crate::json_internal!(@array [$($elems,)* $crate::json_internal!([$($sub)*])] $($rest)*)
    };
    (@array [$($elems:expr,)*] {$($sub:tt)*} $($rest:tt)*) => {
        $crate::json_internal!(@array [$($elems,)* $crate::json_internal!({$($sub)*})] $($rest)*)
    };
    (@array [$($elems:expr,)*] $next:expr , $($rest:tt)*) => {
        $crate::json_internal!(@array [$($elems,)* $crate::json_internal!($next),] $($rest)*)
    };
    (@array [$($elems:expr,)*] $last:expr) => {
        $crate::json_internal!(@array [$($elems,)* $crate::json_internal!($last)])
    };
    (@array [$($elems:expr),*] , $($rest:tt)*) => {
        $crate::json_internal!(@array [$($elems,)*] $($rest)*)
    };

    // ---------------- @object: TT muncher ----------------
    (@object $object:ident () ()) => {};
    (@object $object:ident [$($key:tt)+] ($value:expr) , $($rest:tt)*) => {
        let _ = $object.insert($crate::json_key!(($($key)+)), $value);
        $crate::json_internal!(@object $object () ($($rest)*));
    };
    (@object $object:ident [$($key:tt)+] ($value:expr)) => {
        let _ = $object.insert($crate::json_key!(($($key)+)), $value);
    };
    (@object $object:ident ($($key:tt)+) (: null $($rest:tt)*)) => {
        $crate::json_internal!(@object $object [$($key)+] ($crate::json_internal!(null)) $($rest)*);
    };
    (@object $object:ident ($($key:tt)+) (: true $($rest:tt)*)) => {
        $crate::json_internal!(@object $object [$($key)+] ($crate::json_internal!(true)) $($rest)*);
    };
    (@object $object:ident ($($key:tt)+) (: false $($rest:tt)*)) => {
        $crate::json_internal!(@object $object [$($key)+] ($crate::json_internal!(false)) $($rest)*);
    };
    (@object $object:ident ($($key:tt)+) (: [$($sub:tt)*] $($rest:tt)*)) => {
        $crate::json_internal!(@object $object [$($key)+] ($crate::json_internal!([$($sub)*])) $($rest)*);
    };
    (@object $object:ident ($($key:tt)+) (: {$($sub:tt)*} $($rest:tt)*)) => {
        $crate::json_internal!(@object $object [$($key)+] ($crate::json_internal!({$($sub)*})) $($rest)*);
    };
    (@object $object:ident ($($key:tt)+) (: $value:expr , $($rest:tt)*)) => {
        $crate::json_internal!(@object $object [$($key)+] ($crate::json_internal!($value)) , $($rest)*);
    };
    (@object $object:ident ($($key:tt)+) (: $value:expr)) => {
        $crate::json_internal!(@object $object [$($key)+] ($crate::json_internal!($value)));
    };
    (@object $object:ident ($($key:tt)*) ($tt:tt $($rest:tt)*)) => {
        $crate::json_internal!(@object $object ($($key)* $tt) ($($rest)*));
    };
}

#[macro_export]
#[doc(hidden)]
/// Convert an object key into a String.
macro_rules! json_key {
    (($s:literal)) => {
        $crate::__private::String::from($s)
    };
    (($i:ident)) => {
        $crate::__private::String::from(stringify!($i))
    };
    (($e:expr)) => {
        $crate::__private::String::from($crate::__private::format!("{}", $e))
    };
}