jsn/
lib.rs

1#![warn(missing_docs)]
2
3//! This crate implements a queryable, streaming, JSON pull parser.
4//!
5//! - __Pull parser?__: The [parser](crate::TokenReader) is implemented as iterator that emits
6//! [tokens](crate::Token).
7//! - __Streaming?__: The JSON document being parsed is never fully loaded into memory. It is read
8//! & validated byte by byte. This makes it ideal for dealing with large JSON documents
9//! - __Queryable?__ You can [configure the parser](crate::TokenReader::with_mask) to only emit & allocate
10//! tokens for the parts of the input you are interested in.
11//!
12//! JSON is expected to conform to [RFC
13//! 8259](https://datatracker.ietf.org/doc/html/rfc8259).
14//! However, [newline-delimited JSON](https://github.com/ndjson/ndjson-spec) and [concatenated
15//! json](https://en.wikipedia.org/wiki/JSON_streaming#Concatenated_JSON) formats are also
16//! [supported](crate::TokenReader::with_format).
17//!
18//! Input can be anything that implements the [`Read`](std::io::Read) trait (e.g. a file, byte
19//! slice, network socket etc..)
20//!
21//! ## Basic Usage
22//!
23//! ```
24//! use jsn::{TokenReader, mask::*, Format};
25//! use std::error::Error;
26//!
27//! fn main() -> Result<(), Box<dyn Error>> {
28//!     let data = r#"
29//!         {
30//!             "name": "John Doe",
31//!             "age": 43,
32//!             "nicknames": [ "joe" ],
33//!             "phone": {
34//!                 "carrier": "Verizon",
35//!                 "numbers": [ "+44 1234567", "+44 2345678" ]
36//!             }
37//!         }
38//!         {
39//!             "name": "Jane Doe",
40//!             "age": 32,
41//!             "nicknames": [ "J" ],
42//!             "phone": {
43//!                 "carrier": "AT&T",
44//!                 "numbers": ["+33 38339"]
45//!             }
46//!         }
47//!     "#;
48//!
49//!     let mask = key("numbers").and(index(0))
50//!         .or(key("name"))
51//!         .or(key("age"));
52//!     let mut iter = TokenReader::new(data.as_bytes())
53//!         .with_mask(mask)
54//!         .with_format(Format::Concatenated)
55//!         .into_iter();
56//!
57//!     assert_eq!(iter.next().unwrap()?, "John Doe");
58//!     assert_eq!(iter.next().unwrap()?, 43);
59//!     assert_eq!(iter.next().unwrap()?, "+44 1234567");
60//!     assert_eq!(iter.next().unwrap()?, "Jane Doe");
61//!     assert_eq!(iter.next().unwrap()?, 32);
62//!     assert_eq!(iter.next().unwrap()?, "+33 38339");
63//!     assert_eq!(iter.next(), None);
64//!
65//!     Ok(())
66//! }
67//! ```
68//!
69//! __A few things to notice and whet your appetite:__
70//! - This is new-line delimited JSON
71//! - We only pay for heap allocating the tokens we extracted (i.e. the first index in the
72//! "numbers" array and the "name" and "age" values).
73//! - You can compare tokens to native rust types
74//! - Token masks match anywhere in json; Though the top-level value was an object, we used the
75//! `index` mask to match a token that was at index 0 in an array nested in the "phones" object.
76
77mod error;
78mod input;
79mod iter;
80pub mod mask;
81mod raw_token;
82mod scan;
83mod structure;
84mod token;
85
86pub use error::{JsonError, Position, Reason};
87pub use iter::{DryRun, Format, TokenReader, Tokens};
88pub use token::{FromJson, Token};
89
90#[doc = include_str!("../README.md")]
91#[cfg(doctest)]
92pub struct ReadmeDocTests;