jsonx 0.1.0

A serde-enabled implementation of the JSONX extended-JSON format: typed value constructors, unquoted keys, and trailing commas.
Documentation
//! A [serde](https://serde.rs)-enabled implementation of the **JSONX** format.
//!
//! JSONX is a superset of JSON that relaxes the syntax and adds a handful of
//! typed value constructors. Concretely, on top of JSON it supports:
//!
//! * **Unquoted object keys** matching `^[A-Za-z_][0-9A-Za-z_]*$`.
//! * **Trailing commas** after the last array/object element.
//! * **Typed constructors** written as `type(value)`:
//!   * sized integers: `int8`, `int16`, `int32`, `int64`, `uint8`, `uint16`,
//!     `uint32`, `uint64`, and the machine-width `int` / `uint`;
//!   * `datetime("2017-12-25T15:00:00Z")` (RFC 3339);
//!   * `ip("192.168.1.2")` / `ip("::1")`;
//!   * `ipport("192.168.1.2:65000")` / `ipport("[::1]:65000")`;
//!   * `bytes("YWJjZA==")` (standard Base64).
//!
//! Plain JSON numbers always decode to an `f64`, exactly as in the reference
//! implementation; the constructors above are how you get a typed integer or
//! one of the extended types.
//!
//! # Quick start
//!
//! Work with arbitrary documents via [`Value`]:
//!
//! ```
//! let value: jsonx::Value = jsonx::from_str("{ id: int(7), tags: [\"a\", \"b\",] }").unwrap();
//! assert_eq!(value.get("id"), Some(&jsonx::Value::Int(7)));
//!
//! let text = jsonx::to_string(&value).unwrap();
//! assert_eq!(text, r#"{id:int(7),tags:["a","b"]}"#);
//! ```
//!
//! Or derive `Serialize`/`Deserialize` on your own types. Rust integer types
//! map to the matching JSONX sized integers, and plain `f64`s stay bare:
//!
//! ```
//! #[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
//! struct Server {
//!     name: String,
//!     port: u16,
//!     weight: i32,
//! }
//!
//! let server = Server { name: "db".into(), port: 5432, weight: -1 };
//! let text = jsonx::to_string(&server).unwrap();
//! assert_eq!(text, r#"{name:"db",port:uint16(5432),weight:int32(-1)}"#);
//! assert_eq!(jsonx::from_str::<Server>(&text).unwrap(), server);
//! ```
//!
//! # Extended types
//!
//! For the non-integer extended types, the most ergonomic option is the wrapper
//! types in this crate — [`Bytes`], [`Int`], [`Uint`], [`Ip`], [`IpPort`], and
//! [`Datetime`]. They need no attribute, and because they stay transparent in
//! other serde formats, the *same* struct also serializes cleanly to (say) TOML
//! or JSON, where they degrade to plain strings/integers:
//!
//! ```
//! use jsonx::{Ip, Datetime, DateTime};
//!
//! #[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
//! struct Peer {
//!     addr: Ip,
//!     seen: Datetime,
//! }
//!
//! let peer = Peer {
//!     addr: Ip("10.0.0.1".parse().unwrap()),
//!     seen: Datetime(DateTime::parse_from_rfc3339("2024-06-01T09:00:00Z").unwrap()),
//! };
//! let text = jsonx::to_string(&peer).unwrap();
//! assert_eq!(text, r#"{addr:ip("10.0.0.1"),seen:datetime("2024-06-01T09:00:00Z")}"#);
//! ```
//!
//! If you must keep a bare [`std::net`] address or [`DateTime`] field, the
//! [`ip`], [`ipport`], and [`datetime`] modules provide `#[serde(with = ...)]`
//! glue instead.
//!
//! # Extending JSONX with your own constructors
//!
//! JSONX is open: you can teach it a `type(value)` constructor for one of your
//! own types by implementing [`JsonxConstructor`]. See the [`constructor`]
//! module for the details.
//!
//! # Non-greedy decoding
//!
//! [`from_str_partial`] decodes a single value and tells you where it stopped,
//! so you can parse a stream of concatenated values.

#![warn(missing_docs)]

mod base64;
pub mod constructor;
pub mod datetime;
mod de;
pub mod error;
mod number;
mod ser;
mod tokens;
mod value;
mod wrappers;

pub use constructor::{deserialize_constructor, serialize_constructor, JsonxConstructor};
pub use datetime::DateTime;

/// Derives [`JsonxConstructor`] plus the serde impls that wire it in, for a type
/// that implements [`Display`](std::fmt::Display) and [`FromStr`](std::str::FromStr).
///
/// The constructor name defaults to the type name lowercased; override it with
/// `#[jsonx(name = "...")]`. See the [`constructor`] module for details.
///
/// ```
/// use std::fmt;
/// use std::str::FromStr;
///
/// #[derive(jsonx::JsonxConstructor, Debug, PartialEq)]
/// #[jsonx(name = "semver")]
/// struct Version(u16, u16);
///
/// impl fmt::Display for Version {
///     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
///         write!(f, "{}.{}", self.0, self.1)
///     }
/// }
/// impl FromStr for Version {
///     type Err = String;
///     fn from_str(s: &str) -> Result<Self, String> {
///         let (a, b) = s.split_once('.').ok_or("expected MAJOR.MINOR")?;
///         Ok(Version(a.parse().map_err(|_| "bad major")?, b.parse().map_err(|_| "bad minor")?))
///     }
/// }
///
/// assert_eq!(jsonx::to_string(&Version(1, 4)).unwrap(), r#"semver("1.4")"#);
/// assert_eq!(jsonx::from_str::<Version>(r#"semver("1.4")"#).unwrap(), Version(1, 4));
/// ```
#[cfg(feature = "derive")]
pub use jsonx_derive::JsonxConstructor;

#[doc(hidden)]
pub mod __derive {
    //! Private re-exports for `#[derive(JsonxConstructor)]`-generated code.
    //! Not a stable API.
    pub use ::serde;
}

pub use de::{from_slice, from_str, from_str_partial, Deserializer};
pub use error::{Error, Result};
pub use ser::{
    to_string, to_string_indent, to_string_pretty, to_vec, to_writer, to_writer_pretty, Serializer,
};
pub use value::{Map, Value};
pub use wrappers::{ip, ipport, Bytes, Datetime, Int, Ip, IpPort, Uint};