jsonx/lib.rs
1//! A [serde](https://serde.rs)-enabled implementation of the **JSONX** format.
2//!
3//! JSONX is a superset of JSON that relaxes the syntax and adds a handful of
4//! typed value constructors. Concretely, on top of JSON it supports:
5//!
6//! * **Unquoted object keys** matching `^[A-Za-z_][0-9A-Za-z_]*$`.
7//! * **Trailing commas** after the last array/object element.
8//! * **Typed constructors** written as `type(value)`:
9//! * sized integers: `int8`, `int16`, `int32`, `int64`, `uint8`, `uint16`,
10//! `uint32`, `uint64`, and the machine-width `int` / `uint`;
11//! * `datetime("2017-12-25T15:00:00Z")` (RFC 3339);
12//! * `ip("192.168.1.2")` / `ip("::1")`;
13//! * `ipport("192.168.1.2:65000")` / `ipport("[::1]:65000")`;
14//! * `bytes("YWJjZA==")` (standard Base64).
15//!
16//! Plain JSON numbers always decode to an `f64`, exactly as in the reference
17//! implementation; the constructors above are how you get a typed integer or
18//! one of the extended types.
19//!
20//! # Quick start
21//!
22//! Work with arbitrary documents via [`Value`]:
23//!
24//! ```
25//! let value: jsonx::Value = jsonx::from_str("{ id: int(7), tags: [\"a\", \"b\",] }").unwrap();
26//! assert_eq!(value.get("id"), Some(&jsonx::Value::Int(7)));
27//!
28//! let text = jsonx::to_string(&value).unwrap();
29//! assert_eq!(text, r#"{id:int(7),tags:["a","b"]}"#);
30//! ```
31//!
32//! Or derive `Serialize`/`Deserialize` on your own types. Rust integer types
33//! map to the matching JSONX sized integers, and plain `f64`s stay bare:
34//!
35//! ```
36//! #[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
37//! struct Server {
38//! name: String,
39//! port: u16,
40//! weight: i32,
41//! }
42//!
43//! let server = Server { name: "db".into(), port: 5432, weight: -1 };
44//! let text = jsonx::to_string(&server).unwrap();
45//! assert_eq!(text, r#"{name:"db",port:uint16(5432),weight:int32(-1)}"#);
46//! assert_eq!(jsonx::from_str::<Server>(&text).unwrap(), server);
47//! ```
48//!
49//! # Extended types
50//!
51//! For the non-integer extended types, the most ergonomic option is the wrapper
52//! types in this crate — [`Bytes`], [`Int`], [`Uint`], [`Ip`], [`IpPort`], and
53//! [`Datetime`]. They need no attribute, and because they stay transparent in
54//! other serde formats, the *same* struct also serializes cleanly to (say) TOML
55//! or JSON, where they degrade to plain strings/integers:
56//!
57//! ```
58//! use jsonx::{Ip, Datetime, DateTime};
59//!
60//! #[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
61//! struct Peer {
62//! addr: Ip,
63//! seen: Datetime,
64//! }
65//!
66//! let peer = Peer {
67//! addr: Ip("10.0.0.1".parse().unwrap()),
68//! seen: Datetime(DateTime::parse_from_rfc3339("2024-06-01T09:00:00Z").unwrap()),
69//! };
70//! let text = jsonx::to_string(&peer).unwrap();
71//! assert_eq!(text, r#"{addr:ip("10.0.0.1"),seen:datetime("2024-06-01T09:00:00Z")}"#);
72//! ```
73//!
74//! If you must keep a bare [`std::net`] address or [`DateTime`] field, the
75//! [`ip`], [`ipport`], and [`datetime`] modules provide `#[serde(with = ...)]`
76//! glue instead.
77//!
78//! # Extending JSONX with your own constructors
79//!
80//! JSONX is open: you can teach it a `type(value)` constructor for one of your
81//! own types by implementing [`JsonxConstructor`]. See the [`constructor`]
82//! module for the details.
83//!
84//! # Non-greedy decoding
85//!
86//! [`from_str_partial`] decodes a single value and tells you where it stopped,
87//! so you can parse a stream of concatenated values.
88
89#![warn(missing_docs)]
90
91mod base64;
92pub mod constructor;
93pub mod datetime;
94mod de;
95pub mod error;
96mod number;
97mod ser;
98mod tokens;
99mod value;
100mod wrappers;
101
102pub use constructor::{deserialize_constructor, serialize_constructor, JsonxConstructor};
103pub use datetime::DateTime;
104
105/// Derives [`JsonxConstructor`] plus the serde impls that wire it in, for a type
106/// that implements [`Display`](std::fmt::Display) and [`FromStr`](std::str::FromStr).
107///
108/// The constructor name defaults to the type name lowercased; override it with
109/// `#[jsonx(name = "...")]`. See the [`constructor`] module for details.
110///
111/// ```
112/// use std::fmt;
113/// use std::str::FromStr;
114///
115/// #[derive(jsonx::JsonxConstructor, Debug, PartialEq)]
116/// #[jsonx(name = "semver")]
117/// struct Version(u16, u16);
118///
119/// impl fmt::Display for Version {
120/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121/// write!(f, "{}.{}", self.0, self.1)
122/// }
123/// }
124/// impl FromStr for Version {
125/// type Err = String;
126/// fn from_str(s: &str) -> Result<Self, String> {
127/// let (a, b) = s.split_once('.').ok_or("expected MAJOR.MINOR")?;
128/// Ok(Version(a.parse().map_err(|_| "bad major")?, b.parse().map_err(|_| "bad minor")?))
129/// }
130/// }
131///
132/// assert_eq!(jsonx::to_string(&Version(1, 4)).unwrap(), r#"semver("1.4")"#);
133/// assert_eq!(jsonx::from_str::<Version>(r#"semver("1.4")"#).unwrap(), Version(1, 4));
134/// ```
135#[cfg(feature = "derive")]
136pub use jsonx_derive::JsonxConstructor;
137
138#[doc(hidden)]
139pub mod __derive {
140 //! Private re-exports for `#[derive(JsonxConstructor)]`-generated code.
141 //! Not a stable API.
142 pub use ::serde;
143}
144
145pub use de::{from_slice, from_str, from_str_partial, Deserializer};
146pub use error::{Error, Result};
147pub use ser::{
148 to_string, to_string_indent, to_string_pretty, to_vec, to_writer, to_writer_pretty, Serializer,
149};
150pub use value::{Map, Value};
151pub use wrappers::{ip, ipport, Bytes, Datetime, Int, Ip, IpPort, Uint};