jsonx
A serde-enabled Rust implementation of the
dop251/jsonx format --
a superset of JSON that relaxes the syntax and adds typed value constructors.
Ported in spirit (not line-for-line) from the Go package.
JSONX is (mostly) used as a configuration format, so this implementation does not pursue the maximum performance. It is, however, on par with serde_json.
The library is properly tested and fuzzed and overall is safe to use.
What JSONX adds on top of JSON
- Unquoted object keys matching
^[A-Za-z_][0-9A-Za-z_]*$. - Trailing commas after the last array/object element.
- Typed value constructors written as
type(value):- sized integers --
int8,int16,int32,int64,uint8,uint16,uint32,uint64, plus machine-widthint/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).
- sized integers --
Plain JSON numbers always decode to an f64, exactly as in the reference
implementation. JSONX is itself a valid ES5 expression, so a document can be
eval()-ed in JavaScript given definitions of the type() functions.
Example
Usage
Work with arbitrary documents through Value:
let value: Value =
from_str.unwrap;
assert_eq!;
let text = to_string.unwrap;
assert_eq!;
let pretty = to_string_pretty.unwrap;
Or derive Serialize / Deserialize on your own types. Rust's integer types
map to the matching JSONX sized integers. For the other extended types, use the
wrapper types Bytes, Int, Uint, Ip, IpPort, and Datetime: they need
no attribute and -- because they stay transparent in other serde formats -- let
the same struct serialize cleanly to JSONX and to, say, TOML or JSON
(where they degrade to plain strings/integers).
use ;
let host = Host ;
let text = to_string.unwrap;
// {name:"db",port:uint16(5432),addr:ip("10.0.0.1"),blob:bytes("aGk=")}
assert_eq!;
If you must keep a bare chrono::DateTime or std::net address field, the
#[serde(with = "jsonx::datetime")], #[serde(with = "jsonx::ip")], and
#[serde(with = "jsonx::ipport")] modules provide the same forms via an
attribute.
Extending JSONX with your own types
JSONX is open: a document is a valid ES5 expression, so myType(value) is just
a function call. Give one of your own types a type(value) form and it renders
as a constructor in JSONX while staying a plain string everywhere else.
If the type implements Display + FromStr, #[derive(JsonxConstructor)]
generates the trait and the serde impls for you (default derive feature). The
constructor name defaults to the type name lowercased; override with
#[jsonx(name = "...")].
use ;
;
assert_eq!;
When a string representation doesn't fit, implement JsonxConstructor by hand
instead (TOKEN via the ctor! macro, plus to_jsonx_arg/from_jsonx_arg)
and delegate serde to jsonx::constructor::{serialize, deserialize} -- see the
constructor module docs.
For a foreign type you don't own (e.g. uuid::Uuid), wrap it in a newtype and
derive/implement on the wrapper -- just as Ip wraps IpAddr.
Openness extends to the dynamic path too: decoding into Value captures any
unrecognized name(value) as Value::Constructor { name, arg } (rather than
failing), so generic tooling can pass through, inspect, and re-emit custom
constructors losslessly without knowing them ahead of time. A constructor name
must be a JSONX identifier (a leading letter or _, then letters, digits, or
_); a name that isn't has no round-trippable form, so serializing it errors
rather than emitting unparseable output.
let v: Value = from_str.unwrap;
assert_eq!;
Non-greedy decoding
from_str_partial decodes a single value and reports where it stopped:
let : =
from_str_partial.unwrap;
assert_eq!;
API surface
from_str,from_slice,from_str_partialto_string,to_string_pretty,to_string_indent,to_vec,to_writer,to_writer_prettyValue(includingValue::Constructorand theValue::{int, uint, bytes, string, constructor}builders andValue::to_jsonx_arg),Map,DateTime- wrappers:
Bytes,Int,Uint,Ip,IpPort,Datetime(each a publicJsonxConstructor, soDatetime(dt).to_jsonx_arg()etc. yields the canonical argument text) #[serde(with)]modules:ip,ipport,datetime(plus the publicdatetime::to_jsonx_string/datetime::parsehelpers)- open extension:
#[derive(JsonxConstructor)](defaultderivefeature), theJsonxConstructortrait, thector!macro, and theconstructormodule (serialize/deserializefor#[serde(with)], plus the low-levelserialize_constructor/deserialize_constructor) Serializer,Deserializerfor advanced/streaming useError,Result
Design notes
- The deserializer is streaming and borrows string data straight from the input when a string contains no escapes.
- Object output is key-sorted by default (a
BTreeMapbacksValue), giving deterministic, input-order-independent encodings. Enable thepreserve_orderfeature to backValuewithindexmap::IndexMapand keep author key order instead (matching the other encoders most projects pair this with). int64/uint64values outside the±(2^53 − 1)safe-integer range are quoted (e.g.int64("9223372036854775807")) so they survive a round trip through a JavaScriptNumber.- Invalid UTF-8 and lone surrogates are rejected (Rust strings are UTF-8), and parsing is bounded against deeply-nested adversarial input.
License
Licensed under either of MIT or Apache-2.0 at your option.