#
Struson
[![crates.io](https://img.shields.io/crates/v/struson)](https://crates.io/crates/struson) [![docs.rs](https://img.shields.io/docsrs/struson?label=docs.rs)](https://docs.rs/struson)
Struson is an [RFC 8259](https://www.rfc-editor.org/rfc/rfc8259.html) compliant streaming JSON reader and writer. Its main purpose is to allow writing JSON documents in a memory efficient way without having to store the complete JSON document structure in memory. The API of Struson was inspired by the streaming API of the Java library [Gson](https://github.com/google/gson) (classes `JsonReader` and `JsonWriter`). It is rather low-level and its methods correspond to the elements of a JSON document, with little abstraction on top of it, allowing to read and write any valid JSON document regardless of its structure or content. **Note:** This library is still experimental. The performance is not very good yet and the API might be changed in future versions; releases < 1.0.0 might not follow [Semantic Versioning](https://semver.org/), breaking changes may occur.\ Feedback and suggestions for improvements are welcome! ## Why? The most popular JSON Rust crates [Serde JSON (`serde_json`)](https://github.com/serde-rs/json) and [json-rust (`json`)](https://github.com/maciejhirsz/json-rust) mainly provide high level APIs for working with JSON. - Serde JSON provides an API for converting JSON into DOM like structures (module `serde_json::value`) and object mapper functionality by converting structs to JSON and vice versa. Both requires the complete value to be present in memory. The trait `serde_json::ser::Formatter` actually allows writing JSON in a streaming way, but its API is arguably too low level and inconvenient to use: You have to handle string escaping yourself, and you always have to provide the writer as argument for every method call.\ Note however, that Serde JSON's [`StreamDeserializer`](https://docs.rs/serde_json/latest/serde_json/struct.StreamDeserializer.html) allows reading multiple top-level values in a streaming way, and that certain streaming use cases can be solved with custom `Visitor` implementations, see the documentation for examples of [streaming an array](https://serde.rs/stream-array.html) and [discarding data](https://serde.rs/ignored-any.html). - json-rust provides an API for converting JSON into DOM like structures (enum `json::JsonValue`), this requires the complete value to be present in memory. The trait `json::codegen::Generator` offers a partial API for writing JSON in a streaming way, however it misses methods for writing JSON arrays and objects in a streaming way. If you need to process JSON in a DOM like way or want object mapper functionality to convert structs to JSON and vice versa, then Struson is _not_ suited for your use case and you should instead use one of the libraries above. ## Main features - Low level streaming API, no implicit value conversion - Strong enforcement of correct API usage - Panics only for incorrect API usage\ Malformed JSON and unexpected JSON structure only causes errors - API does not require recursion for JSON arrays and objects\ Can theoretically read and write arbitrarily deeply nested JSON data - Read and write arbitrarily precise JSON numbers as string\ ([`JsonReader::next_number_as_str`](https://docs.rs/struson/latest/struson/reader/trait.JsonReader.html#tymethod.next_number_as_str) and [`JsonWriter::number_value_from_string`](https://docs.rs/struson/latest/struson/writer/trait.JsonWriter.html#tymethod.number_value_from_string)) - Seek to specific location in JSON data ([`JsonReader::seek_to`](https://docs.rs/struson/latest/struson/reader/trait.JsonReader.html#tymethod.seek_to)) - Transfer JSON data from a reader to a writer ([`JsonReader::transfer_to`](https://docs.rs/struson/latest/struson/reader/trait.JsonReader.html#tymethod.transfer_to)) - Read and write arbitrarily large JSON string values\ ([`JsonReader::next_string_reader`](https://docs.rs/struson/latest/struson/reader/trait.JsonReader.html#tymethod.next_string_reader) and [`JsonWriter::string_value_writer`](https://docs.rs/struson/latest/struson/writer/trait.JsonWriter.html#tymethod.string_value_writer)) - Optional [Serde integration](#serde-integration) ## Usage examples Two variants of the API are provided: - simple: ensures correct API usage at compile-time - advanced: ensures correct API usage only at runtime (by panicking); more flexible and provides more functionality ### Simple API **🔬 Experimental**\ The simple API and its naming is currently experimental, please provide feedback [here](https://github.com/Marcono1234/struson/issues/34). It has to be enabled by specifying the `experimental` feature in `Cargo.toml`: ```toml [dependencies] struson = { version = "...", features = ["experimental"] } ``` Any feedback is appreciated! #### Reading See [`SimpleJsonReader`](https://docs.rs/struson/latest/struson/reader/simple/struct.SimpleJsonReader.html). ```rust use struson::reader::simple::*; // In this example JSON data comes from a string; // normally it would come from a file or a network connection let json_reader = SimpleJsonReader::new(r#"["a", "short", "example"]"#.as_bytes()); let mut words = Vec::::new(); json_reader.read_array_items(|item_reader| { let word = item_reader.read_string()?; words.push(word); Ok(()) })?; assert_eq!(words, vec!["a", "short", "example"]); ``` For reading nested values, the methods [`read_seeked`](https://docs.rs/struson/latest/struson/reader/simple/trait.ValueReader.html#tymethod.read_seeked) and [`read_seeked_multi`](https://docs.rs/struson/latest/struson/reader/simple/trait.ValueReader.html#tymethod.read_seeked_multi) can be used: ```rust use struson::reader::simple::*; use struson::reader::simple::multi_json_path::multi_json_path; // In this example JSON data comes from a string; // normally it would come from a file or a network connection let json = r#"{ "users": [ {"name": "John", "age": 32}, {"name": "Jane", "age": 41} ] }"#; let json_reader = SimpleJsonReader::new(json.as_bytes()); let mut ages = Vec::::new(); // Select the ages of all users let json_path = multi_json_path!["users", [*], "age"]; json_reader.read_seeked_multi(&json_path, false, |value_reader| { let age = value_reader.read_number()??; ages.push(age); Ok(()) })?; assert_eq!(ages, vec![32, 41]); ``` #### Writing See [`SimpleJsonWriter`](https://docs.rs/struson/latest/struson/writer/simple/struct.SimpleJsonWriter.html). ```rust use struson::writer::simple::*; // In this example JSON bytes are stored in a Vec; // normally they would be written to a file or network connection let mut writer = Vec::::new(); let json_writer = SimpleJsonWriter::new(&mut writer); json_writer.write_object(|object_writer| { object_writer.write_number_member("a", 1)?; object_writer.write_bool_member("b", true)?; Ok(()) })?; let json = String::from_utf8(writer)?; assert_eq!(json, r#"{"a":1,"b":true}"#); ``` ### Advanced API #### Reading See [`JsonStreamReader`](https://docs.rs/struson/latest/struson/reader/struct.JsonStreamReader.html). ```rust use struson::reader::*; // In this example JSON data comes from a string; // normally it would come from a file or a network connection let json = r#"{"a": [1, true]}"#; let mut json_reader = JsonStreamReader::new(json.as_bytes()); json_reader.begin_object()?; assert_eq!(json_reader.next_name()?, "a"); json_reader.begin_array()?; assert_eq!(json_reader.next_number::()??, 1); assert_eq!(json_reader.next_bool()?, true); json_reader.end_array()?; json_reader.end_object()?; // Ensures that there is no trailing data json_reader.consume_trailing_whitespace()?; ``` #### Writing See [`JsonStreamWriter`](https://docs.rs/struson/latest/struson/writer/struct.JsonStreamWriter.html). ```rust use struson::writer::*; // In this example JSON bytes are stored in a Vec; // normally they would be written to a file or network connection let mut writer = Vec::::new(); let mut json_writer = JsonStreamWriter::new(&mut writer); json_writer.begin_object()?; json_writer.name("a")?; json_writer.begin_array()?; json_writer.number_value(1)?; json_writer.bool_value(true)?; json_writer.end_array()?; json_writer.end_object()?; // Ensures that the JSON document is complete and flushes the buffer json_writer.finish_document()?; let json = String::from_utf8(writer)?; assert_eq!(json, r#"{"a":[1,true]}"#); ``` ## Serde integration Optional integration with [Serde](https://docs.rs/serde/latest/serde/) exists to allow writing a `Serialize` to a `JsonWriter` and reading a `Deserialize` from a `JsonReader`. See the [`serde` module](https://docs.rs/struson/latest/struson/serde/index.html) of this crate for more information. ## Changelog See [GitHub releases](https://github.com/Marcono1234/struson/releases). ## Building This project uses [cargo-make](https://github.com/sagiegurari/cargo-make) for building: ```sh cargo make ``` If you don't want to install cargo-make, you can instead manually run the tasks declared in the [`Makefile.toml`](Makefile.toml). ## Similar projects - > A streaming JSON parser/emitter library for rust - > JSON Tokenizer written in Rust - > JSON serialization/deserialization (full-featured, modern, streaming, direct into struct/enum) - > Rust re-implementation of the Python streaming JSON parser [ijson](https://github.com/isagalaev/ijson) - > A zero-copy json-lexer, filters and serializer. - > High-fidelity JSON lexer and parser - 's `justjson::parser::Tokenizer` > A JSON tokenizer, which converts JSON source to a series of Tokens - > Json pull parser - [rustc-serialize `Parser`](https://docs.rs/rustc-serialize/latest/rustc_serialize/json/struct.Parser.html) (deprecated) > A streaming JSON parser implemented as an iterator of JsonEvent, consuming an iterator of char. ## License Licensed under either of - [Apache License, Version 2.0](LICENSE-APACHE) - [MIT License](LICENSE-MIT) at your option. All contributions you make to this project are licensed implicitly under both licenses mentioned above, without any additional terms or conditions. Note: This dual-licensing is the same you see for the majority of Rust projects, see also the [Rust API Guidelines](https://rust-lang.github.io/api-guidelines/necessities.html#crate-and-its-dependencies-have-a-permissive-license-c-permissive).