aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChia-You Chen <hortune@google.com>2023-03-20 06:35:59 +0000
committerChia-You Chen <hortune@google.com>2023-03-20 06:37:22 +0000
commit8510b006a6a6473e19ee115c373e70595dbcfa5f (patch)
treebd849b13c38135fd3569ab230fce549c43883ca4
parentb6416d5eaa20b40ae73d74d3c820641ae1a7e2e5 (diff)
downloadtinyjson-main-16k.tar.gz
Import tinyjson crate.main-16k
Import this due to the dependency from rules_rust. Bug: 272102666 Change-Id: Ifbc4d61a7302701738e837e1036d7a6ab157ad24
-rw-r--r--.gitattributes2
-rw-r--r--.github/workflows/ci.yaml51
-rw-r--r--.gitignore3
-rw-r--r--.rustfmt.toml1
-rw-r--r--Cargo.toml44
-rw-r--r--Cargo.toml.orig30
l---------LICENSE1
-rw-r--r--LICENSE.txt21
-rw-r--r--METADATA32
-rw-r--r--MODULE_LICENSE_MIT0
-rw-r--r--OWNERS1
-rw-r--r--README.md93
-rw-r--r--examples/README.md8
-rw-r--r--src/generator.rs330
-rw-r--r--src/json_value.rs796
-rw-r--r--src/lib.rs90
-rw-r--r--src/parser.rs470
-rw-r--r--tests/assets/JSON-Schema-Test-Suite/README.md3
-rw-r--r--tests/assets/JSONTestSuite/README.md3
-rw-r--r--tests/assets/jsonorg/README.md5
20 files changed, 1984 insertions, 0 deletions
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..47d64b7
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+/bench/assets -text
+/bench/assets linguist-generated
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 0000000..e6711f3
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,51 @@
+name: CI
+on: [push, pull_request]
+env:
+ CARGO_TERM_COLOR: always
+ RUST_BACKTRACE: 1
+
+jobs:
+ unit-test:
+ name: unit tests
+ strategy:
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ fail-fast: false
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v3
+ - uses: dtolnay/rust-toolchain@stable
+ - name: Run tests
+ run: cargo test --color always --all
+ linter:
+ name: clippy and rustfmt
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: dtolnay/rust-toolchain@stable
+ with:
+ components: rustfmt, clippy
+ - run: cargo fmt --all -- --color always --check
+ - run: cargo clippy --color always --all -- -D warnings
+ examples:
+ name: examples
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: dtolnay/rust-toolchain@stable
+ - name: Run parse example
+ run: |
+ ret="$(echo '{"hello": "world"}' | cargo run --example parse)"
+ if [[ "$ret" != 'Parsed: Object({"hello": String("world")})' ]]; then
+ echo "Error: '$ret'" 2>&1
+ exit 1
+ fi
+ - name: Run minify example
+ run: |
+ ret="$(echo ' { "hello" : "world" } ' | cargo run --example minify)"
+ if [[ "$ret" != '{"hello":"world"}' ]]; then
+ echo "Error: '$ret'" 2>&1
+ exit 1
+ fi
+ - name: Run json_value example
+ run: cargo run --example json_value
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..59767ff
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+target
+Cargo.lock
+/src/main.rs
diff --git a/.rustfmt.toml b/.rustfmt.toml
new file mode 100644
index 0000000..32a9786
--- /dev/null
+++ b/.rustfmt.toml
@@ -0,0 +1 @@
+edition = "2018"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..48067d3
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,44 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2018"
+name = "tinyjson"
+version = "2.5.1"
+authors = ["rhysd <lin90162@yahoo.co.jp>"]
+include = [
+ "Cargo.toml",
+ "README.md",
+ "LICENSE.txt",
+ "src/*.rs",
+]
+description = "Tiny simple JSON parser/generator"
+homepage = "https://github.com/rhysd/tinyjson"
+readme = "README.md"
+keywords = [
+ "json",
+ "parser",
+ "generator",
+]
+categories = [
+ "encoding",
+ "parsing",
+]
+license = "MIT"
+repository = "https://github.com/rhysd/tinyjson"
+
+[dependencies]
+
+[dev-dependencies.walkdir]
+version = "2"
+
+[badges.maintenance]
+status = "passively-maintained"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..dfbea0a
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,30 @@
+[package]
+name = "tinyjson"
+version = "2.5.1"
+edition = "2018"
+authors = ["rhysd <lin90162@yahoo.co.jp>"]
+description = "Tiny simple JSON parser/generator"
+license = "MIT"
+readme = "README.md"
+homepage = "https://github.com/rhysd/tinyjson"
+repository = "https://github.com/rhysd/tinyjson"
+keywords = ["json", "parser", "generator"]
+categories = ["encoding", "parsing"]
+
+include = [
+ "Cargo.toml",
+ "README.md",
+ "LICENSE.txt",
+ "src/*.rs",
+]
+
+[badges]
+maintenance = { status = "passively-maintained" }
+
+[dependencies]
+
+[dev-dependencies]
+walkdir = "2"
+
+[workspace]
+members = ["bench"]
diff --git a/LICENSE b/LICENSE
new file mode 120000
index 0000000..85de3d4
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1 @@
+LICENSE.txt \ No newline at end of file
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..108178e
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,21 @@
+the MIT License
+
+Copyright (c) 2016 rhysd
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
+THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..15f8c3d
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,32 @@
+name: "tinyjson"
+description:
+ "tinyjson is a library to parse/generate JSON format document. "
+ " "
+ "Goals of this library are "
+ " "
+ "Simplicity: This library uses standard containers like Vec or HashMap as "
+ "its internal representation and exposes it to users. Users can operate "
+ "JSON values via the standard APIs. And it keeps this crate as small as "
+ "possible. "
+ "Explicit: This library does not hide memory allocation from users. You "
+ "need to allocate memory like Vec, String, HashMap by yourself. It is good "
+ "for readers of your source code to show where memory allocations happen. "
+ "And you can have control of how memory is allocated (e.g. allocating "
+ "memory in advance with with_capacity method). "
+ "No dependencies: This library is built on top of only standard libraries. "
+ "No unsafe code: This library is built with Safe Rust. "
+ "Well tested: This library is tested with famous test suites: "
+ "JSON checker in json.org "
+ "JSONTestSuite "
+ "JSON-Schema-Test-Suite "
+ ""
+
+third_party {
+ url {
+ type: ARCHIVE
+ value: "https://crates.io/crates/tinyjson"
+ }
+ version: "v2.5.1"
+ last_upgrade_date { year: 2023 month: 2 day: 13 }
+ license_type: NOTICE
+}
diff --git a/MODULE_LICENSE_MIT b/MODULE_LICENSE_MIT
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_MIT
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..45dc4dd
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1 @@
+include platform/prebuilts/rust:master:/OWNERS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d5a2e99
--- /dev/null
+++ b/README.md
@@ -0,0 +1,93 @@
+tinyjson
+========
+[![version](https://img.shields.io/crates/v/tinyjson.svg)](https://crates.io/crates/tinyjson)
+[![CI](https://github.com/rhysd/tinyjson/workflows/CI/badge.svg?branch=master&event=push)](https://github.com/rhysd/tinyjson/actions)
+
+[tinyjson](https://crates.io/crates/tinyjson) is a library to parse/generate JSON format document.
+
+Goals of this library are
+
+- **Simplicity**: This library uses standard containers like `Vec` or `HashMap` as its internal representation
+ and exposes it to users. Users can operate JSON values via the standard APIs. And it keeps this crate as small
+ as possible.
+- **Explicit**: This library does not hide memory allocation from users. You need to allocate memory like `Vec`,
+ `String`, `HashMap` by yourself. It is good for readers of your source code to show where memory allocations
+ happen. And you can have control of how memory is allocated (e.g. allocating memory in advance with
+ `with_capacity` method).
+- **No dependencies**: This library is built on top of only standard libraries.
+- **No unsafe code**: This library is built with Safe Rust.
+- **Well tested**: This library is tested with famous test suites:
+ - [JSON checker in json.org](http://www.json.org/JSON_checker/)
+ - [JSONTestSuite](https://github.com/nst/JSONTestSuite)
+ - [JSON-Schema-Test-Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite)
+
+[Documentation](https://docs.rs/tinyjson/latest/tinyjson)
+
+## Requirements
+
+Rust stable toolchain.
+
+## Installation
+
+Add this crate to `dependencies` section of your `Cargo.toml`
+
+```toml
+[dependencies]
+tinyjson = "2"
+```
+
+## Example
+
+```rust
+use tinyjson::JsonValue;
+use std::collections::HashMap;
+use std::convert::TryInto;
+
+let s = r#"
+ {
+ "bool": true,
+ "arr": [1, null, "test"],
+ "nested": {
+ "blah": false,
+ "blahblah": 3.14
+ },
+ "unicode": "\u2764"
+ }
+"#;
+
+// Parse from strings
+let parsed: JsonValue = s.parse().unwrap();
+
+// Access to inner value represented with standard containers
+let object: &HashMap<_, _> = parsed.get().unwrap();
+println!("Parsed HashMap: {:?}", object);
+
+// Generate JSON string
+println!("{}", parsed.stringify().unwrap());
+// Generate formatted JSON string with indent
+println!("{}", parsed.format().unwrap());
+
+// Convert to inner value represented with standard containers
+let object: HashMap<_, _> = parsed.try_into().unwrap();
+println!("Converted into HashMap: {:?}", object);
+
+// Create JSON values from standard containers
+let mut m = HashMap::new();
+m.insert("foo".to_string(), true.into());
+let mut v = JsonValue::from(m);
+
+// Access with `Index` and `IndexMut` operators quickly
+println!("{:?}", v["foo"]);
+v["foo"] = JsonValue::from("hello".to_string());
+println!("{:?}", v["foo"]);
+```
+
+See [the document](https://docs.rs/tinyjson/latest/tinyjson) to know all APIs.
+
+## Repository
+
+https://github.com/rhysd/tinyjson
+
+## License
+
+[the MIT License](LICENSE.txt)
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 0000000..abd0360
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,8 @@
+Working examples are put in this directory. They can be run with `cargo run --example`.
+
+```sh
+echo '{"hello": "world"}' | cargo run --example parse
+echo '["foo", 42, null ]' | cargo run --example minify
+cargo run --example json_value
+```
+
diff --git a/src/generator.rs b/src/generator.rs
new file mode 100644
index 0000000..079f98f
--- /dev/null
+++ b/src/generator.rs
@@ -0,0 +1,330 @@
+use crate::JsonValue;
+use std::collections::HashMap;
+use std::fmt;
+use std::io::{self, Write};
+
+/// Serialization error. This error only happens when some write error happens on writing the serialized byte sequence
+/// to the given `io::Write` object.
+#[derive(Debug)]
+pub struct JsonGenerateError {
+ msg: String,
+}
+
+impl JsonGenerateError {
+ pub fn message(&self) -> &str {
+ self.msg.as_str()
+ }
+}
+
+impl fmt::Display for JsonGenerateError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "Generate error: {}", &self.msg)
+ }
+}
+
+impl std::error::Error for JsonGenerateError {}
+
+/// Convenient type alias for serialization results.
+pub type JsonGenerateResult = Result<String, JsonGenerateError>;
+
+/// JSON serializer for `JsonValue`.
+///
+/// Basically you don't need to use this struct directly since `JsonValue::stringify` or `JsonValue::format` methods are
+/// using this internally.
+///
+/// ```
+/// use tinyjson::{JsonGenerator, JsonValue};
+///
+/// let v = JsonValue::from("hello, world".to_string());
+/// let mut buf = vec![];
+/// let mut gen = JsonGenerator::new(&mut buf);
+/// gen.generate(&v).unwrap();
+///
+/// assert_eq!(String::from_utf8(buf).unwrap(), "\"hello, world\"");
+/// ```
+pub struct JsonGenerator<'indent, W: Write> {
+ out: W,
+ indent: Option<&'indent str>,
+}
+
+impl<'indent, W: Write> JsonGenerator<'indent, W> {
+ /// Create a new `JsonGenerator` object. The serialized byte sequence will be written to the given `io::Write`
+ /// object.
+ pub fn new(out: W) -> Self {
+ Self { out, indent: None }
+ }
+
+ /// Set indent string. This will be used by [`JsonGenerator::generate`].
+ /// ```
+ /// use tinyjson::{JsonGenerator, JsonValue};
+ ///
+ /// let v = JsonValue::from(vec![1.0.into(), 2.0.into(), 3.0.into()]);
+ /// let mut buf = vec![];
+ /// let mut gen = JsonGenerator::new(&mut buf).indent(" ");
+ /// gen.generate(&v).unwrap();
+ ///
+ /// assert_eq!(String::from_utf8(buf).unwrap(),
+ /// "[
+ /// 1,
+ /// 2,
+ /// 3
+ /// ]");
+ /// ```
+ pub fn indent(mut self, indent: &'indent str) -> Self {
+ self.indent = Some(indent);
+ self
+ }
+
+ fn quote(&mut self, s: &str) -> io::Result<()> {
+ const B: u8 = b'b'; // \x08
+ const T: u8 = b't'; // \x09
+ const N: u8 = b'n'; // \x0a
+ const F: u8 = b'f'; // \x0c
+ const R: u8 = b'r'; // \x0d
+ const Q: u8 = b'"'; // \x22
+ const S: u8 = b'\\'; // \x5c
+ const U: u8 = 1; // non-printable
+
+ #[rustfmt::skip]
+ const ESCAPE_TABLE: [u8; 256] = [
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ U, U, U, U, U, U, U, U, B, T, N, U, F, R, U, U, // 0
+ U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, // 1
+ 0, 0, Q, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, S, 0, 0, 0, // 5
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 7
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F
+ ];
+
+ self.out.write_all(b"\"")?;
+ let mut start = 0;
+ for (i, c) in s.char_indices() {
+ let u = c as usize;
+ if u < 256 {
+ let esc = ESCAPE_TABLE[u];
+ if esc == 0 {
+ continue;
+ }
+ if start != i {
+ self.out.write_all(s[start..i].as_bytes())?;
+ }
+ if esc == U {
+ write!(self.out, "\\u{:04x}", u)?;
+ } else {
+ self.out.write_all(&[b'\\', esc])?;
+ }
+ start = i + 1;
+ }
+ }
+ if start != s.len() {
+ self.out.write_all(s[start..].as_bytes())?;
+ }
+ self.out.write_all(b"\"")
+ }
+
+ fn number(&mut self, f: f64) -> io::Result<()> {
+ if f.is_infinite() {
+ Err(io::Error::new(
+ io::ErrorKind::Other,
+ "JSON cannot represent inf",
+ ))
+ } else if f.is_nan() {
+ Err(io::Error::new(
+ io::ErrorKind::Other,
+ "JSON cannot represent NaN",
+ ))
+ } else {
+ write!(self.out, "{}", f)
+ }
+ }
+
+ fn encode_array(&mut self, array: &[JsonValue]) -> io::Result<()> {
+ self.out.write_all(b"[")?;
+ let mut first = true;
+ for elem in array.iter() {
+ if first {
+ first = false;
+ } else {
+ self.out.write_all(b",")?;
+ }
+ self.encode(elem)?;
+ }
+ self.out.write_all(b"]")
+ }
+
+ fn encode_object(&mut self, m: &HashMap<String, JsonValue>) -> io::Result<()> {
+ self.out.write_all(b"{")?;
+ let mut first = true;
+ for (k, v) in m {
+ if first {
+ first = false;
+ } else {
+ self.out.write_all(b",")?;
+ }
+ self.quote(k)?;
+ self.out.write_all(b":")?;
+ self.encode(v)?;
+ }
+ self.out.write_all(b"}")
+ }
+
+ fn encode(&mut self, value: &JsonValue) -> io::Result<()> {
+ match value {
+ JsonValue::Number(n) => self.number(*n),
+ JsonValue::Boolean(b) => write!(self.out, "{}", *b),
+ JsonValue::String(s) => self.quote(s),
+ JsonValue::Null => self.out.write_all(b"null"),
+ JsonValue::Array(a) => self.encode_array(a),
+ JsonValue::Object(o) => self.encode_object(o),
+ }
+ }
+
+ fn write_indent(&mut self, indent: &str, level: usize) -> io::Result<()> {
+ for _ in 0..level {
+ self.out.write_all(indent.as_bytes())?;
+ }
+ Ok(())
+ }
+
+ fn format_array(&mut self, array: &[JsonValue], indent: &str, level: usize) -> io::Result<()> {
+ if array.is_empty() {
+ return self.out.write_all(b"[]");
+ }
+
+ self.out.write_all(b"[\n")?;
+ let mut first = true;
+ for elem in array.iter() {
+ if first {
+ first = false;
+ } else {
+ self.out.write_all(b",\n")?;
+ }
+ self.write_indent(indent, level + 1)?;
+ self.format(elem, indent, level + 1)?;
+ }
+ self.out.write_all(b"\n")?;
+ self.write_indent(indent, level)?;
+ self.out.write_all(b"]")
+ }
+
+ fn format_object(
+ &mut self,
+ m: &HashMap<String, JsonValue>,
+ indent: &str,
+ level: usize,
+ ) -> io::Result<()> {
+ if m.is_empty() {
+ return self.out.write_all(b"{}");
+ }
+
+ self.out.write_all(b"{\n")?;
+ let mut first = true;
+ for (k, v) in m {
+ if first {
+ first = false;
+ } else {
+ self.out.write_all(b",\n")?;
+ }
+ self.write_indent(indent, level + 1)?;
+ self.quote(k)?;
+ self.out.write_all(b": ")?;
+ self.format(v, indent, level + 1)?;
+ }
+ self.out.write_all(b"\n")?;
+ self.write_indent(indent, level)?;
+ self.out.write_all(b"}")
+ }
+
+ fn format(&mut self, value: &JsonValue, indent: &str, level: usize) -> io::Result<()> {
+ match value {
+ JsonValue::Number(n) => self.number(*n),
+ JsonValue::Boolean(b) => write!(self.out, "{}", *b),
+ JsonValue::String(s) => self.quote(s),
+ JsonValue::Null => self.out.write_all(b"null"),
+ JsonValue::Array(a) => self.format_array(a, indent, level),
+ JsonValue::Object(o) => self.format_object(o, indent, level),
+ }
+ }
+
+ /// Serialize the `JsonValue` into UTF-8 byte sequence. The result will be written to the `io::Write` object passed
+ /// at [`JsonGenerator::new`].
+ /// This method serializes the value without indentation by default. But after setting an indent string via
+ /// [`JsonGenerator::indent`], this method will use the indent for elements of array and object.
+ ///
+ /// ```
+ /// use tinyjson::{JsonGenerator, JsonValue};
+ ///
+ /// let v = JsonValue::from(vec![1.0.into(), 2.0.into(), 3.0.into()]);
+ ///
+ /// let mut buf = vec![];
+ /// let mut gen = JsonGenerator::new(&mut buf);
+ /// gen.generate(&v).unwrap();
+ /// assert_eq!(String::from_utf8(buf).unwrap(), "[1,2,3]");
+ ///
+ /// let mut buf = vec![];
+ /// let mut gen = JsonGenerator::new(&mut buf).indent(" "); // with 2-spaces indent
+ /// gen.generate(&v).unwrap();
+ ///
+ /// assert_eq!(String::from_utf8(buf).unwrap(),
+ /// "[
+ /// 1,
+ /// 2,
+ /// 3
+ /// ]");
+ /// ```
+ pub fn generate(&mut self, value: &JsonValue) -> io::Result<()> {
+ if let Some(indent) = &self.indent {
+ self.format(value, indent, 0)
+ } else {
+ self.encode(value)
+ }
+ }
+}
+
+/// Serialize the given `JsonValue` value to `String` without indentation. This method is almost identical to
+/// `JsonValue::stringify` but exists for a historical reason.
+///
+/// ```
+/// use tinyjson::JsonValue;
+///
+/// let v = JsonValue::from(vec![1.0.into(), 2.0.into(), 3.0.into()]);
+/// let s = tinyjson::stringify(&v).unwrap();
+/// assert_eq!(s, "[1,2,3]");
+/// ```
+pub fn stringify(value: &JsonValue) -> JsonGenerateResult {
+ let mut to = Vec::new();
+ let mut gen = JsonGenerator::new(&mut to);
+ gen.generate(value).map_err(|err| JsonGenerateError {
+ msg: format!("{}", err),
+ })?;
+ Ok(String::from_utf8(to).unwrap())
+}
+
+/// Serialize the given `JsonValue` value to `String` with 2-spaces indentation. This method is almost identical to
+/// `JsonValue::format` but exists for a historical reason.
+///
+/// ```
+/// use tinyjson::JsonValue;
+///
+/// let v = JsonValue::from(vec![1.0.into(), 2.0.into(), 3.0.into()]);
+/// let s = tinyjson::format(&v).unwrap();
+/// assert_eq!(s, "[\n 1,\n 2,\n 3\n]");
+/// ```
+pub fn format(value: &JsonValue) -> JsonGenerateResult {
+ let mut to = Vec::new();
+ let mut gen = JsonGenerator::new(&mut to).indent(" ");
+ gen.generate(value).map_err(|err| JsonGenerateError {
+ msg: format!("{}", err),
+ })?;
+ Ok(String::from_utf8(to).unwrap())
+}
diff --git a/src/json_value.rs b/src/json_value.rs
new file mode 100644
index 0000000..d92e309
--- /dev/null
+++ b/src/json_value.rs
@@ -0,0 +1,796 @@
+use crate::generator::{format, stringify, JsonGenerateResult, JsonGenerator};
+use std::collections::HashMap;
+use std::convert::TryFrom;
+use std::fmt;
+use std::io;
+use std::ops::{Index, IndexMut};
+
+const NULL: () = ();
+
+/// Enum to represent one JSON value. Each variant represents corresponding JSON types.
+/// ```
+/// use tinyjson::JsonValue;
+/// use std::convert::TryInto;
+///
+/// // Convert from raw values using `From` trait
+/// let value = JsonValue::from("this is string".to_string());
+///
+/// // Get reference to inner value
+/// let maybe_number: Option<&f64> = value.get();
+/// assert!(maybe_number.is_none());
+/// let maybe_string: Option<&String> = value.get();
+/// assert!(maybe_string.is_some());
+///
+/// // Check type of JSON value
+/// assert!(matches!(value, JsonValue::String(_)));
+/// assert!(value.is_string());
+///
+/// // Convert into raw values using `TryInto` trait
+/// let original_value: String = value.try_into().unwrap();
+/// ```
+#[derive(Debug, Clone, PartialEq)]
+pub enum JsonValue {
+ /// Number type value.
+ Number(f64),
+ /// Boolean type value.
+ Boolean(bool),
+ /// String type value.
+ String(String),
+ /// Null type value.
+ Null,
+ /// Array type value.
+ Array(Vec<JsonValue>),
+ /// Object type value.
+ Object(HashMap<String, JsonValue>),
+}
+
+/// Trait to access to inner value of `JsonValue` as reference.
+///
+/// This is used by several APIs like [`JsonValue::get`] to represent any inner values of [`JsonValue`].
+pub trait InnerAsRef {
+ fn json_value_as(v: &JsonValue) -> Option<&Self>;
+}
+
+macro_rules! impl_inner_ref {
+ ($to:ty, $pat:pat => $val:expr) => {
+ impl InnerAsRef for $to {
+ fn json_value_as(v: &JsonValue) -> Option<&$to> {
+ use JsonValue::*;
+ match v {
+ $pat => Some($val),
+ _ => None,
+ }
+ }
+ }
+ };
+}
+
+impl_inner_ref!(f64, Number(n) => n);
+impl_inner_ref!(bool, Boolean(b) => b);
+impl_inner_ref!(String, String(s) => s);
+impl_inner_ref!((), Null => &NULL);
+impl_inner_ref!(Vec<JsonValue>, Array(a) => a);
+impl_inner_ref!(HashMap<String, JsonValue>, Object(h) => h);
+
+/// Trait to access to inner value of `JsonValue` as mutable reference.
+///
+/// This is a mutable version of [`InnerAsRef`].
+pub trait InnerAsRefMut {
+ fn json_value_as_mut(v: &mut JsonValue) -> Option<&mut Self>;
+}
+
+macro_rules! impl_inner_ref_mut {
+ ($to:ty, $pat:pat => $val:expr) => {
+ impl InnerAsRefMut for $to {
+ fn json_value_as_mut(v: &mut JsonValue) -> Option<&mut $to> {
+ use JsonValue::*;
+ match v {
+ $pat => Some($val),
+ _ => None,
+ }
+ }
+ }
+ };
+}
+
+impl_inner_ref_mut!(f64, Number(n) => n);
+impl_inner_ref_mut!(bool, Boolean(b) => b);
+impl_inner_ref_mut!(String, String(s) => s);
+impl_inner_ref_mut!(Vec<JsonValue>, Array(a) => a);
+impl_inner_ref_mut!(HashMap<String, JsonValue>, Object(h) => h);
+
+// Note: matches! is available from Rust 1.42
+macro_rules! is_xxx {
+ (
+ $(#[$meta:meta])*
+ $name:ident,
+ $variant:pat,
+ ) => {
+ $(#[$meta])*
+ pub fn $name(&self) -> bool {
+ match self {
+ $variant => true,
+ _ => false,
+ }
+ }
+ };
+}
+
+impl JsonValue {
+ /// Get immutable reference to the inner value.
+ ///
+ /// ```
+ /// use tinyjson::JsonValue;
+ ///
+ /// let value: JsonValue = "[1, 2, 3]".parse().unwrap();
+ /// let vec: &Vec<_> = value.get().unwrap();
+ /// assert_eq!(vec[0], JsonValue::from(1.0));
+ ///
+ /// // Try to convert with incorrect type
+ /// assert!(value.get::<f64>().is_none());
+ /// ```
+ pub fn get<T: InnerAsRef>(&self) -> Option<&T> {
+ T::json_value_as(self)
+ }
+
+ /// Get mutable reference to the inner value.
+ ///
+ /// ```
+ /// use tinyjson::JsonValue;
+ ///
+ /// let mut value: JsonValue = "[1, 2, 3]".parse().unwrap();
+ /// let vec: &mut Vec<_> = value.get_mut().unwrap();
+ /// vec[0] = JsonValue::from(false);
+ /// assert_eq!(value.stringify().unwrap(), "[false,2,3]");
+ ///
+ /// // Try to convert with incorrect type
+ /// assert!(value.get_mut::<f64>().is_none());
+ /// ```
+ pub fn get_mut<T: InnerAsRefMut>(&mut self) -> Option<&mut T> {
+ T::json_value_as_mut(self)
+ }
+
+ is_xxx!(
+ /// Check if the inner value is a boolean.
+ ///
+ /// ```
+ /// use tinyjson::JsonValue;
+ ///
+ /// let v = JsonValue::from(true);
+ /// assert!(v.is_bool());
+ /// let v = JsonValue::from(1.0);
+ /// assert!(!v.is_bool());
+ /// ```
+ is_bool,
+ JsonValue::Boolean(_),
+ );
+ is_xxx!(
+ /// Check if the inner value is a number.
+ ///
+ /// ```
+ /// use tinyjson::JsonValue;
+ ///
+ /// let v = JsonValue::from(1.0);
+ /// assert!(v.is_number());
+ /// let v = JsonValue::from(false);
+ /// assert!(!v.is_number());
+ /// ```
+ is_number,
+ JsonValue::Number(_),
+ );
+ is_xxx!(
+ /// Check if the inner value is a string.
+ ///
+ /// ```
+ /// use tinyjson::JsonValue;
+ ///
+ /// let v = JsonValue::from("foo".to_string());
+ /// assert!(v.is_string());
+ /// let v = JsonValue::from(1.0);
+ /// assert!(!v.is_string());
+ /// ```
+ is_string,
+ JsonValue::String(_),
+ );
+ is_xxx!(
+ /// Check if the inner value is null.
+ ///
+ /// ```
+ /// use tinyjson::JsonValue;
+ ///
+ /// let v = JsonValue::from(()); // () is inner representation of null value
+ /// assert!(v.is_null());
+ /// let v = JsonValue::from(false);
+ /// assert!(!v.is_null());
+ /// ```
+ is_null,
+ JsonValue::Null,
+ );
+ is_xxx!(
+ /// Check if the inner value is an array.
+ ///
+ /// ```
+ /// use tinyjson::JsonValue;
+ ///
+ /// let v = JsonValue::from(vec![]);
+ /// assert!(v.is_array());
+ /// let v = JsonValue::from(1.0);
+ /// assert!(!v.is_array());
+ /// ```
+ is_array,
+ JsonValue::Array(_),
+ );
+ is_xxx!(
+ /// Check if the inner value is an object.
+ ///
+ /// ```
+ /// use tinyjson::JsonValue;
+ /// use std::collections::HashMap;
+ ///
+ /// let v = JsonValue::from(HashMap::new());
+ /// assert!(v.is_object());
+ /// let v = JsonValue::from(vec![]);
+ /// assert!(!v.is_object());
+ /// ```
+ is_object,
+ JsonValue::Object(_),
+ );
+
+ /// Convert this JSON value to `String` value.
+ ///
+ /// ```
+ /// use tinyjson::JsonValue;
+ ///
+ /// let v = JsonValue::from(vec![1.0.into(), true.into(), "str".to_string().into()]);
+ /// let s = v.stringify().unwrap();
+ /// assert_eq!(&s, "[1,true,\"str\"]");
+ /// ```
+ pub fn stringify(&self) -> JsonGenerateResult {
+ stringify(self)
+ }
+
+ /// Write this JSON value to the given `io::Write` object as UTF-8 byte sequence.
+ ///
+ /// ```
+ /// use tinyjson::JsonValue;
+ /// use std::io::Write;
+ ///
+ /// let v = JsonValue::from(vec![1.0.into(), true.into(), "str".to_string().into()]);
+ /// let mut bytes = vec![];
+ /// v.write_to(&mut bytes).unwrap();
+ /// assert_eq!(&String::from_utf8(bytes).unwrap(), "[1,true,\"str\"]");
+ /// ```
+ pub fn write_to<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
+ JsonGenerator::new(w).generate(self)
+ }
+
+ /// Convert this JSON value to `String` value with 2-spaces indentation.
+ ///
+ /// ```
+ /// use tinyjson::JsonValue;
+ ///
+ /// let v = JsonValue::from(vec![1.0.into(), true.into(), "str".to_string().into()]);
+ /// let s = v.format().unwrap();
+ /// assert_eq!(&s,
+ /// "[
+ /// 1,
+ /// true,
+ /// \"str\"
+ /// ]");
+ /// ```
+ pub fn format(&self) -> JsonGenerateResult {
+ format(self)
+ }
+
+ /// Write this JSON value to the given `io::Write` object as UTF-8 byte sequence with 2-spaces indentation.
+ ///
+ /// ```
+ /// use tinyjson::JsonValue;
+ ///
+ /// let v = JsonValue::from(vec![1.0.into(), true.into(), "str".to_string().into()]);
+ /// let mut bytes = vec![];
+ /// v.format_to(&mut bytes).unwrap();
+ /// assert_eq!(&String::from_utf8(bytes).unwrap(),
+ /// "[
+ /// 1,
+ /// true,
+ /// \"str\"
+ /// ]");
+ /// ```
+ pub fn format_to<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
+ JsonGenerator::new(w).indent(" ").generate(self)
+ }
+}
+
+/// Access to value of the key of object.
+///
+/// ```
+/// use tinyjson::JsonValue;
+/// use std::collections::HashMap;
+///
+/// let mut m = HashMap::new();
+/// m.insert("foo".to_string(), 1.0.into());
+/// let v = JsonValue::from(m);
+/// let i = &v["foo"];
+/// assert_eq!(i, &JsonValue::Number(1.0));
+/// ```
+///
+/// This will panic when the given `JsonValue` value is not an object
+///
+/// ```should_panic
+/// # use tinyjson::JsonValue;
+/// let v = JsonValue::from(vec![]);
+/// let _ = &v["foo"]; // Panic
+/// ```
+///
+/// or when the key does not exist in the object.
+///
+/// ```should_panic
+/// # use tinyjson::JsonValue;
+/// # use std::collections::HashMap;
+/// let v = JsonValue::from(HashMap::new());
+/// let _ = &v["foo"]; // Panic
+/// ```
+///
+/// Using this operator, you can access the nested elements quickly
+///
+/// ```
+/// # use tinyjson::JsonValue;
+/// let mut json: JsonValue = r#"
+/// {
+/// "foo": {
+/// "bar": [
+/// { "target": 42 }
+/// ]
+/// }
+/// }
+/// "#.parse().unwrap();
+///
+/// // Access with index operator
+/// let target_value: f64 = *json["foo"]["bar"][0]["target"].get().unwrap();
+/// assert_eq!(target_value, 42.0);
+/// ```
+
+impl<'a> Index<&'a str> for JsonValue {
+ type Output = JsonValue;
+
+ fn index(&self, key: &'a str) -> &Self::Output {
+ let obj = match self {
+ JsonValue::Object(o) => o,
+ _ => panic!(
+ "Attempted to access to an object with key '{}' but actually it was {:?}",
+ key, self
+ ),
+ };
+
+ match obj.get(key) {
+ Some(json) => json,
+ None => panic!("Key '{}' was not found in {:?}", key, self),
+ }
+ }
+}
+
+/// Access to value of the index of array.
+///
+/// ```
+/// use tinyjson::JsonValue;
+///
+/// let v = JsonValue::from(vec![1.0.into(), true.into()]);
+/// let b = &v[1];
+/// assert_eq!(b, &JsonValue::Boolean(true));
+/// ```
+///
+/// This will panic when the given `JsonValue` value is not an array
+///
+/// ```should_panic
+/// # use tinyjson::JsonValue;
+/// use std::collections::HashMap;
+/// let v = JsonValue::from(HashMap::new());
+/// let _ = &v[0]; // Panic
+/// ```
+///
+/// or when the index is out of bounds.
+///
+/// ```should_panic
+/// # use tinyjson::JsonValue;
+/// let v = JsonValue::from(vec![]);
+/// let _ = &v[0]; // Panic
+/// ```
+impl Index<usize> for JsonValue {
+ type Output = JsonValue;
+
+ fn index(&self, index: usize) -> &'_ Self::Output {
+ let array = match self {
+ JsonValue::Array(a) => a,
+ _ => panic!(
+ "Attempted to access to an array with index {} but actually the value was {:?}",
+ index, self,
+ ),
+ };
+ &array[index]
+ }
+}
+
+/// Access to value of the key of mutable object.
+///
+/// ```
+/// use tinyjson::JsonValue;
+/// use std::collections::HashMap;
+///
+/// let mut m = HashMap::new();
+/// m.insert("foo".to_string(), 1.0.into());
+/// let mut v = JsonValue::from(m);
+/// v["foo"] = JsonValue::Number(3.14);
+/// assert_eq!(v["foo"], JsonValue::Number(3.14));
+/// ```
+///
+/// This will panic when the given `JsonValue` value is not an object
+///
+/// ```should_panic
+/// # use tinyjson::JsonValue;
+/// let mut v = JsonValue::from(vec![]);
+/// let _ = &mut v["foo"]; // Panic
+/// ```
+///
+/// or when the key does not exist in the object.
+///
+/// ```should_panic
+/// # use tinyjson::JsonValue;
+/// # use std::collections::HashMap;
+/// let mut v = JsonValue::from(HashMap::new());
+/// let _ = &mut v["foo"]; // Panic
+/// ```
+///
+/// Using this operator, you can modify the nested elements quickly
+///
+/// ```
+/// # use tinyjson::JsonValue;
+/// let mut json: JsonValue = r#"
+/// {
+/// "foo": {
+/// "bar": [
+/// { "target": 42 }
+/// ]
+/// }
+/// }
+/// "#.parse().unwrap();
+///
+/// // Modify with index operator
+/// json["foo"]["bar"][0]["target"] = JsonValue::Boolean(false);
+/// assert_eq!(json["foo"]["bar"][0]["target"], JsonValue::Boolean(false));
+/// ```
+impl<'a> IndexMut<&'a str> for JsonValue {
+ fn index_mut(&mut self, key: &'a str) -> &mut Self::Output {
+ let obj = match self {
+ JsonValue::Object(o) => o,
+ _ => panic!(
+ "Attempted to access to an object with key '{}' but actually it was {:?}",
+ key, self
+ ),
+ };
+
+ if let Some(json) = obj.get_mut(key) {
+ json
+ } else {
+ panic!("Key '{}' was not found in object", key)
+ }
+ }
+}
+
+/// Access to value of the index of mutable array.
+///
+/// ```
+/// use tinyjson::JsonValue;
+///
+/// let mut v = JsonValue::from(vec![1.0.into(), true.into()]);
+/// let b = &mut v[1];
+/// assert_eq!(b, &JsonValue::Boolean(true));
+/// ```
+///
+/// This will panic when the given `JsonValue` value is not an array
+///
+/// ```should_panic
+/// # use tinyjson::JsonValue;
+/// use std::collections::HashMap;
+/// let mut v = JsonValue::from(HashMap::new());
+/// let _ = &mut v[0]; // Panic
+/// ```
+///
+/// or when the index is out of bounds.
+///
+/// ```should_panic
+/// # use tinyjson::JsonValue;
+/// let mut v = JsonValue::from(vec![]);
+/// let _ = &mut v[0]; // Panic
+/// ```
+impl IndexMut<usize> for JsonValue {
+ fn index_mut(&mut self, index: usize) -> &mut Self::Output {
+ let array = match self {
+ JsonValue::Array(a) => a,
+ _ => panic!(
+ "Attempted to access to an array with index {} but actually the value was {:?}",
+ index, self,
+ ),
+ };
+
+ &mut array[index]
+ }
+}
+
+macro_rules! impl_from {
+ (
+ $(#[$meta:meta])*
+ $v:ident: $t:ty => $e:expr
+ ) => {
+ $(#[$meta])*
+ impl From<$t> for JsonValue {
+ fn from($v: $t) -> JsonValue {
+ use JsonValue::*;
+ $e
+ }
+ }
+ };
+}
+
+impl_from!(
+ /// Convert `f64` value into `JsonValue`.
+ ///
+ /// ```
+ /// use tinyjson::JsonValue;
+ /// let v = JsonValue::from(1.0);
+ /// assert!(v.is_number());
+ /// ```
+ n: f64 => Number(n)
+);
+impl_from!(
+ /// Convert `bool` value into `JsonValue`.
+ ///
+ /// ```
+ /// use tinyjson::JsonValue;
+ /// let v = JsonValue::from(true);
+ /// assert!(v.is_bool());
+ /// ```
+ b: bool => Boolean(b)
+);
+impl_from!(
+ /// Convert `bool` value into `JsonValue`. Note that `&str` is not available. Explicitly allocate `String` object
+ /// and pass it.
+ ///
+ /// ```
+ /// use tinyjson::JsonValue;
+ /// let v = JsonValue::from("foo".to_string());
+ /// assert!(v.is_string());
+ /// ```
+ s: String => String(s)
+);
+impl_from!(
+ /// Convert `()` into `JsonValue`. `()` is an inner representation of null JSON value.
+ ///
+ /// ```
+ /// use tinyjson::JsonValue;
+ /// let v = JsonValue::from(());
+ /// assert!(v.is_null());
+ /// ```
+ _x: () => Null
+);
+impl_from!(
+ /// Convert `Vec` value into `JsonValue`.
+ ///
+ /// ```
+ /// use tinyjson::JsonValue;
+ /// let v = JsonValue::from(vec![1.0.into(), true.into()]);
+ /// assert!(v.is_array());
+ /// ```
+ a: Vec<JsonValue> => Array(a)
+);
+impl_from!(
+ /// Convert `HashMap` value into `JsonValue`.
+ ///
+ /// ```
+ /// use tinyjson::JsonValue;
+ /// use std::collections::HashMap;
+ /// let mut m = HashMap::new();
+ /// m.insert("foo".to_string(), 1.0.into());
+ /// let v = JsonValue::from(m);
+ /// assert!(v.is_object());
+ /// ```
+ o: HashMap<String, JsonValue> => Object(o)
+);
+
+/// Error caused when trying to convert `JsonValue` into some wrong type value.
+///
+/// ```
+/// use tinyjson::{JsonValue, UnexpectedValue};
+/// use std::convert::TryFrom;
+///
+/// let error = String::try_from(JsonValue::from(1.0)).unwrap_err();
+/// assert!(matches!(error, UnexpectedValue{..}));
+/// ```
+#[derive(Debug)]
+pub struct UnexpectedValue {
+ value: JsonValue,
+ expected: &'static str,
+}
+
+impl UnexpectedValue {
+ /// Get reference to the value which failed to be converted.
+ ///
+ /// ```
+ /// use tinyjson::JsonValue;
+ /// use std::convert::TryFrom;
+ ///
+ /// let error = String::try_from(JsonValue::from(1.0)).unwrap_err();
+ /// assert_eq!(error.value(), &JsonValue::Number(1.0));
+ /// ```
+ pub fn value(&self) -> &JsonValue {
+ &self.value
+ }
+}
+
+impl fmt::Display for UnexpectedValue {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "Unexpected JSON value: {:?}. Expected {} value",
+ self.value, self.expected
+ )
+ }
+}
+
+impl std::error::Error for UnexpectedValue {}
+
+/// Convert this error into the value which failed to be converted.
+///
+/// ```
+/// use tinyjson::JsonValue;
+/// use std::convert::TryFrom;
+///
+/// let error = String::try_from(JsonValue::from(1.0)).unwrap_err();
+/// assert_eq!(JsonValue::from(error), JsonValue::Number(1.0));
+/// ```
+impl From<UnexpectedValue> for JsonValue {
+ fn from(err: UnexpectedValue) -> Self {
+ err.value
+ }
+}
+
+macro_rules! impl_try_from {
+ (
+ $(#[$meta:meta])*
+ $pat:pat => $val:expr,
+ $ty:ty,
+ ) => {
+ $(#[$meta])*
+ impl TryFrom<JsonValue> for $ty {
+ type Error = UnexpectedValue;
+
+ fn try_from(v: JsonValue) -> Result<Self, UnexpectedValue> {
+ match v {
+ $pat => Ok($val),
+ v => Err(UnexpectedValue {
+ value: v,
+ expected: stringify!($ty),
+ }),
+ }
+ }
+ }
+ };
+}
+
+impl_try_from!(
+ /// Try to convert the `JsonValue` value into `f64`. `UnexpectedValue` error happens when trying to convert an
+ /// incorrect type value.
+ ///
+ /// ```
+ /// use tinyjson::JsonValue;
+ /// use std::convert::TryFrom;
+ ///
+ /// let v = JsonValue::from(1.0);
+ /// let r = f64::try_from(v);
+ /// assert!(r.is_ok());
+ ///
+ /// let v = JsonValue::from(true);
+ /// let r = f64::try_from(v);
+ /// assert!(r.is_err());
+ /// ```
+ JsonValue::Number(n) => n,
+ f64,
+);
+impl_try_from!(
+ /// Try to convert the `JsonValue` value into `bool`. `UnexpectedValue` error happens when trying to convert an
+ /// incorrect type value.
+ ///
+ /// ```
+ /// use tinyjson::JsonValue;
+ /// use std::convert::TryFrom;
+ ///
+ /// let v = JsonValue::from(true);
+ /// let r = bool::try_from(v);
+ /// assert!(r.is_ok());
+ ///
+ /// let v = JsonValue::from(1.0);
+ /// let r = bool::try_from(v);
+ /// assert!(r.is_err());
+ /// ```
+ JsonValue::Boolean(b) => b,
+ bool,
+);
+impl_try_from!(
+ /// Try to convert the `JsonValue` value into `String`. `UnexpectedValue` error happens when trying to convert an
+ /// incorrect type value.
+ ///
+ /// ```
+ /// use tinyjson::JsonValue;
+ /// use std::convert::TryFrom;
+ ///
+ /// let v = JsonValue::from("foo".to_string());
+ /// let r = String::try_from(v);
+ /// assert!(r.is_ok());
+ ///
+ /// let v = JsonValue::from(1.0);
+ /// let r = String::try_from(v);
+ /// assert!(r.is_err());
+ /// ```
+ JsonValue::String(s) => s,
+ String,
+);
+impl_try_from!(
+ /// Try to convert the `JsonValue` value into `()`. Note that `()` is an inner representation of null JSON value.
+ /// `UnexpectedValue` error happens when trying to convert an incorrect type value.
+ ///
+ /// ```
+ /// use tinyjson::JsonValue;
+ /// use std::convert::TryFrom;
+ ///
+ /// let v = JsonValue::from(());
+ /// let r = <()>::try_from(v);
+ /// assert!(r.is_ok());
+ ///
+ /// let v = JsonValue::from(1.0);
+ /// let r = <()>::try_from(v);
+ /// assert!(r.is_err());
+ /// ```
+ JsonValue::Null => (),
+ (),
+);
+impl_try_from!(
+ /// Try to convert the `JsonValue` value into `Vec<JsonValue>`. `UnexpectedValue` error happens when trying to
+ /// convert an incorrect type value.
+ ///
+ /// ```
+ /// use tinyjson::JsonValue;
+ /// use std::convert::TryFrom;
+ ///
+ /// let v = JsonValue::from(vec![true.into()]);
+ /// let r = <Vec<_>>::try_from(v);
+ /// assert!(r.is_ok());
+ ///
+ /// let v = JsonValue::from(1.0);
+ /// let r = <Vec<_>>::try_from(v);
+ /// assert!(r.is_err());
+ /// ```
+ JsonValue::Array(a) => a,
+ Vec<JsonValue>,
+);
+impl_try_from!(
+ /// Try to convert the `JsonValue` value into `HashMap<String, JsonValue>`. `UnexpectedValue` error happens when
+ /// trying to convert an incorrect type value.
+ ///
+ /// ```
+ /// use tinyjson::JsonValue;
+ /// use std::convert::TryFrom;
+ /// use std::collections::HashMap;
+ ///
+ /// let mut m = HashMap::new();
+ /// m.insert("foo".to_string(), 42.0.into());
+ /// let v = JsonValue::from(m);
+ /// let r = <HashMap<_, _>>::try_from(v);
+ /// assert!(r.is_ok());
+ ///
+ /// let v = JsonValue::from(1.0);
+ /// let r = <HashMap<_, _>>::try_from(v);
+ /// assert!(r.is_err());
+ /// ```
+ JsonValue::Object(o) => o,
+ HashMap<String, JsonValue>,
+);
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..39ca04d
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,90 @@
+//! [tinyjson](https://crates.io/crates/tinyjson) is a library to parse/generate JSON format document.
+//!
+//! Goals of this library are
+//!
+//! - **Simplicity**: This library uses standard containers like `Vec` or `HashMap` as its internal representation
+//! and exposes it to users. Users can operate JSON values via the standard APIs. And it keeps this crate as small
+//! as possible.
+//! - **Explicit**: This library does not hide memory allocation from users. You need to allocate memory like `Vec`,
+//! `String`, `HashMap` by yourself. It is good for readers of your source code to show where memory allocations
+//! happen. And you can have control of how memory is allocated (e.g. allocating memory in advance with
+//! `with_capacity` method).
+//! - **No dependencies**: This library is built on top of only standard libraries.
+//! - **No unsafe code**: This library is built with Safe Rust.
+//! - **Well tested**: This library is tested with famous test suites:
+//! - [JSON checker in json.org](http://www.json.org/JSON_checker/)
+//! - [JSONTestSuite](https://github.com/nst/JSONTestSuite)
+//! - [JSON-Schema-Test-Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite)
+//!
+//! Example:
+//!
+//! ```
+//! use tinyjson::JsonValue;
+//! use std::collections::HashMap;
+//! use std::convert::TryInto;
+//!
+//! let s = r#"
+//! {
+//! "bool": true,
+//! "arr": [1, null, "test"],
+//! "nested": {
+//! "blah": false,
+//! "blahblah": 3.14
+//! },
+//! "unicode": "\u2764"
+//! }
+//! "#;
+//!
+//! // Parse from strings
+//! let parsed: JsonValue = s.parse().unwrap();
+//!
+//! // Access to inner value represented with standard containers
+//! let object: &HashMap<_, _> = parsed.get().unwrap();
+//! println!("Parsed HashMap: {:?}", object);
+//!
+//! // Generate JSON string
+//! println!("{}", parsed.stringify().unwrap());
+//! // Generate formatted JSON string with indent
+//! println!("{}", parsed.format().unwrap());
+//!
+//! // Convert to inner value represented with standard containers
+//! let object: HashMap<_, _> = parsed.try_into().unwrap();
+//! println!("Converted into HashMap: {:?}", object);
+//!
+//! // Create JSON values from standard containers
+//! let mut m = HashMap::new();
+//! m.insert("foo".to_string(), true.into());
+//! let mut v = JsonValue::from(m);
+//!
+//! // Access with `Index` and `IndexMut` operators quickly
+//! println!("{:?}", v["foo"]);
+//! v["foo"] = JsonValue::from("hello".to_string());
+//! println!("{:?}", v["foo"]);
+//! ```
+//!
+//! Any JSON value is represented with [`JsonValue`] enum.
+//!
+//! Each JSON types are mapped to Rust types as follows:
+//!
+//! | JSON | Rust |
+//! |---------|------------------------------|
+//! | Number | `f64` |
+//! | Boolean | `bool` |
+//! | String | `String` |
+//! | Null | `()` |
+//! | Array | `Vec<JsonValue>` |
+//! | Object | `HashMap<String, JsonValue>` |
+
+// This library is built with Safe Rust
+#![forbid(unsafe_code)]
+// Suppress warning which prefers `matches!` macro to `match` statement since the macro was
+// introduced in recent Rust 1.42. This library should support older Rust.
+#![allow(clippy::match_like_matches_macro)]
+
+mod generator;
+mod json_value;
+mod parser;
+
+pub use generator::*;
+pub use json_value::{InnerAsRef, InnerAsRefMut, JsonValue, UnexpectedValue};
+pub use parser::*;
diff --git a/src/parser.rs b/src/parser.rs
new file mode 100644
index 0000000..9eac64e
--- /dev/null
+++ b/src/parser.rs
@@ -0,0 +1,470 @@
+use std::char;
+use std::collections::HashMap;
+use std::fmt;
+use std::iter::Peekable;
+use std::str::FromStr;
+
+use crate::JsonValue;
+
+/// Parse error.
+///
+/// ```
+/// use tinyjson::{JsonParser, JsonParseError};
+/// let error = JsonParser::new("[1, 2, 3".chars()).parse().unwrap_err();
+/// assert!(matches!(error, JsonParseError{..}));
+/// ```
+#[derive(Debug)]
+pub struct JsonParseError {
+ msg: String,
+ line: usize,
+ col: usize,
+}
+
+impl JsonParseError {
+ fn new(msg: String, line: usize, col: usize) -> JsonParseError {
+ JsonParseError { msg, line, col }
+ }
+
+ /// Get the line numbr where the parse error happened. This value is 1-based.
+ ///
+ /// ```
+ /// use tinyjson::{JsonParser, JsonParseError};
+ /// let error = JsonParser::new("[1, 2, 3".chars()).parse().unwrap_err();
+ /// assert_eq!(error.line(), 1);
+ /// ```
+ pub fn line(&self) -> usize {
+ self.line
+ }
+
+ /// Get the column numbr where the parse error happened. This value is 1-based.
+ ///
+ /// ```
+ /// use tinyjson::{JsonParser, JsonParseError};
+ /// let error = JsonParser::new("[1, 2, 3".chars()).parse().unwrap_err();
+ /// assert_eq!(error.column(), 8);
+ /// ```
+ pub fn column(&self) -> usize {
+ self.col
+ }
+}
+
+impl fmt::Display for JsonParseError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "Parse error at line:{}, col:{}: {}",
+ self.line, self.col, &self.msg,
+ )
+ }
+}
+
+impl std::error::Error for JsonParseError {}
+
+/// Convenient type alias for parse results.
+pub type JsonParseResult = Result<JsonValue, JsonParseError>;
+
+// Note: char::is_ascii_whitespace is not available because some characters are not defined as
+// whitespace character in JSON spec. For example, U+000C FORM FEED is whitespace in Rust but
+// it isn't in JSON.
+fn is_whitespace(c: char) -> bool {
+ match c {
+ '\u{0020}' | '\u{000a}' | '\u{000d}' | '\u{0009}' => true,
+ _ => false,
+ }
+}
+
+/// JSON parser to parse UTF-8 string into `JsonValue` value.
+///
+/// Basically you don't need to use this struct directly thanks to `FromStr` trait implementation.
+///
+/// ```
+/// use tinyjson::{JsonParser, JsonValue};
+///
+/// let mut parser = JsonParser::new("[1, 2, 3]".chars());
+/// let array = parser.parse().unwrap();
+///
+/// // Equivalent to the above code using `FromStr`
+/// let array: JsonValue = "[1, 2, 3]".parse().unwrap();
+/// ```
+pub struct JsonParser<I>
+where
+ I: Iterator<Item = char>,
+{
+ chars: Peekable<I>,
+ line: usize,
+ col: usize,
+}
+
+impl<I: Iterator<Item = char>> JsonParser<I> {
+ /// Create a new parser instance from an iterator which iterates characters. The iterator is usually built from
+ /// `str::chars` for parsing `str` or `String` values.
+ pub fn new(it: I) -> Self {
+ JsonParser {
+ chars: it.peekable(),
+ line: 1,
+ col: 0,
+ }
+ }
+
+ fn err<T>(&self, msg: String) -> Result<T, JsonParseError> {
+ Err(JsonParseError::new(msg, self.line, self.col))
+ }
+
+ fn unexpected_eof(&self) -> Result<char, JsonParseError> {
+ Err(JsonParseError::new(
+ String::from("Unexpected EOF"),
+ self.line,
+ self.col,
+ ))
+ }
+
+ fn next_pos(&mut self, c: char) {
+ if c == '\n' {
+ self.col = 0;
+ self.line += 1;
+ } else {
+ self.col += 1;
+ }
+ }
+
+ fn peek(&mut self) -> Result<char, JsonParseError> {
+ while let Some(c) = self.chars.peek().copied() {
+ if !is_whitespace(c) {
+ return Ok(c);
+ }
+ self.next_pos(c);
+ self.chars.next().unwrap();
+ }
+ self.unexpected_eof()
+ }
+
+ fn next(&mut self) -> Option<char> {
+ while let Some(c) = self.chars.next() {
+ self.next_pos(c);
+ if !is_whitespace(c) {
+ return Some(c);
+ }
+ }
+ None
+ }
+
+ fn consume(&mut self) -> Result<char, JsonParseError> {
+ if let Some(c) = self.next() {
+ Ok(c)
+ } else {
+ self.unexpected_eof()
+ }
+ }
+
+ fn consume_no_skip(&mut self) -> Result<char, JsonParseError> {
+ if let Some(c) = self.chars.next() {
+ self.next_pos(c);
+ Ok(c)
+ } else {
+ self.unexpected_eof()
+ }
+ }
+
+ fn parse_object(&mut self) -> JsonParseResult {
+ if self.consume()? != '{' {
+ return self.err(String::from("Object must starts with '{'"));
+ }
+
+ if self.peek()? == '}' {
+ self.consume().unwrap();
+ return Ok(JsonValue::Object(HashMap::new()));
+ }
+
+ let mut m = HashMap::new();
+ loop {
+ let key = match self.parse_any()? {
+ JsonValue::String(s) => s,
+ v => return self.err(format!("Key of object must be string but found {:?}", v)),
+ };
+
+ let c = self.consume()?;
+ if c != ':' {
+ return self.err(format!(
+ "':' is expected after key of object but actually found '{}'",
+ c
+ ));
+ }
+
+ m.insert(key, self.parse_any()?);
+
+ match self.consume()? {
+ ',' => {}
+ '}' => return Ok(JsonValue::Object(m)),
+ c => {
+ return self.err(format!(
+ "',' or '}}' is expected for object but actually found '{}'",
+ c.escape_debug(),
+ ))
+ }
+ }
+ }
+ }
+
+ fn parse_array(&mut self) -> JsonParseResult {
+ if self.consume()? != '[' {
+ return self.err(String::from("Array must starts with '['"));
+ }
+
+ if self.peek()? == ']' {
+ self.consume().unwrap();
+ return Ok(JsonValue::Array(vec![]));
+ }
+
+ let mut v = vec![self.parse_any()?];
+ loop {
+ match self.consume()? {
+ ',' => {}
+ ']' => return Ok(JsonValue::Array(v)),
+ c => {
+ return self.err(format!(
+ "',' or ']' is expected for array but actually found '{}'",
+ c
+ ))
+ }
+ }
+
+ v.push(self.parse_any()?); // Next element
+ }
+ }
+
+ fn push_utf16(&self, s: &mut String, utf16: &mut Vec<u16>) -> Result<(), JsonParseError> {
+ if utf16.is_empty() {
+ return Ok(());
+ }
+
+ match String::from_utf16(utf16) {
+ Ok(utf8) => s.push_str(&utf8),
+ Err(err) => return self.err(format!("Invalid UTF-16 sequence {:?}: {}", &utf16, err)),
+ }
+ utf16.clear();
+ Ok(())
+ }
+
+ fn parse_string(&mut self) -> JsonParseResult {
+ if self.consume()? != '"' {
+ return self.err(String::from("String must starts with double quote"));
+ }
+
+ let mut utf16 = Vec::new(); // Buffer for parsing \uXXXX UTF-16 characters
+ let mut s = String::new();
+ loop {
+ let c = match self.consume_no_skip()? {
+ '\\' => match self.consume_no_skip()? {
+ '\\' => '\\',
+ '/' => '/',
+ '"' => '"',
+ 'b' => '\u{0008}',
+ 'f' => '\u{000c}',
+ 'n' => '\n',
+ 'r' => '\r',
+ 't' => '\t',
+ 'u' => {
+ let mut u = 0u16;
+ for _ in 0..4 {
+ let c = self.consume()?;
+ if let Some(h) = c.to_digit(16) {
+ u = u * 0x10 + h as u16;
+ } else {
+ return self.err(format!("Unicode character must be \\uXXXX (X is hex character) format but found character '{}'", c));
+ }
+ }
+ utf16.push(u);
+ // Additional \uXXXX character may follow. UTF-16 characters must be converted
+ // into UTF-8 string as sequence because surrogate pairs must be considered
+ // like "\uDBFF\uDFFF".
+ continue;
+ }
+ c => return self.err(format!("'\\{}' is invalid escaped character", c)),
+ },
+ '"' => {
+ self.push_utf16(&mut s, &mut utf16)?;
+ return Ok(JsonValue::String(s));
+ }
+ // Note: c.is_control() is not available here because JSON accepts 0x7f (DEL) in
+ // string literals but 0x7f is control character.
+ // Rough spec of JSON says string literal cannot contain control characters. But it
+ // can actually contain 0x7f.
+ c if (c as u32) < 0x20 => {
+ return self.err(format!(
+ "String cannot contain control character {}",
+ c.escape_debug(),
+ ));
+ }
+ c => c,
+ };
+
+ self.push_utf16(&mut s, &mut utf16)?;
+
+ s.push(c);
+ }
+ }
+
+ fn parse_constant(&mut self, s: &'static str) -> Option<JsonParseError> {
+ for c in s.chars() {
+ match self.consume_no_skip() {
+ Ok(x) if x != c => {
+ return Some(JsonParseError::new(
+ format!("Unexpected character '{}' while parsing '{}'", c, s),
+ self.line,
+ self.col,
+ ));
+ }
+ Ok(_) => {}
+ Err(e) => return Some(e),
+ }
+ }
+ None
+ }
+
+ fn parse_null(&mut self) -> JsonParseResult {
+ match self.parse_constant("null") {
+ Some(err) => Err(err),
+ None => Ok(JsonValue::Null),
+ }
+ }
+
+ fn parse_true(&mut self) -> JsonParseResult {
+ match self.parse_constant("true") {
+ Some(err) => Err(err),
+ None => Ok(JsonValue::Boolean(true)),
+ }
+ }
+
+ fn parse_false(&mut self) -> JsonParseResult {
+ match self.parse_constant("false") {
+ Some(err) => Err(err),
+ None => Ok(JsonValue::Boolean(false)),
+ }
+ }
+
+ fn parse_number(&mut self) -> JsonParseResult {
+ let neg = if self.peek()? == '-' {
+ self.consume_no_skip().unwrap();
+ true
+ } else {
+ false
+ };
+
+ let mut s = String::new();
+ let mut saw_dot = false;
+ let mut saw_exp = false;
+
+ while let Some(d) = self.chars.peek() {
+ match d {
+ '0'..='9' => s.push(*d),
+ '.' => {
+ saw_dot = true;
+ break;
+ }
+ 'e' | 'E' => {
+ saw_exp = true;
+ break;
+ }
+ _ => break,
+ }
+ self.consume_no_skip().unwrap();
+ }
+
+ if s.is_empty() {
+ return self.err("Integer part must not be empty in number literal".to_string());
+ }
+
+ if s.starts_with('0') && s.len() > 1 {
+ return self
+ .err("Integer part of number must not start with 0 except for '0'".to_string());
+ }
+
+ if saw_dot {
+ s.push(self.consume_no_skip().unwrap()); // eat '.'
+ while let Some(d) = self.chars.peek() {
+ match d {
+ '0'..='9' => s.push(*d),
+ 'e' | 'E' => {
+ saw_exp = true;
+ break;
+ }
+ _ => break,
+ }
+ self.consume_no_skip().unwrap();
+ }
+ if s.ends_with('.') {
+ return self.err("Fraction part of number must not be empty".to_string());
+ }
+ }
+
+ if saw_exp {
+ s.push(self.consume_no_skip().unwrap()); // eat 'e' or 'E'
+ if let Some('+') | Some('-') = self.chars.peek() {
+ s.push(self.consume_no_skip().unwrap());
+ }
+
+ let mut saw_digit = false;
+ while let Some(d) = self.chars.peek() {
+ match d {
+ '0'..='9' => s.push(*d),
+ _ => break,
+ }
+ saw_digit = true;
+ self.consume_no_skip().unwrap();
+ }
+
+ if !saw_digit {
+ return self.err("Exponent part must not be empty in number literal".to_string());
+ }
+ }
+
+ match s.parse::<f64>() {
+ Ok(n) => Ok(JsonValue::Number(if neg { -n } else { n })),
+ Err(err) => self.err(format!("Invalid number literal '{}': {}", s, err)),
+ }
+ }
+
+ fn parse_any(&mut self) -> JsonParseResult {
+ match self.peek()? {
+ '0'..='9' | '-' => self.parse_number(),
+ '"' => self.parse_string(),
+ '[' => self.parse_array(),
+ '{' => self.parse_object(),
+ 't' => self.parse_true(),
+ 'f' => self.parse_false(),
+ 'n' => self.parse_null(),
+ c => self.err(format!("Invalid character: {}", c.escape_debug())),
+ }
+ }
+
+ /// Run the parser to parse one JSON value.
+ pub fn parse(&mut self) -> JsonParseResult {
+ let v = self.parse_any()?;
+
+ if let Some(c) = self.next() {
+ return self.err(format!(
+ "Expected EOF but got character '{}'",
+ c.escape_debug(),
+ ));
+ }
+
+ Ok(v)
+ }
+}
+
+/// Parse given `str` object into `JsonValue` value. This is recommended way to parse strings into JSON value with
+/// this library.
+///
+/// ```
+/// use tinyjson::JsonValue;
+///
+/// let array: JsonValue = "[1, 2, 3]".parse().unwrap();
+/// assert!(array.is_array());
+/// ```
+impl FromStr for JsonValue {
+ type Err = JsonParseError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ JsonParser::new(s.chars()).parse()
+ }
+}
diff --git a/tests/assets/JSON-Schema-Test-Suite/README.md b/tests/assets/JSON-Schema-Test-Suite/README.md
new file mode 100644
index 0000000..4674bdc
--- /dev/null
+++ b/tests/assets/JSON-Schema-Test-Suite/README.md
@@ -0,0 +1,3 @@
+Imported from https://github.com/json-schema-org/JSON-Schema-Test-Suite/tree/846dd7abddd2347803372cd7212e5fb0cc74379e
+
+See [LICENSE](./LICENSE) for files in this directory.
diff --git a/tests/assets/JSONTestSuite/README.md b/tests/assets/JSONTestSuite/README.md
new file mode 100644
index 0000000..db92c38
--- /dev/null
+++ b/tests/assets/JSONTestSuite/README.md
@@ -0,0 +1,3 @@
+Imported from https://github.com/nst/JSONTestSuite/tree/9f23c68b521dd700e8c99151d6dc1c5c52a0246e
+
+See [LICENSE](./LICENSE) for license for files in this directory.
diff --git a/tests/assets/jsonorg/README.md b/tests/assets/jsonorg/README.md
new file mode 100644
index 0000000..dace048
--- /dev/null
+++ b/tests/assets/jsonorg/README.md
@@ -0,0 +1,5 @@
+These assets are imported from json.org.
+
+http://www.json.org/JSON_checker/
+
+