Add README and examples
This commit is contained in:
@@ -0,0 +1,111 @@
|
|||||||
|
# wson
|
||||||
|
|
||||||
|
Rust implementation of the WSON text format and JBON binary format, with serde support.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Serde API for ordinary acyclic Rust data:
|
||||||
|
- text: `wson::to_string`, `wson::from_str`
|
||||||
|
- binary JBON: `wson::to_vec`, `wson::from_slice`
|
||||||
|
- Graph API for WSON reference/cycle support:
|
||||||
|
- text references: `(0){"child":$0}`
|
||||||
|
- binary references: marker `0x06` for ids and `0x05` for references
|
||||||
|
- Java-compatible defaults from `net.woggioni:wson`:
|
||||||
|
- sorted objects (`BTreeMap`)
|
||||||
|
- parser `max_depth = 1_048_576`
|
||||||
|
- `serialize_references = false` unless enabled
|
||||||
|
- Signed zigzag LEB128 and byte-reversed double encoding compatible with `net.woggioni:jwo`.
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
wson = "0.1"
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
struct Config {
|
||||||
|
name: String,
|
||||||
|
enabled: bool,
|
||||||
|
retries: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> wson::Result<()> {
|
||||||
|
let value = Config {
|
||||||
|
name: "worker".to_string(),
|
||||||
|
enabled: true,
|
||||||
|
retries: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
let text = wson::to_string(&value)?;
|
||||||
|
assert_eq!(text, r#"{"enabled":true,"name":"worker","retries":3}"#);
|
||||||
|
let decoded: Config = wson::from_str(&text)?;
|
||||||
|
assert_eq!(decoded, value);
|
||||||
|
|
||||||
|
let bytes = wson::to_vec(&value)?;
|
||||||
|
let decoded: Config = wson::from_slice(&bytes)?;
|
||||||
|
assert_eq!(decoded, value);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## References and cycles
|
||||||
|
|
||||||
|
Serde's data model is acyclic, so cyclic/shared WSON values use the graph API:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use wson::{Config, Node};
|
||||||
|
|
||||||
|
fn main() -> wson::Result<()> {
|
||||||
|
let cfg = Config {
|
||||||
|
serialize_references: true,
|
||||||
|
..Config::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let graph = wson::text::parse_str("(0){\"child\":$0,\"id\":25}", &cfg)?;
|
||||||
|
let root = graph.root();
|
||||||
|
let child = match graph.node(root) {
|
||||||
|
Node::Object(entries) => *entries.get("child").unwrap(),
|
||||||
|
_ => panic!("expected object"),
|
||||||
|
};
|
||||||
|
assert_eq!(root, child);
|
||||||
|
|
||||||
|
let text = wson::text::dump_graph(&graph, &cfg)?;
|
||||||
|
let jbon = wson::binary::dump_graph(&graph, &cfg)?;
|
||||||
|
println!("{text} ({} JBON bytes)", jbon.len());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Compatibility notes
|
||||||
|
|
||||||
|
- WSON integers are signed 64-bit. `u64` values above `i64::MAX` fail to serialize.
|
||||||
|
- WSON floats are `f64`. Text output uses Java `Double.toString`-style formatting.
|
||||||
|
- The text parser intentionally mirrors the Java parser's leniency: commas/colons are not strictly validated, and unknown characters are skipped.
|
||||||
|
- The parser enforces `Config::max_depth`; dumpers do not limit depth, but cyclic graphs require `serialize_references`.
|
||||||
|
- Object keys are sorted by default to match the Java default `TreeMap` object implementation.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run --example serde_text
|
||||||
|
cargo run --example serde_jbon
|
||||||
|
cargo run --example graph_references
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --check
|
||||||
|
cargo clippy --all-targets -- -D warnings
|
||||||
|
cargo test
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
use wson::{Config, Node};
|
||||||
|
|
||||||
|
fn main() -> wson::Result<()> {
|
||||||
|
let cfg = Config {
|
||||||
|
serialize_references: true,
|
||||||
|
..Config::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let graph = wson::text::parse_str("(0){\"child\":$0,\"id\":25}", &cfg)?;
|
||||||
|
let root = graph.root();
|
||||||
|
let child = match graph.node(root) {
|
||||||
|
Node::Object(entries) => *entries.get("child").expect("child entry"),
|
||||||
|
other => panic!("expected object, got {other:?}"),
|
||||||
|
};
|
||||||
|
assert_eq!(root, child);
|
||||||
|
println!("root and child are the same node: {root:?}");
|
||||||
|
|
||||||
|
let text = wson::text::dump_graph(&graph, &cfg)?;
|
||||||
|
println!("text: {text}");
|
||||||
|
|
||||||
|
let jbon = wson::binary::dump_graph(&graph, &cfg)?;
|
||||||
|
println!("jbon length: {} bytes", jbon.len());
|
||||||
|
|
||||||
|
let reparsed = wson::binary::parse_slice(&jbon, &cfg)?;
|
||||||
|
let reparsed_root = reparsed.root();
|
||||||
|
let reparsed_child = match reparsed.node(reparsed_root) {
|
||||||
|
Node::Object(entries) => *entries.get("child").expect("child entry"),
|
||||||
|
other => panic!("expected object, got {other:?}"),
|
||||||
|
};
|
||||||
|
assert_eq!(reparsed_root, reparsed_child);
|
||||||
|
println!("binary round trip ok");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
struct ServiceConfig {
|
||||||
|
name: String,
|
||||||
|
enabled: bool,
|
||||||
|
retries: i64,
|
||||||
|
endpoints: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> wson::Result<()> {
|
||||||
|
let value = ServiceConfig {
|
||||||
|
name: "worker".to_string(),
|
||||||
|
enabled: true,
|
||||||
|
retries: 3,
|
||||||
|
endpoints: vec![
|
||||||
|
"https://a.example".to_string(),
|
||||||
|
"https://b.example".to_string(),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let bytes = wson::to_vec(&value)?;
|
||||||
|
println!("jbon length: {} bytes", bytes.len());
|
||||||
|
println!("jbon prefix: {:02x?}", &bytes[..bytes.len().min(16)]);
|
||||||
|
|
||||||
|
let decoded: ServiceConfig = wson::from_slice(&bytes)?;
|
||||||
|
assert_eq!(decoded, value);
|
||||||
|
println!("round trip ok");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
struct ServiceConfig {
|
||||||
|
name: String,
|
||||||
|
enabled: bool,
|
||||||
|
retries: i64,
|
||||||
|
endpoints: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> wson::Result<()> {
|
||||||
|
let value = ServiceConfig {
|
||||||
|
name: "worker".to_string(),
|
||||||
|
enabled: true,
|
||||||
|
retries: 3,
|
||||||
|
endpoints: vec![
|
||||||
|
"https://a.example".to_string(),
|
||||||
|
"https://b.example".to_string(),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let text = wson::to_string(&value)?;
|
||||||
|
println!("text: {text}");
|
||||||
|
|
||||||
|
let decoded: ServiceConfig = wson::from_str(&text)?;
|
||||||
|
assert_eq!(decoded, value);
|
||||||
|
println!("round trip ok");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user