binary_extract/
lib.rs

1use json::{self, JsonValue};
2
3#[derive(Debug)]
4pub enum ExtractError {
5    JsonError(json::Error),
6    KeyNotFound(),
7    JsonTooShort(),
8    MissingEnd(),
9}
10
11impl From<json::Error> for ExtractError {
12    fn from(err: json::Error) -> Self {
13        ExtractError::JsonError(err)
14    }
15}
16
17/// Extract a value from a json string without parsing the whole thing.
18///
19/// With the object from benches/json.rs, `extract()` is ~3.5x faster than
20/// `json::parse`.
21///
22/// # Examples
23///
24/// ```
25/// let value = binary_extract::extract(r#"{"foo": "bar"}"#, "foo").unwrap();
26/// assert_eq!(value, "bar");
27/// ```
28pub fn extract(s: &str, key: &str) -> Result<JsonValue, ExtractError> {
29    let mut in_string = false;
30    let mut is_key = true;
31    let mut level = 0;
32    let key_decorated = format!("\"{key}\"");
33    let mut it = s.chars().enumerate();
34
35    while let Some((i, c)) = it.next()  {
36        match c {
37            '\\' => {
38                it.nth(0);
39                continue;
40            }
41            '"' => {
42                in_string = !in_string;
43                continue;
44            }
45            _ => (),
46        }
47        if !in_string {
48            match c {
49                ':' => is_key = false,
50                ',' => is_key = true,
51                '{' => level = level + 1,
52                '}' => level = level - 1,
53                _ => (),
54            }
55        }
56        if is_key && level == 1 && i > 0 {
57            if let Some(sub) = s.get(i - 1..i + key.len() + 1) {
58                if sub == key_decorated {
59                    let start = i + key.len() + 2;
60                    if s.len() <= start {
61                        return Err(ExtractError::JsonTooShort());
62                    }
63                    let end = find_end(&s[start..])? + start;
64                    return Ok(json::parse(&s[start..end])?);
65                }
66            }
67        }
68
69    }
70
71    Err(ExtractError::KeyNotFound())
72}
73
74fn find_end(s: &str) -> Result<usize, ExtractError> {
75    let mut level = 0;
76    let mut first_char: Option<char> = Default::default();
77
78    for (i, c) in s.chars().enumerate() {
79        if let None = first_char {
80            first_char = Some(c);
81        }
82        match c {
83            '{' | '[' => {
84                level = level + 1;
85                continue;
86            }
87            '}' | ']' => {
88                level = level - 1;
89                if level > 0 {
90                    continue;
91                }
92            }
93            _ => ()
94        }
95        if level < 0 || level == 0 && (c == ',' || c == '}' || c == ']') {
96            return match first_char {
97                Some('{') | Some('[') => Ok(i + 1),
98                _ => Ok(i),
99            };
100        }
101    }
102
103    Err(ExtractError::MissingEnd())
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use json::{array, object};
110
111    #[test]
112    fn test() {
113        let value = extract(r#"{"foo": "bar"}"#, "foo").unwrap();
114        assert_eq!(value, "bar");
115
116        let value = extract(r#"{"foo": "bar","bar":"baz"}"#, "foo").unwrap();
117        assert_eq!(value, "bar");
118
119        let value = extract(r#"{"foo": "bar","bar":"baz"}"#, "bar").unwrap();
120        assert_eq!(value, "baz");
121
122        let value = extract(r#"{"foo":{"beep":"boop","bar":"oops"},"bar":"baz"}"#, "bar").unwrap();
123        assert_eq!(value, "baz");
124
125        let value = extract(r#"{"foo":[{"bar":"oops"}],"bar":"baz"}"#, "bar").unwrap();
126        assert_eq!(value, "baz");
127
128        let value = extract(r#"{"foo":{"bar":"baz"}}"#, "foo").unwrap();
129        assert_eq!(
130            value,
131            object! {
132                bar: "baz"
133            }
134        );
135
136        let value = extract(r#"{"foo":["bar","baz"]}"#, "foo").unwrap();
137        assert_eq!(
138            value,
139            array! {
140                "bar",
141                "baz"
142            }
143        );
144
145        let value = extract(r#"{"foo": "bar"}"#, "foo").unwrap();
146        assert_eq!(value, "bar");
147
148        let value = extract(r#"{"beep":"\\","foo":"bar"}"#, "foo").unwrap();
149        assert_eq!(value, "bar");
150
151        let value = extract(r#"{"foo":"bar\"baz"}"#, "foo").unwrap();
152        assert_eq!(value, "bar\"baz");
153
154        let value = extract(r#"{"_a":0,"a_":1,"_a_":2,"a":3}"#, "a").unwrap();
155        assert_eq!(value, 3);
156
157        extract(r#"{"foo"}"#, "foo").unwrap_err();
158        extract(r#"{"foo":"bar"}"#, "bar").unwrap_err();
159
160        let value = extract(r#"{"foo":{"bar":{"baz":"beep"}}}"#, "foo").unwrap();
161        assert_eq!(
162            value,
163            object! {
164                bar: {
165                    baz: "beep"
166                }
167            }
168        );
169    }
170}