├── .gitignore ├── tests └── it │ ├── json_org_validator_tests │ ├── fail16.json │ ├── fail29.json │ ├── fail30.json │ ├── fail31.json │ ├── fail33.json │ ├── fail2.json │ ├── fail24.json │ ├── fail27.json │ ├── fail4.json │ ├── fail8.json │ ├── fail19.json │ ├── fail20.json │ ├── fail23.json │ ├── fail28.json │ ├── fail5.json │ ├── fail9.json │ ├── fail6.json │ ├── fail7.json │ ├── fail11.json │ ├── fail12.json │ ├── fail14.json │ ├── fail21.json │ ├── fail22.json │ ├── fail25.json │ ├── fail15.json │ ├── fail17.json │ ├── fail26.json │ ├── fail3.json │ ├── fail13.json │ ├── fail32.json │ ├── fail18.json │ ├── pass2.json │ ├── fail10.json │ ├── fail1.json │ ├── pass3.json │ └── pass1.json │ ├── json_integration.rs │ └── main.rs ├── src ├── lib.rs ├── null.rs ├── boolean.rs ├── array.rs ├── error.rs ├── object.rs ├── parser.rs ├── number.rs └── string.rs ├── .github └── workflows │ ├── test.yml │ └── coverage.yml ├── Cargo.toml ├── write_bench_files.py ├── benches ├── parse.rs └── serde_mapping.rs ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | data -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail16.json: -------------------------------------------------------------------------------- 1 | [\naked] -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail29.json: -------------------------------------------------------------------------------- 1 | [0e] -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail30.json: -------------------------------------------------------------------------------- 1 | [0e+] -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail31.json: -------------------------------------------------------------------------------- 1 | [0e+-1] -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail33.json: -------------------------------------------------------------------------------- 1 | ["mismatch"} -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail2.json: -------------------------------------------------------------------------------- 1 | ["Unclosed array" -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail24.json: -------------------------------------------------------------------------------- 1 | ['single quote'] -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail27.json: -------------------------------------------------------------------------------- 1 | ["line 2 | break"] -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail4.json: -------------------------------------------------------------------------------- 1 | ["extra comma",] -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail8.json: -------------------------------------------------------------------------------- 1 | ["Extra close"]] -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail19.json: -------------------------------------------------------------------------------- 1 | {"Missing colon" null} -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail20.json: -------------------------------------------------------------------------------- 1 | {"Double colon":: null} -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail23.json: -------------------------------------------------------------------------------- 1 | ["Bad value", truth] -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail28.json: -------------------------------------------------------------------------------- 1 | ["line\ 2 | break"] -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail5.json: -------------------------------------------------------------------------------- 1 | ["double extra comma",,] -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail9.json: -------------------------------------------------------------------------------- 1 | {"Extra comma": true,} -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail6.json: -------------------------------------------------------------------------------- 1 | [ , "<-- missing value"] -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail7.json: -------------------------------------------------------------------------------- 1 | ["Comma after the close"], -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail11.json: -------------------------------------------------------------------------------- 1 | {"Illegal expression": 1 + 2} -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail12.json: -------------------------------------------------------------------------------- 1 | {"Illegal invocation": alert()} -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail14.json: -------------------------------------------------------------------------------- 1 | {"Numbers cannot be hex": 0x14} -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail21.json: -------------------------------------------------------------------------------- 1 | {"Comma instead of colon", null} -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail22.json: -------------------------------------------------------------------------------- 1 | ["Colon instead of comma": false] -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail25.json: -------------------------------------------------------------------------------- 1 | [" tab character in string "] -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail15.json: -------------------------------------------------------------------------------- 1 | ["Illegal backslash escape: \x15"] -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail17.json: -------------------------------------------------------------------------------- 1 | ["Illegal backslash escape: \017"] -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail26.json: -------------------------------------------------------------------------------- 1 | ["tab\ character\ in\ string\ "] -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail3.json: -------------------------------------------------------------------------------- 1 | {unquoted_key: "keys must be quoted"} -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail13.json: -------------------------------------------------------------------------------- 1 | {"Numbers cannot have leading zeroes": 013} -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail32.json: -------------------------------------------------------------------------------- 1 | {"Comma instead if closing brace": true, -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail18.json: -------------------------------------------------------------------------------- 1 | [[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]] -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/pass2.json: -------------------------------------------------------------------------------- 1 | [[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]] -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail10.json: -------------------------------------------------------------------------------- 1 | {"Extra value after close": true} "misplaced quoted value" -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/fail1.json: -------------------------------------------------------------------------------- 1 | "A JSON payload should be an object or array, not a string." -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/pass3.json: -------------------------------------------------------------------------------- 1 | { 2 | "JSON Test Pattern pass3": { 3 | "The outermost value": "must be an object or array.", 4 | "In this test": "It is an object." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Library to parse JSON 2 | #![deny(missing_docs)] 3 | #![no_std] 4 | #![forbid(unsafe_code)] 5 | #[macro_use] 6 | extern crate alloc; 7 | 8 | mod array; 9 | mod boolean; 10 | mod error; 11 | mod null; 12 | mod number; 13 | mod object; 14 | mod parser; 15 | mod string; 16 | 17 | pub use error::*; 18 | pub use parser::{parse, Number, Object, Value}; 19 | -------------------------------------------------------------------------------- /src/null.rs: -------------------------------------------------------------------------------- 1 | use super::error::*; 2 | 3 | #[inline] 4 | pub fn parse_null(values: &mut &[u8]) -> Result<(), Error> { 5 | let data: [u8; 4] = values 6 | .get(..4) 7 | .ok_or(Error::InvalidEOF)? 8 | .try_into() 9 | .unwrap(); 10 | *values = &values[4..]; 11 | if data != [b'n', b'u', b'l', b'l'] { 12 | return Err(Error::InvalidNullToken(data)); 13 | }; 14 | Ok(()) 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | name: Test 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: Swatinem/rust-cache@v1 12 | - uses: actions-rs/toolchain@v1 13 | with: 14 | toolchain: stable 15 | - name: test 16 | run: cargo test 17 | 18 | test-preserve-order: 19 | name: Test preserve order 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v2 23 | - uses: Swatinem/rust-cache@v1 24 | - uses: actions-rs/toolchain@v1 25 | with: 26 | toolchain: stable 27 | - name: test 28 | run: cargo test --features preserve_order 29 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Coverage 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | coverage: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - name: Install Rust 11 | run: rustup toolchain install stable --component llvm-tools-preview 12 | - name: Install cargo-llvm-cov 13 | uses: taiki-e/install-action@cargo-llvm-cov 14 | - uses: Swatinem/rust-cache@v1 15 | - name: Generate code coverage 16 | run: cargo llvm-cov --lcov --output-path lcov.info 17 | - name: Upload coverage to Codecov 18 | uses: codecov/codecov-action@v1 19 | with: 20 | token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos 21 | files: lcov.info 22 | fail_ci_if_error: true 23 | -------------------------------------------------------------------------------- /src/boolean.rs: -------------------------------------------------------------------------------- 1 | use super::error::*; 2 | 3 | #[inline] 4 | pub fn parse_true(values: &mut &[u8]) -> Result<(), Error> { 5 | let data: [u8; 4] = values 6 | .get(..4) 7 | .ok_or(Error::InvalidEOF)? 8 | .try_into() 9 | .unwrap(); 10 | *values = &values[4..]; 11 | if data != [b't', b'r', b'u', b'e'] { 12 | return Err(Error::InvalidTrueToken(data)); 13 | }; 14 | Ok(()) 15 | } 16 | 17 | #[inline] 18 | pub fn parse_false(values: &mut &[u8]) -> Result<(), Error> { 19 | let data: [u8; 5] = values 20 | .get(..5) 21 | .ok_or(Error::InvalidEOF)? 22 | .try_into() 23 | .unwrap(); 24 | *values = &values[5..]; 25 | if data != [b'f', b'a', b'l', b's', b'e'] { 26 | return Err(Error::InvalidFalseToken(data)); 27 | }; 28 | Ok(()) 29 | } 30 | -------------------------------------------------------------------------------- /src/array.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | 3 | use crate::{ 4 | parser::{current_token, parse_value, skip_unused}, 5 | Value, 6 | }; 7 | 8 | use super::error::*; 9 | 10 | pub fn parse_array<'b, 'a>(values: &'b mut &'a [u8]) -> Result>, Error> { 11 | *values = &values[1..]; 12 | let mut items = vec![]; 13 | loop { 14 | skip_unused(values); 15 | let token = current_token(values)?; 16 | if token == b']' { 17 | *values = &values[1..]; 18 | break; 19 | }; 20 | if !items.is_empty() { 21 | if token != b',' { 22 | return Err(Error::MissingComma(token)); 23 | } else { 24 | *values = &values[1..]; // consume "," 25 | } 26 | } 27 | 28 | items.push(parse_value(values)?); 29 | } 30 | Ok(items) 31 | } 32 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "json-deserializer" 3 | version = "0.4.4" 4 | license = "Apache-2.0" 5 | description = "Performant library to deserialize JSON" 6 | homepage = "https://github.com/jorgecarleitao/json-deserializer" 7 | repository = "https://github.com/jorgecarleitao/json-deserializer" 8 | authors = ["Jorge C. Leitao "] 9 | keywords = [ "json" ] 10 | edition = "2021" 11 | 12 | [dependencies] 13 | indexmap = { version = "1.5.2", optional = true, default_features = false } 14 | 15 | [dev-dependencies] 16 | proptest = "1" 17 | serde = {version="1", features = ["derive"], default-features = false} 18 | serde_json = "1" 19 | simd-json = {version = "0.4", features = ["allow-non-simd"]} 20 | 21 | criterion = "0.3" 22 | 23 | [features] 24 | default = [] 25 | preserve_order = ["indexmap"] 26 | 27 | [[bench]] 28 | name = "parse" 29 | harness = false 30 | -------------------------------------------------------------------------------- /write_bench_files.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import json 3 | import random 4 | import string 5 | 6 | 7 | Path("./data").mkdir(exist_ok=True) 8 | for log2_size in range(10,22, 2): 9 | size = 2**log2_size 10 | data = ["this is something"] * size 11 | with open(f"data/string_{log2_size}.json", "w") as f: 12 | json.dump(data, f) 13 | 14 | data = ["this is something \u20AC \""] * size 15 | with open(f"data/string_escaped_chars_{log2_size}.json", "w") as f: 16 | json.dump(data, f) 17 | 18 | data = list(range(0, size)) 19 | with open(f"data/integer_{log2_size}.json", "w") as f: 20 | json.dump(data, f) 21 | 22 | data = [random.uniform(0, 1) for _ in range(0, size)] 23 | with open(f"data/float_{log2_size}.json", "w") as f: 24 | json.dump(data, f) 25 | 26 | data = [False, True] * (size // 2) 27 | with open(f"data/bool_{log2_size}.json", "w") as f: 28 | json.dump(data, f) 29 | 30 | # more complex 31 | data = [{"a": ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10))}] * size 32 | with open(f"data/object_string_{log2_size}.json", "w") as f: 33 | json.dump(data, f) 34 | 35 | # more complex 36 | data = [{"a": False}] * size 37 | with open(f"data/object_bool_{log2_size}.json", "w") as f: 38 | json.dump(data, f) 39 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::Display; 2 | 3 | /// List of possible errors 4 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 5 | pub enum Error { 6 | /// todo 7 | NumberWithLeadingZero, 8 | /// todo 9 | NumberWithEmptyFraction, 10 | /// todo 11 | NumberWithEmptyExponent, 12 | /// todo 13 | InvalidUtf8, 14 | /// todo 15 | StringWithControlCharacters, 16 | /// todo 17 | InvalidEscaped(u8), 18 | /// todo 19 | InvalidHex(u8), 20 | /// Invalid surrogate 21 | InvalidLoneLeadingSurrogateInHexEscape(u16), 22 | /// Invalid surrogate pair 23 | InvalidSurrogateInHexEscape(u16), 24 | /// When a surrogate misses the pair 25 | UnexpectedEndOfHexEscape, 26 | /// todo 27 | KeyWithoutDoubleColon, 28 | /// todo 29 | InvalidToken(u8), 30 | /// todo 31 | MissingComma(u8), 32 | /// todo 33 | InvalidStringToken(u8), 34 | /// todo 35 | InvalidNullToken([u8; 4]), 36 | /// When an invalid token is found while trying to parse "false" 37 | InvalidFalseToken([u8; 5]), 38 | /// When an invalid token is found while trying to parse "true" 39 | InvalidTrueToken([u8; 4]), 40 | /// todo 41 | InvalidEOF, 42 | } 43 | 44 | impl Display for Error { 45 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 46 | write!(f, "{:?}", self) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/object.rs: -------------------------------------------------------------------------------- 1 | use alloc::borrow::Cow; 2 | 3 | use crate::{ 4 | parser::{current_token, parse_value, skip_unused}, 5 | string::parse_string, 6 | Object, Value, 7 | }; 8 | 9 | use super::error::*; 10 | 11 | // assumes that `values` contains `{` 12 | pub fn parse_object<'b, 'a>(values: &'b mut &'a [u8]) -> Result, Error> { 13 | *values = &values[1..]; 14 | let mut items = Object::new(); 15 | loop { 16 | skip_unused(values); 17 | let token = current_token(values)?; 18 | if token == b'}' { 19 | *values = &values[1..]; 20 | break; 21 | }; 22 | if !items.is_empty() { 23 | if token != b',' { 24 | return Err(Error::MissingComma(values[0])); 25 | } 26 | *values = &values[1..]; // consume "," 27 | skip_unused(values); 28 | } 29 | 30 | let token = current_token(values)?; 31 | if token != b'"' { 32 | return Err(Error::InvalidStringToken(token)) 33 | } 34 | 35 | let (k, v) = parse_item(values)?; 36 | items.insert(k.into_owned(), v); 37 | } 38 | Ok(items) 39 | } 40 | 41 | #[inline] 42 | fn parse_item<'b, 'a>(values: &'b mut &'a [u8]) -> Result<(Cow<'a, str>, Value<'a>), Error> { 43 | let key = parse_string(values)?; 44 | 45 | skip_unused(values); 46 | let token = current_token(values)?; 47 | if token != b':' { 48 | return Err(Error::InvalidToken(token)); 49 | }; 50 | *values = &values[1..]; 51 | 52 | let value = parse_value(values)?; 53 | Ok((key, value)) 54 | } 55 | -------------------------------------------------------------------------------- /tests/it/json_org_validator_tests/pass1.json: -------------------------------------------------------------------------------- 1 | [ 2 | "JSON Test Pattern pass1", 3 | {"object with 1 member":["array with 1 element"]}, 4 | {}, 5 | [], 6 | -42, 7 | true, 8 | false, 9 | null, 10 | { 11 | "integer": 1234567890, 12 | "real": -9876.543210, 13 | "e": 0.123456789e-12, 14 | "E": 1.234567890E+34, 15 | "": 23456789012E66, 16 | "zero": 0, 17 | "one": 1, 18 | "space": " ", 19 | "quote": "\"", 20 | "backslash": "\\", 21 | "controls": "\b\f\n\r\t", 22 | "slash": "/ & \/", 23 | "alpha": "abcdefghijklmnopqrstuvwyz", 24 | "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", 25 | "digit": "0123456789", 26 | "0123456789": "digit", 27 | "special": "`1~!@#$%^&*()_+-={':[,]}|;.?", 28 | "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A", 29 | "true": true, 30 | "false": false, 31 | "null": null, 32 | "array":[ ], 33 | "object":{ }, 34 | "address": "50 St. James Street", 35 | "url": "http://www.JSON.org/", 36 | "comment": "// /* */": " ", 38 | " s p a c e d " :[1,2 , 3 39 | 40 | , 41 | 42 | 4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7], 43 | "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}", 44 | "quotes": "" \u0022 %22 0x22 034 "", 45 | "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?" 46 | : "A key can be any string" 47 | }, 48 | 0.5 ,98.6 49 | , 50 | 99.44 51 | , 52 | 53 | 1066, 54 | 1e1, 55 | 0.1e1, 56 | 1e-1, 57 | 1e00,2e+00,2e-00 58 | ,"rosebud"] -------------------------------------------------------------------------------- /tests/it/json_integration.rs: -------------------------------------------------------------------------------- 1 | use proptest::prelude::*; 2 | use std::collections::HashMap; 3 | 4 | use crate::parse; 5 | use serde_json::Value; 6 | 7 | fn hash_to_map(values: HashMap) -> serde_json::Map { 8 | let mut v = serde_json::Map::::new(); 9 | for (key, value) in values { 10 | v.insert(key, value); 11 | } 12 | v 13 | } 14 | 15 | fn arb_json(pretty: bool) -> impl Strategy { 16 | let leaf = prop_oneof![ 17 | Just(Value::Null), 18 | any::().prop_map(Value::Bool), 19 | any::().prop_map(Value::from), 20 | any::().prop_map(Value::from), 21 | "\\PC*".prop_map(Value::String), 22 | ]; 23 | leaf.prop_recursive( 24 | 4, // 8 levels deep 25 | 20, // Shoot for maximum size of 256 nodes 26 | 10, // We put up to 10 items per collection 27 | |inner| { 28 | prop_oneof![ 29 | // Take the inner strategy and make the two recursive cases. 30 | prop::collection::vec(inner.clone(), 0..10).prop_map(Value::Array), 31 | prop::collection::hash_map("\\PC*", inner, 0..10) 32 | .prop_map(hash_to_map) 33 | .prop_map(Value::Object), 34 | ] 35 | }, 36 | ) 37 | .prop_map(move |x| { 38 | let s = Value::Array(vec![x]); 39 | if pretty { 40 | serde_json::to_string_pretty(&s).unwrap() 41 | } else { 42 | serde_json::to_string(&s).unwrap() 43 | } 44 | }) 45 | } 46 | 47 | proptest! { 48 | #[test] 49 | fn read_valid_json_doesnt_crash(a in arb_json(false)) { 50 | assert!(parse(a.as_bytes()).is_ok()); 51 | } 52 | 53 | #[test] 54 | fn read_pretty_json_doesnt_crash(a in arb_json(true)) { 55 | assert!(parse(a.as_bytes()).is_ok()); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /benches/parse.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | 3 | use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; 4 | 5 | mod serde_mapping; 6 | 7 | fn parse_serde_json(data: &[u8]) { 8 | let a: serde_json::Value = serde_json::from_slice(data).unwrap(); 9 | assert!(a.is_array()); 10 | } 11 | 12 | fn parse_serde_json_typed(data: &[u8]) { 13 | let a: serde_mapping::Value = serde_json::from_slice(data).unwrap(); 14 | if let serde_mapping::Value::Array(_) = a { 15 | } else { 16 | panic!() 17 | } 18 | } 19 | 20 | fn parse_simd_json(data: &mut [u8]) { 21 | use simd_json::Value; 22 | let a = simd_json::to_borrowed_value(data).unwrap(); 23 | assert!(a.is_array()); 24 | } 25 | 26 | fn parse_json(data: &[u8]) { 27 | let a = json_deserializer::parse(data).unwrap(); 28 | if let json_deserializer::Value::Array(_) = a { 29 | } else { 30 | panic!() 31 | } 32 | } 33 | 34 | fn read(file: &str, log2_size: usize) -> Vec { 35 | let mut f = std::fs::File::open(format!("data/{}_{}.json", file, log2_size)).unwrap(); 36 | let mut data = vec![]; 37 | f.read_to_end(&mut data).unwrap(); 38 | data 39 | } 40 | 41 | fn add_benchmark(c: &mut Criterion) { 42 | (10..=20usize).step_by(2).for_each(|log2_size| { 43 | for type_ in [ 44 | "integer", 45 | "float", 46 | "string", 47 | "string_escaped_chars", 48 | "bool", 49 | "object_string", 50 | "object_bool", 51 | ] { 52 | let bytes = read(type_, log2_size); 53 | 54 | c.bench_function( 55 | &format!("{} json_deserializer 2^{}", type_, log2_size), 56 | |b| b.iter(|| parse_json(&bytes)), 57 | ); 58 | 59 | c.bench_function(&format!("{} serde_json 2^{}", type_, log2_size), |b| { 60 | b.iter(|| parse_serde_json(&bytes)) 61 | }); 62 | 63 | c.bench_function( 64 | &format!("{} serde_json_custom 2^{}", type_, log2_size), 65 | |b| b.iter(|| parse_serde_json_typed(&bytes)), 66 | ); 67 | 68 | let bytes = bytes.clone(); 69 | c.bench_function(&format!("{} simd_json 2^{}", type_, log2_size), move |b| { 70 | b.iter_batched( 71 | || bytes.clone(), 72 | |mut data| parse_simd_json(&mut data), 73 | BatchSize::SmallInput, 74 | ) 75 | }); 76 | } 77 | }) 78 | } 79 | 80 | criterion_group!(benches, add_benchmark); 81 | criterion_main!(benches); 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![test](https://github.com/jorgecarleitao/json-deserializer/actions/workflows/test.yml/badge.svg)](https://github.com/jorgecarleitao/json-deserializer/actions/workflows/test.yml) 2 | [![codecov](https://codecov.io/gh/jorgecarleitao/json-deserializer/branch/main/graph/badge.svg?token=AgyTF60R3D)](https://codecov.io/gh/jorgecarleitao/json-deserializer) 3 | 4 | # Rust native JSON deserializer 5 | 6 | This repository contains a performant Rust implementation to parse JSON by reference. 7 | 8 | ## Why not `serde-json`? 9 | 10 | `serde-json` is both a JSON parser and data model based on serde's model (`Value`). 11 | `Value` is an owning data structure. 12 | 13 | In many use cases, JSON can be parsed into references. In particular, ownership of strings 14 | is only required when the JSON string contains non-ascii characters. 15 | 16 | There is a performance oportunity if JSON is parsed by reference instead of by value when possible. 17 | 18 | This crate fills this gap. When parsing e.g. a list of strings, this crate 19 | is ~2x faster than `serde-json` (see below). 20 | 21 | ## Safety 22 | 23 | This crate is `#![forbid(unsafe_code)]` and only panics on failed allocations. 24 | 25 | ### Benches 26 | 27 | Run 28 | 29 | ```bash 30 | python3 write_bench_files.py && cargo bench --bench parse 31 | ``` 32 | 33 | for a comparison with `serde_json`. Broadly speaking, this crate is either faster or equally fast. 34 | Some examples: 35 | 36 | ### Array of bools 37 | ``` 38 | bool json_deserializer 2^20 time: [26.022 ms 26.056 ms 26.090 ms] 39 | bool serde_json 2^20 time: [30.419 ms 30.468 ms 30.516 ms] 40 | bool simd_json 2^20 time: [31.440 ms 31.486 ms 31.531 ms] 41 | ``` 42 | 43 | ### Array of strings 44 | ``` 45 | string json_deserializer 2^18 time: [10.106 ms 10.138 ms 10.173 ms] 46 | string serde_json 2^18 time: [23.177 ms 23.209 ms 23.243 ms] 47 | string simd_json 2^18 time: [10.924 ms 10.941 ms 10.959 ms] 48 | 49 | # with `RUSTFLAGS='-C target-cpu=native'` (skilake in this case) 50 | string simd_json 2^18 time: [8.0735 ms 8.0887 ms 8.1046 ms] 51 | ``` 52 | 53 | ### Array of an object with a string 54 | ``` 55 | object_string json_deserializer 2^14 56 | time: [2.7631 ms 2.7681 ms 2.7736 ms] 57 | object_string serde_json 2^14 58 | time: [4.3729 ms 4.3823 ms 4.3922 ms] 59 | object_string simd_json 2^14 60 | time: [2.6313 ms 2.6357 ms 2.6401 ms] 61 | ``` 62 | 63 | ### Array of an object with a bool 64 | 65 | ``` 66 | object_bool json_deserializer 2^10 67 | time: [144.14 us 144.35 us 144.62 us] 68 | object_bool serde_json 2^10 69 | time: [197.12 us 197.62 us 198.31 us] 70 | object_bool simd_json 2^10 71 | time: [160.87 us 161.33 us 161.77 us] 72 | ``` 73 | -------------------------------------------------------------------------------- /src/parser.rs: -------------------------------------------------------------------------------- 1 | use alloc::borrow::Cow; 2 | use alloc::string::String; 3 | use alloc::vec::Vec; 4 | 5 | use super::array::parse_array; 6 | use super::boolean::parse_false; 7 | use super::boolean::parse_true; 8 | use super::error::*; 9 | use super::null::parse_null; 10 | use super::number::parse_number; 11 | use super::object::parse_object; 12 | use super::string::parse_string; 13 | 14 | /// Typedef for the inside of an object. 15 | #[cfg(not(feature = "preserve_order"))] 16 | pub type Object<'a> = alloc::collections::BTreeMap>; 17 | /// Typedef for the inside of an object. 18 | #[cfg(feature = "preserve_order")] 19 | pub type Object<'a> = indexmap::IndexMap>; 20 | 21 | /// Reference to JSON data. 22 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 23 | pub enum Number<'a> { 24 | /// A float (contains exactly 1 period) 25 | Float(&'a [u8], &'a [u8]), 26 | /// An integer (contains exactly 0 periods) 27 | Integer(&'a [u8], &'a [u8]), 28 | } 29 | 30 | /// Reference to JSON data. 31 | #[derive(Debug, Clone, PartialEq, Eq)] 32 | pub enum Value<'a> { 33 | /// A `null` 34 | Null, 35 | /// A string (i.e. something quoted; quotes are not part of this. Data has not been UTF-8 validated) 36 | String(Cow<'a, str>), 37 | /// A number (i.e. something starting with a number with an optional period) 38 | Number(Number<'a>), 39 | /// A bool (i.e. `false` or `true`) 40 | Bool(bool), 41 | /// An object (i.e. items inside curly brackets `{}` separated by colon `:` and comma `,`) 42 | Object(Object<'a>), 43 | /// An array (i.e. items inside squared brackets `[]` separated by comma `,`) 44 | Array(Vec>), 45 | } 46 | 47 | /// Parses JSON-compliant bytes into [`Value`] 48 | /// # Errors 49 | /// If and only if `json` is not valid JSON. 50 | /// # Panics 51 | /// If and only if there is not enough memory to allocate. 52 | pub fn parse(mut json: &[u8]) -> Result { 53 | let res = parse_value(&mut json)?; 54 | skip_unused(&mut json); 55 | if json.is_empty() { 56 | Ok(res) 57 | } else { 58 | Err(Error::InvalidEOF) 59 | } 60 | } 61 | 62 | pub fn parse_value<'b, 'a>(values: &'b mut &'a [u8]) -> Result, Error> { 63 | skip_unused(values); 64 | let token = current_token(values)?; 65 | match token { 66 | b'{' => parse_object(values).map(Value::Object), 67 | b'[' => parse_array(values).map(Value::Array), 68 | b'"' => parse_string(values).map(Value::String), 69 | b'n' => parse_null(values).map(|_| Value::Null), 70 | b't' => parse_true(values).map(|_| Value::Bool(true)), 71 | b'f' => parse_false(values).map(|_| Value::Bool(false)), 72 | b'0'..=b'9' | b'-' => parse_number(values).map(Value::Number), 73 | other => Err(Error::InvalidToken(other)), 74 | } 75 | } 76 | 77 | #[inline] 78 | pub fn skip_unused(values: &mut &[u8]) { 79 | while let [first, rest @ ..] = values { 80 | if !matches!(first, b'\n' | b' ' | b'\r' | b'\t') { 81 | break; 82 | } 83 | *values = rest; 84 | } 85 | } 86 | 87 | #[inline] 88 | pub fn current_token(values: &[u8]) -> Result { 89 | if let Some(t) = values.get(0) { 90 | Ok(*t) 91 | } else { 92 | Err(Error::InvalidEOF) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/number.rs: -------------------------------------------------------------------------------- 1 | use super::error::*; 2 | use super::Number; 3 | 4 | #[inline] 5 | pub fn parse_number<'b, 'a>(values: &'b mut &'a [u8]) -> Result, Error> { 6 | let number = *values; 7 | 8 | let mut is_float = false; 9 | let mut number_end = 0; 10 | let mut length = 0; 11 | 12 | let mut prev_state = State::Start; 13 | let byte = values.get(0).ok_or(Error::InvalidEOF)?; 14 | let mut state = next_state(*byte, prev_state)?; 15 | 16 | loop { 17 | if matches!(state, State::FractionStart | State::ExponentSignedNegative) { 18 | is_float = true 19 | } 20 | 21 | length += 1; 22 | 23 | prev_state = state; 24 | 25 | if matches!( 26 | state, 27 | State::Signed | State::Zero | State::Nonzero | State::FractionStart | State::Fraction 28 | ) { 29 | number_end += 1; 30 | } 31 | 32 | *values = values.get(1..).ok_or(Error::InvalidEOF)?; 33 | 34 | if values.is_empty() { 35 | break; 36 | } 37 | 38 | let byte = values.get(0).ok_or(Error::InvalidEOF)?; 39 | 40 | state = next_state(*byte, state)?; 41 | 42 | if state == State::Finished { 43 | break; 44 | } 45 | } 46 | match prev_state { 47 | State::FractionStart => Err(Error::NumberWithEmptyFraction), 48 | State::ExponentStart | State::ExponentSignedPositive | State::ExponentSignedNegative => { 49 | Err(Error::NumberWithEmptyExponent) 50 | } 51 | _ => { 52 | let number = &number[..length]; 53 | let exponent = if number_end == number.len() { 54 | &[] 55 | } else { 56 | &number[number_end + 1..] 57 | }; 58 | let number = &number[..number_end]; 59 | Ok(if is_float { 60 | Number::Float(number, exponent) 61 | } else { 62 | Number::Integer(number, exponent) 63 | }) 64 | } 65 | } 66 | } 67 | 68 | /// The state of the string lexer 69 | #[derive(Copy, Clone, PartialEq, Eq)] 70 | pub enum State { 71 | Finished, // when it is done 72 | Start, 73 | Signed, 74 | Zero, 75 | Nonzero, 76 | FractionStart, 77 | Fraction, 78 | ExponentStart, 79 | ExponentSignedPositive, 80 | ExponentSignedNegative, 81 | Exponent, 82 | } 83 | 84 | /// The transition state of the lexer 85 | #[inline] 86 | fn next_state(byte: u8, state: State) -> Result { 87 | Ok(match (byte, &state) { 88 | (b'-', State::Start) => State::Signed, 89 | (b'0', State::Start | State::Signed) => State::Zero, 90 | (b'1'..=b'9', State::Start | State::Signed) => State::Nonzero, 91 | 92 | (b'0'..=b'9', State::Zero) => return Err(Error::NumberWithLeadingZero), 93 | 94 | (b'.', State::Zero | State::Nonzero) => State::FractionStart, 95 | (b'e' | b'E', State::FractionStart) => return Err(Error::NumberWithEmptyFraction), 96 | (b'e' | b'E', State::Zero | State::Nonzero | State::Fraction) => State::ExponentStart, 97 | 98 | (b'0'..=b'9', State::Nonzero) => State::Nonzero, 99 | 100 | (b'0'..=b'9', State::FractionStart | State::Fraction) => State::Fraction, 101 | 102 | (b'+', State::ExponentStart) => State::ExponentSignedPositive, 103 | (b'-', State::ExponentStart) => State::ExponentSignedNegative, 104 | ( 105 | b'0'..=b'9', 106 | State::ExponentStart 107 | | State::ExponentSignedPositive 108 | | State::ExponentSignedNegative 109 | | State::Exponent, 110 | ) => State::Exponent, 111 | 112 | (_, _) => State::Finished, 113 | }) 114 | } 115 | -------------------------------------------------------------------------------- /benches/serde_mapping.rs: -------------------------------------------------------------------------------- 1 | extern crate alloc; 2 | 3 | use alloc::borrow::Cow; 4 | use alloc::collections::BTreeMap; 5 | use core::fmt; 6 | use serde::de::{Deserialize, MapAccess, SeqAccess, Visitor}; 7 | 8 | macro_rules! tri { 9 | ($e:expr $(,)?) => { 10 | match $e { 11 | core::result::Result::Ok(val) => val, 12 | core::result::Result::Err(err) => return core::result::Result::Err(err), 13 | } 14 | }; 15 | } 16 | 17 | /// Typedef for the inside of an object. 18 | pub type Object<'a> = BTreeMap>; 19 | 20 | #[derive(Debug, Clone, Copy, PartialEq)] 21 | pub enum ConvertedNumber { 22 | Float(f64), 23 | Int(i64), 24 | UInt(u64), 25 | } 26 | 27 | /// Reference to JSON data. 28 | #[derive(Debug, Clone, PartialEq)] 29 | pub enum Value<'a> { 30 | /// A `null` 31 | Null, 32 | /// A string (i.e. something quoted; quotes are not part of this. Data has not been UTF-8 validated) 33 | String(Cow<'a, str>), 34 | /// A number that's actually been converted (used for test code for benching with typed serde json) 35 | Number(ConvertedNumber), 36 | /// A bool (i.e. `false` or `true`) 37 | Bool(bool), 38 | /// An object (i.e. items inside curly brackets `{}` separated by colon `:` and comma `,`) 39 | Object(Object<'a>), 40 | /// An array (i.e. items inside squared brackets `[]` separated by comma `,`) 41 | Array(Vec>), 42 | } 43 | 44 | impl<'de> Deserialize<'de> for Value<'de> { 45 | #[inline] 46 | fn deserialize(deserializer: D) -> Result, D::Error> 47 | where 48 | D: serde::Deserializer<'de>, 49 | { 50 | struct ValueVisitor; 51 | 52 | impl<'de> Visitor<'de> for ValueVisitor { 53 | type Value = Value<'de>; 54 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 55 | formatter.write_str("any valid JSON value") 56 | } 57 | 58 | #[inline] 59 | fn visit_bool(self, value: bool) -> Result, E> { 60 | Ok(Value::Bool(value)) 61 | } 62 | 63 | #[inline] 64 | fn visit_i64(self, value: i64) -> Result, E> { 65 | Ok(Value::Number(ConvertedNumber::Int(value))) 66 | } 67 | 68 | #[inline] 69 | fn visit_u64(self, value: u64) -> Result, E> { 70 | Ok(Value::Number(ConvertedNumber::UInt(value))) 71 | } 72 | 73 | #[inline] 74 | fn visit_f64(self, value: f64) -> Result, E> { 75 | Ok(Value::Number(ConvertedNumber::Float(value))) 76 | } 77 | 78 | #[inline] 79 | fn visit_borrowed_str(self, v: &'de str) -> Result { 80 | Ok(Value::String(Cow::Borrowed(v))) 81 | } 82 | 83 | #[inline] 84 | fn visit_string(self, value: alloc::string::String) -> Result, E> { 85 | Ok(Value::String(Cow::Owned(value))) 86 | } 87 | 88 | #[inline] 89 | fn visit_str(self, value: &str) -> Result, E> { 90 | Ok(Value::String(Cow::Owned(value.to_owned()))) 91 | } 92 | 93 | #[inline] 94 | fn visit_none(self) -> Result, E> { 95 | Ok(Value::Null) 96 | } 97 | 98 | #[inline] 99 | fn visit_some(self, deserializer: D) -> Result, D::Error> 100 | where 101 | D: serde::Deserializer<'de>, 102 | { 103 | serde::Deserialize::deserialize(deserializer) 104 | } 105 | 106 | #[inline] 107 | fn visit_unit(self) -> Result, E> { 108 | Ok(Value::Null) 109 | } 110 | 111 | #[inline] 112 | fn visit_seq(self, mut visitor: V) -> Result, V::Error> 113 | where 114 | V: SeqAccess<'de>, 115 | { 116 | let mut vec = vec![]; 117 | 118 | while let Some(elem) = tri!(visitor.next_element()) { 119 | vec.push(elem); 120 | } 121 | 122 | Ok(Value::Array(vec)) 123 | } 124 | 125 | #[inline] 126 | fn visit_map(self, mut visitor: V) -> Result, V::Error> 127 | where 128 | V: MapAccess<'de>, 129 | { 130 | let mut values = BTreeMap::new(); 131 | 132 | while let Some((key, value)) = tri!(visitor.next_entry()) { 133 | values.insert(key, value); 134 | } 135 | 136 | Ok(Value::Object(values)) 137 | } 138 | } 139 | 140 | deserializer.deserialize_any(ValueVisitor) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/string.rs: -------------------------------------------------------------------------------- 1 | use alloc::borrow::Cow; 2 | 3 | use alloc::string::String; 4 | 5 | use super::Error; 6 | 7 | #[inline] 8 | fn skip_escape(values: &mut &[u8]) -> Result { 9 | *values = &values[1..]; 10 | let ch = *values.get(0).ok_or(Error::InvalidEOF)?; 11 | if ch == b'u' { 12 | const NUM_UNICODE_CHARS: usize = 4; 13 | if values.len() < NUM_UNICODE_CHARS { 14 | return Err(Error::InvalidEOF); 15 | } else { 16 | *values = &values[NUM_UNICODE_CHARS..]; 17 | } 18 | Ok(NUM_UNICODE_CHARS + 1) 19 | } else { 20 | Ok(1) 21 | } 22 | } 23 | 24 | #[inline] 25 | fn compute_length(values: &mut &[u8]) -> Result<(usize, usize, usize), Error> { 26 | let mut length = 0; 27 | let mut escapes = 0; 28 | let mut controls = 0; 29 | debug_assert!(!values.is_empty(), "Tried to parse string on empty input"); 30 | loop { 31 | *values = &values[1..]; 32 | let ch = *values.get(0).ok_or(Error::InvalidEOF)?; 33 | length += 1; 34 | match ch { 35 | b'\\' => { 36 | escapes += 1; 37 | length += skip_escape(values)?; 38 | }, 39 | b'"' => { 40 | *values = &values[1..]; 41 | return Ok((length, escapes, controls)); 42 | } 43 | _ if ch.is_ascii_control() => { 44 | controls += 1; 45 | }, 46 | _ => {} 47 | } 48 | } 49 | } 50 | 51 | #[inline] 52 | pub fn parse_string<'b, 'a>(values: &'b mut &'a [u8]) -> Result, Error> { 53 | // compute the size of the string value and whether it has escapes 54 | let string = *values; 55 | let (length, escapes, controls) = compute_length(values)?; 56 | 57 | let mut data = &string[1..length]; 58 | if controls > 0 { 59 | Err(Error::StringWithControlCharacters) 60 | } else if escapes > 0 { 61 | let capacity = data.len() - escapes; 62 | let mut container = String::with_capacity(capacity); 63 | 64 | while !data.is_empty() { 65 | let first = data[0]; 66 | if first == b'\\' { 67 | data = &data[1..]; 68 | data = parse_escape(data, &mut container)?; 69 | } else { 70 | container.push(first as char); 71 | data = &data[1..]; 72 | } 73 | } 74 | Ok(Cow::Owned(container)) 75 | } else { 76 | alloc::str::from_utf8(data) 77 | .map(Cow::Borrowed) 78 | .map_err(|_| Error::InvalidUtf8) 79 | } 80 | } 81 | 82 | #[allow(clippy::zero_prefixed_literal)] 83 | static HEX: [u8; 256] = { 84 | const __: u8 = 255; // not a hex digit 85 | [ 86 | // 1 2 3 4 5 6 7 8 9 A B C D E F 87 | __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 0 88 | __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 1 89 | __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2 90 | 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, __, __, __, __, __, __, // 3 91 | __, 10, 11, 12, 13, 14, 15, __, __, __, __, __, __, __, __, __, // 4 92 | __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 5 93 | __, 10, 11, 12, 13, 14, 15, __, __, __, __, __, __, __, __, __, // 6 94 | __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7 95 | __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8 96 | __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9 97 | __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A 98 | __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B 99 | __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C 100 | __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D 101 | __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E 102 | __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F 103 | ] 104 | }; 105 | 106 | #[inline] 107 | fn decode_hex_val(val: u8) -> Option { 108 | let n = HEX[val as usize] as u16; 109 | if n == 255 { 110 | None 111 | } else { 112 | Some(n) 113 | } 114 | } 115 | 116 | /// Parses a JSON escape sequence and appends it into the scratch space. Assumes 117 | /// the previous byte read was a backslash. 118 | fn parse_escape<'a>(mut input: &'a [u8], scratch: &mut String) -> Result<&'a [u8], Error> { 119 | let ch = input[0]; 120 | input = &input[1..]; 121 | match ch { 122 | b'"' => scratch.push('"'), 123 | b'\\' => scratch.push('\\'), 124 | b'/' => scratch.push('/'), 125 | b'b' => scratch.push('\x08'), 126 | b'f' => scratch.push('\x0c'), 127 | b'n' => scratch.push('\n'), 128 | b'r' => scratch.push('\r'), 129 | b't' => scratch.push('\t'), 130 | b'u' => { 131 | let hex = decode_hex_escape(input)?; 132 | input = &input[4..]; 133 | 134 | let c = match hex { 135 | n @ 0xDC00..=0xDFFF => { 136 | return Err(Error::InvalidLoneLeadingSurrogateInHexEscape(n)) 137 | } 138 | 139 | // Non-BMP characters are encoded as a sequence of two hex 140 | // escapes, representing UTF-16 surrogates. If deserializing a 141 | // utf-8 string the surrogates are required to be paired, 142 | // whereas deserializing a byte string accepts lone surrogates. 143 | n1 @ 0xD800..=0xDBFF => { 144 | let byte = input.get(0).ok_or(Error::InvalidEOF)?; 145 | if *byte == b'\\' { 146 | input = &input[1..]; 147 | } else { 148 | return Err(Error::UnexpectedEndOfHexEscape); 149 | } 150 | 151 | let byte = input.get(0).ok_or(Error::InvalidEOF)?; 152 | if *byte == b'u' { 153 | input = &input[1..]; 154 | } else { 155 | return parse_escape(input, scratch); 156 | } 157 | 158 | let n2 = decode_hex_escape(input)?; 159 | input = &input[4..]; 160 | if !(0xDC00..=0xDFFF).contains(&n2) { 161 | return Err(Error::InvalidSurrogateInHexEscape(n2)); 162 | } 163 | 164 | let n = (((n1 - 0xD800) as u32) << 10 | (n2 - 0xDC00) as u32) + 0x1_0000; 165 | char::from_u32(n as u32).unwrap() 166 | } 167 | 168 | // Every u16 outside of the surrogate ranges above is guaranteed 169 | // to be a legal char. 170 | n => char::from_u32(n as u32).unwrap(), 171 | }; 172 | 173 | scratch.push(c); 174 | } 175 | other => return Err(Error::InvalidEscaped(other)), 176 | } 177 | 178 | Ok(input) 179 | } 180 | 181 | fn decode_hex_escape(input: &[u8]) -> Result { 182 | let numbers_u8: [u8; 4] = input[..4].try_into().unwrap(); 183 | let mut n = 0; 184 | for number in numbers_u8 { 185 | let hex = decode_hex_val(number).ok_or(Error::InvalidHex(number))?; 186 | n = (n << 4) + hex; 187 | } 188 | Ok(n) 189 | } 190 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | https://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | Copyright 2020-2022 Jorge C. Leitão 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | https://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /tests/it/main.rs: -------------------------------------------------------------------------------- 1 | mod json_integration; 2 | 3 | use std::borrow::Cow; 4 | 5 | use json_deserializer::{parse, Error, Number, Object, Value}; 6 | 7 | fn string(v: &str) -> Cow { 8 | Cow::Borrowed(v) 9 | } 10 | 11 | #[test] 12 | fn basics() -> Result<(), Error> { 13 | let data: &[u8] = br#"{ 14 | "a": "b", 15 | "b": "c", 16 | "c": 1.1, 17 | "d": null, 18 | "e": false, 19 | "f": true, 20 | "g": ["b", 2, null, true, false, [], {}] 21 | }"#; 22 | 23 | let item = parse(data)?; 24 | 25 | let d = [ 26 | (string("a"), Value::String(string("b"))), 27 | (string("b"), Value::String(string("c"))), 28 | (string("c"), Value::Number(Number::Float(b"1.1", b""))), 29 | (string("d"), Value::Null), 30 | (string("e"), Value::Bool(false)), 31 | (string("f"), Value::Bool(true)), 32 | ( 33 | string("g"), 34 | Value::Array(vec![ 35 | Value::String(string("b")), 36 | Value::Number(Number::Integer(b"2", b"")), 37 | Value::Null, 38 | Value::Bool(true), 39 | Value::Bool(false), 40 | Value::Array(vec![]), 41 | Value::Object(Default::default()), 42 | ]), 43 | ), 44 | ] 45 | .into_iter() 46 | .map(|(key, value)| (key.into_owned(), value)) 47 | .collect::(); 48 | 49 | assert_eq!(item, Value::Object(d)); 50 | Ok(()) 51 | } 52 | 53 | #[test] 54 | fn comma_and_string() -> Result<(), Error> { 55 | let data: &[u8] = br#"[",", "1.2", 1.2]"#; 56 | 57 | let item = parse(data)?; 58 | assert_eq!( 59 | item, 60 | Value::Array(vec![ 61 | Value::String(string(",")), 62 | Value::String(string("1.2")), 63 | Value::Number(Number::Float(b"1.2", b"")) 64 | ]) 65 | ); 66 | Ok(()) 67 | } 68 | 69 | #[test] 70 | fn empty_object() -> Result<(), Error> { 71 | let data: &[u8] = b"[{\"\":null}]"; 72 | 73 | let o = [(string("").into_owned(), Value::Null)] 74 | .into_iter() 75 | .collect::(); 76 | 77 | let item = parse(data)?; 78 | assert_eq!(item, Value::Array(vec![Value::Object(o)])); 79 | Ok(()) 80 | } 81 | 82 | #[test] 83 | fn escaped() -> Result<(), Error> { 84 | let data: &[u8] = br#"["\n"]"#; 85 | 86 | let item = parse(data)?; 87 | assert_eq!( 88 | item, 89 | Value::Array(vec![Value::String(Cow::Owned("\n".into()))]) 90 | ); 91 | Ok(()) 92 | } 93 | 94 | #[test] 95 | fn null() -> Result<(), Error> { 96 | let data: &[u8] = b"[null]"; 97 | 98 | let item = parse(data)?; 99 | assert_eq!(item, Value::Array(vec![Value::Null])); 100 | Ok(()) 101 | } 102 | 103 | #[test] 104 | fn empty_string() -> Result<(), Error> { 105 | let data: &[u8] = b"[\"\"]"; 106 | 107 | let item = parse(data)?; 108 | assert_eq!(item, Value::Array(vec![Value::String(string(""))])); 109 | Ok(()) 110 | } 111 | 112 | #[test] 113 | fn number() -> Result<(), Error> { 114 | let num: &str = "10"; 115 | let array: &str = &format!("[{}]", num); 116 | 117 | assert_eq!( 118 | parse(num.as_bytes())?, 119 | Value::Number(Number::Integer(b"10", b"")) 120 | ); 121 | assert_eq!( 122 | parse(array.as_bytes())?, 123 | Value::Array(vec![Value::Number(Number::Integer(b"10", b""))]) 124 | ); 125 | Ok(()) 126 | } 127 | 128 | #[test] 129 | fn utf8() -> Result<(), Error> { 130 | let data: &str = "[\"Ç\"]"; 131 | 132 | let item = parse(data.as_bytes())?; 133 | assert_eq!(item, Value::Array(vec![Value::String(string("Ç"))])); 134 | Ok(()) 135 | } 136 | 137 | #[test] 138 | fn escaped_1() -> Result<(), Error> { 139 | let data: &str = r#"["\n"]"#; 140 | 141 | let item = parse(data.as_bytes())?; 142 | assert_eq!( 143 | item, 144 | Value::Array(vec![Value::String(Cow::Owned("\n".to_string()))]) 145 | ); 146 | Ok(()) 147 | } 148 | 149 | #[test] 150 | fn escaped_error() -> Result<(), Error> { 151 | let data: &str = r#"["\"]"#; 152 | 153 | assert!(parse(data.as_bytes()).is_err()); 154 | Ok(()) 155 | } 156 | 157 | #[test] 158 | fn codepoint() -> Result<(), Error> { 159 | let data: &str = r#"["\u20AC"]"#; 160 | 161 | let item = parse(data.as_bytes())?; 162 | assert_eq!( 163 | item, 164 | Value::Array(vec![Value::String(Cow::Owned("€".to_string()))]) 165 | ); 166 | Ok(()) 167 | } 168 | 169 | #[test] 170 | fn multiple_escape() -> Result<(), Error> { 171 | let data: &[u8] = b"[\"\\\\A\"]"; 172 | 173 | let item = parse(data)?; 174 | assert_eq!( 175 | item, 176 | Value::Array(vec![Value::String(Cow::Owned("\\A".to_string()))]) 177 | ); 178 | Ok(()) 179 | } 180 | 181 | #[test] 182 | fn number_exponent() -> Result<(), Error> { 183 | let num: &str = "1E10"; 184 | let array: &str = &format!("[{}]", num); 185 | 186 | assert_eq!( 187 | parse(num.as_bytes())?, 188 | Value::Number(Number::Integer(b"1", b"10")) 189 | ); 190 | assert_eq!( 191 | parse(array.as_bytes())?, 192 | Value::Array(vec![Value::Number(Number::Integer(b"1", b"10"))]) 193 | ); 194 | Ok(()) 195 | } 196 | 197 | #[test] 198 | fn number_exponent1() -> Result<(), Error> { 199 | let num: &str = "1e-42"; 200 | let array: &str = &format!("[{}]", num); 201 | 202 | assert_eq!( 203 | parse(num.as_bytes())?, 204 | Value::Number(Number::Float(b"1", b"-42")) 205 | ); 206 | assert_eq!( 207 | parse(array.as_bytes())?, 208 | Value::Array(vec![Value::Number(Number::Float(b"1", b"-42"))]) 209 | ); 210 | Ok(()) 211 | } 212 | 213 | #[test] 214 | fn number_exponent2() -> Result<(), Error> { 215 | let num: &str = "8.310346185542391e275"; 216 | let array: &str = &format!("[{}]", num); 217 | 218 | assert_eq!( 219 | parse(num.as_bytes())?, 220 | Value::Number(Number::Float(b"8.310346185542391", b"275")) 221 | ); 222 | assert_eq!( 223 | parse(array.as_bytes())?, 224 | Value::Array(vec![Value::Number(Number::Float( 225 | b"8.310346185542391", 226 | b"275" 227 | ))]) 228 | ); 229 | Ok(()) 230 | } 231 | 232 | #[test] 233 | fn number_exponent3() -> Result<(), Error> { 234 | let num = "1.1e+10"; 235 | let array: &str = &format!("[{}]", num); 236 | let obj: &str = &format!("{{\"Value\":{}}}", num); 237 | 238 | assert_eq!( 239 | parse(num.as_bytes())?, 240 | Value::Number(Number::Float(b"1.1", b"+10")) 241 | ); 242 | assert_eq!( 243 | parse(array.as_bytes())?, 244 | Value::Array(vec![Value::Number(Number::Float(b"1.1", b"+10"))]) 245 | ); 246 | let mut expected = Object::new(); 247 | expected.insert( 248 | "Value".to_string(), 249 | Value::Number(Number::Float(b"1.1", b"+10")), 250 | ); 251 | 252 | assert_eq!(parse(obj.as_bytes())?, Value::Object(expected)); 253 | Ok(()) 254 | } 255 | 256 | #[test] 257 | fn pretty_1() -> Result<(), Error> { 258 | let data: &[u8] = b"[\n null\n]"; 259 | 260 | let item = parse(data)?; 261 | assert_eq!(item, Value::Array(vec![Value::Null])); 262 | Ok(()) 263 | } 264 | 265 | #[test] 266 | fn edges() { 267 | assert!(parse(br#""#).is_err()); 268 | assert!(parse(br#"""#).is_err()); 269 | assert!(parse(br#""""#).is_ok()); 270 | assert!(parse(br#""\u"#).is_err()); 271 | assert!(parse(br#""\u""#).is_err()); 272 | assert!(parse(br#""\u"""#).is_err()); 273 | assert!(parse(br#""\u1234""#).is_ok()); 274 | 275 | assert!(parse(br#"1"#).is_ok()); 276 | assert!(parse(br#"11"#).is_ok()); 277 | assert!(parse(br#"1.1"#).is_ok()); 278 | assert!(parse(br#"1.1E-6"#).is_ok()); 279 | assert!(parse(br#"1.1e-6"#).is_ok()); 280 | assert!(parse(br#"1.1e+6"#).is_ok()); 281 | 282 | assert!(parse(br#"nula"#).is_err()); 283 | assert!(parse(br#"trua"#).is_err()); 284 | assert!(parse(br#"falsa"#).is_err()); 285 | 286 | assert!(parse(br#"[1.1.1]"#).is_err()); 287 | assert!(parse(br#"[1p]"#).is_err()); 288 | assert!(parse(br#"{"a": 1p}"#).is_err()); 289 | assert!(parse(br#"{"a"a: 1}"#).is_err()); 290 | 291 | assert!(parse(br#""\/""#).is_ok()); 292 | assert!(parse(br#""\f""#).is_ok()); 293 | 294 | // \xc3\x28 is invalid utf8 295 | assert!(parse(&[34, 195, 40, 34]).is_err()); 296 | } 297 | 298 | #[test] 299 | fn json_org_validator_tests() { 300 | // Tests from http://www.json.org/JSON_checker/ 301 | assert!(parse(include_bytes!("json_org_validator_tests/pass1.json")).is_ok()); 302 | assert!(parse(include_bytes!("json_org_validator_tests/pass2.json")).is_ok()); 303 | assert!(parse(include_bytes!("json_org_validator_tests/pass3.json")).is_ok()); 304 | 305 | //assert!(parse(include_bytes!("json_org_validator_tests/fail1.json")).is_err()); // Top-level values is allowed 306 | assert!(parse(include_bytes!("json_org_validator_tests/fail2.json")).is_err()); 307 | assert!(parse(include_bytes!("json_org_validator_tests/fail3.json")).is_err()); 308 | assert!(parse(include_bytes!("json_org_validator_tests/fail4.json")).is_err()); 309 | assert!(parse(include_bytes!("json_org_validator_tests/fail5.json")).is_err()); 310 | assert!(parse(include_bytes!("json_org_validator_tests/fail6.json")).is_err()); 311 | assert!(parse(include_bytes!("json_org_validator_tests/fail7.json")).is_err()); 312 | assert!(parse(include_bytes!("json_org_validator_tests/fail8.json")).is_err()); 313 | assert!(parse(include_bytes!("json_org_validator_tests/fail9.json")).is_err()); 314 | assert!(parse(include_bytes!("json_org_validator_tests/fail10.json")).is_err()); 315 | assert!(parse(include_bytes!("json_org_validator_tests/fail11.json")).is_err()); 316 | assert!(parse(include_bytes!("json_org_validator_tests/fail12.json")).is_err()); 317 | assert!(parse(include_bytes!("json_org_validator_tests/fail13.json")).is_err()); 318 | assert!(parse(include_bytes!("json_org_validator_tests/fail14.json")).is_err()); 319 | assert!(parse(include_bytes!("json_org_validator_tests/fail15.json")).is_err()); 320 | assert!(parse(include_bytes!("json_org_validator_tests/fail16.json")).is_err()); 321 | assert!(parse(include_bytes!("json_org_validator_tests/fail17.json")).is_err()); 322 | //assert!(parse(include_bytes!("json_org_validator_tests/fail18.json")).is_err()); // No maximum depth 323 | assert!(parse(include_bytes!("json_org_validator_tests/fail19.json")).is_err()); 324 | assert!(parse(include_bytes!("json_org_validator_tests/fail20.json")).is_err()); 325 | assert!(parse(include_bytes!("json_org_validator_tests/fail21.json")).is_err()); 326 | assert!(parse(include_bytes!("json_org_validator_tests/fail22.json")).is_err()); 327 | assert!(parse(include_bytes!("json_org_validator_tests/fail23.json")).is_err()); 328 | assert!(parse(include_bytes!("json_org_validator_tests/fail24.json")).is_err()); 329 | assert!(parse(include_bytes!("json_org_validator_tests/fail25.json")).is_err()); 330 | assert!(parse(include_bytes!("json_org_validator_tests/fail26.json")).is_err()); 331 | assert!(parse(include_bytes!("json_org_validator_tests/fail27.json")).is_err()); 332 | assert!(parse(include_bytes!("json_org_validator_tests/fail28.json")).is_err()); 333 | assert!(parse(include_bytes!("json_org_validator_tests/fail29.json")).is_err()); 334 | assert!(parse(include_bytes!("json_org_validator_tests/fail30.json")).is_err()); 335 | assert!(parse(include_bytes!("json_org_validator_tests/fail31.json")).is_err()); 336 | assert!(parse(include_bytes!("json_org_validator_tests/fail32.json")).is_err()); 337 | assert!(parse(include_bytes!("json_org_validator_tests/fail33.json")).is_err()); 338 | } 339 | 340 | #[test] 341 | fn err_fmt() { 342 | let er = parse(br#"paa"#).err().unwrap(); 343 | assert_eq!(format!("{}", er), "InvalidToken(112)".to_string()); 344 | assert_eq!(format!("{:?}", er), "InvalidToken(112)".to_string()); 345 | assert_eq!(er, Error::InvalidToken(112)) 346 | } 347 | 348 | #[test] 349 | fn surrogates() { 350 | assert!(parse(br#""\uDC00""#).is_err()); 351 | 352 | assert!(parse(br#""\uD83C""#).is_err()); 353 | 354 | assert!(parse(br#""\uD83C\uDF95""#).is_ok()); 355 | assert!(parse(br#""\uD83C\uFFFF""#).is_err()); 356 | assert!(parse(br#""\uD83C\FDF95""#).is_err()); 357 | assert!(parse(br#""\uD83C\\""#).is_ok()); 358 | assert!(parse(br#""\uDB00""#).is_err()); 359 | assert!(parse(br#""\uFFFF""#).is_ok()); 360 | assert!(parse(br#""\u\FFFF""#).is_err()); 361 | assert!(parse(br#""\uDD00""#).is_err()); 362 | } 363 | 364 | #[test] 365 | fn value_fmt() { 366 | assert_eq!(format!("{:?}", Value::Null), "Null".to_string()); 367 | } 368 | 369 | #[test] 370 | fn can_clone() { 371 | let _ = Value::Null.clone(); 372 | } 373 | 374 | #[cfg(feature = "preserve_order")] 375 | #[test] 376 | fn object() -> Result<(), Error> { 377 | let data: &[u8] = r#"{"u64": 1, "f64": 0.1, "utf8": "foo1", "bools": true} 378 | "# 379 | .as_bytes(); 380 | 381 | let item = parse(data)?; 382 | 383 | let d = [ 384 | (string("u64"), Value::Number(Number::Integer(b"1", b""))), 385 | (string("f64"), Value::Number(Number::Float(b"0.1", b""))), 386 | (string("utf8"), Value::String(string("foo1"))), 387 | (string("bools"), Value::Bool(true)), 388 | ] 389 | .into_iter() 390 | .map(|(key, value)| (key.into_owned(), value)) 391 | .collect::(); 392 | 393 | assert_eq!(item, Value::Object(d)); 394 | Ok(()) 395 | } 396 | --------------------------------------------------------------------------------