jsonx 0.1.0

A serde-enabled implementation of the JSONX extended-JSON format: typed value constructors, unquoted keys, and trailing commas.
Documentation
# jsonx

A [serde](https://serde.rs)-enabled Rust implementation of the 
[`dop251/jsonx`](https://github.com/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-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. JSONX is itself a valid ES5 expression, so a document can be
`eval()`-ed in JavaScript given definitions of the `type()` functions.

## Example

```js
{
  k01: null,
  k04: "test",
  k05: 1.45678e-98,
  k06: int(-454365464),
  k14: int64("9223372036854775807"),
  k16: datetime("2017-12-25T15:00:00Z"),
  k17: ip("192.168.1.2"),
  k20: ipport("[::1]:65000"),
  k21: bytes("YWJjZA=="),
  nested: { test: true },
}
```

## Usage

Work with arbitrary documents through `Value`:

```rust
let value: jsonx::Value =
    jsonx::from_str(r#"{ 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"]}"#);

let pretty = jsonx::to_string_pretty(&value).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).

```rust
use jsonx::{Bytes, Ip};

#[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
struct Host {
    name: String,
    port: u16,
    addr: Ip,
    blob: Bytes,
}

let host = Host {
    name: "db".into(),
    port: 5432,
    addr: Ip("10.0.0.1".parse().unwrap()),
    blob: Bytes(b"hi".to_vec()),
};

let text = jsonx::to_string(&host).unwrap();
// {name:"db",port:uint16(5432),addr:ip("10.0.0.1"),blob:bytes("aGk=")}
assert_eq!(jsonx::from_str::<Host>(&text).unwrap(), host);
```

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 = "...")]`.

```rust
use std::{fmt, 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")"#);
```

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.

```rust
let v: jsonx::Value = jsonx::from_str(r#"ipnet("10.0.0.0/8")"#).unwrap();
assert_eq!(jsonx::to_string(&v).unwrap(), r#"ipnet("10.0.0.0/8")"#);
```

### Non-greedy decoding

`from_str_partial` decodes a single value and reports where it stopped:

```rust
let (value, offset): (jsonx::Value, usize) =
    jsonx::from_str_partial("{test: 1} blah").unwrap();
assert_eq!(&"{test: 1} blah"[offset..], "blah");
```

## API surface

- `from_str`, `from_slice`, `from_str_partial`
- `to_string`, `to_string_pretty`, `to_string_indent`, `to_vec`,
  `to_writer`, `to_writer_pretty`
- `Value` (including `Value::Constructor` and the `Value::{int, uint, bytes,
  string, constructor}` builders and `Value::to_jsonx_arg`), `Map`, `DateTime`
- wrappers: `Bytes`, `Int`, `Uint`, `Ip`, `IpPort`, `Datetime` (each a public
  `JsonxConstructor`, so `Datetime(dt).to_jsonx_arg()` etc. yields the canonical
  argument text)
- `#[serde(with)]` modules: `ip`, `ipport`, `datetime` (plus the public
  `datetime::to_jsonx_string` / `datetime::parse` helpers)
- open extension: `#[derive(JsonxConstructor)]` (default `derive` feature), the
  `JsonxConstructor` trait, the `ctor!` macro, and the `constructor` module
  (`serialize` / `deserialize` for `#[serde(with)]`, plus the low-level
  `serialize_constructor` / `deserialize_constructor`)
- `Serializer`, `Deserializer` for advanced/streaming use
- `Error`, `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 `BTreeMap` backs `Value`), giving
  deterministic, input-order-independent encodings. Enable the `preserve_order`
  feature to back `Value` with `indexmap::IndexMap` and keep author key order
  instead (matching the other encoders most projects pair this with).
- `int64`/`uint64` values outside the `±(2^53 − 1)` safe-integer range are
  quoted (e.g. `int64("9223372036854775807")`) so they survive a round trip
  through a JavaScript `Number`.
- 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.