simple_json_parser/
lib.rs

1#[derive(Debug, Clone, PartialEq, Eq)]
2pub enum JSONKey<'a> {
3    Slice(&'a str),
4    Index(usize),
5}
6
7#[derive(Debug, PartialEq, Eq)]
8pub enum RootJSONValue<'a> {
9    String(&'a str),
10    Number(&'a str),
11    Boolean(bool),
12    Null,
13}
14
15#[derive(Debug)]
16pub enum JSONParseErrorReason {
17    ExpectedColon,
18    ExpectedEndOfValue,
19    /// Doubles as both closing and ending
20    ExpectedBracket,
21    ExpectedTrueFalseNull,
22    ExpectedKey,
23    ExpectedValue,
24    ExpectedEndOfMultilineComment,
25    /// Both for string values and keys
26    ExpectedQuote,
27}
28
29#[derive(Debug)]
30pub struct JSONParseError {
31    pub at: usize,
32    pub reason: JSONParseErrorReason,
33}
34
35impl std::error::Error for JSONParseError {}
36
37impl std::fmt::Display for JSONParseError {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
39        f.write_fmt(format_args!(
40            "JSONParseError: {:?} at {:?}",
41            self.reason, self.at
42        ))
43    }
44}
45
46/// If you want to return early (break on an exception in the callback) or
47/// more configuration use [`parse_with_exit_signal`]
48///
49/// # Errors
50/// Returns an error if it tries to parse invalid JSON input
51pub fn parse<'a>(
52    on: &'a str,
53    mut cb: impl for<'b> FnMut(&'b [JSONKey<'a>], RootJSONValue<'a>),
54) -> Result<usize, JSONParseError> {
55    parse_with_exit_signal(
56        on,
57        |k, v| {
58            cb(k, v);
59            false
60        },
61        false,
62        true,
63    )
64}
65
66enum State {
67    InKey {
68        escaped: bool,
69        start: usize,
70    },
71    Colon,
72    InObject,
73    Comment {
74        start: usize,
75        multiline: bool,
76        last_was_asterisk: bool,
77        hash: bool,
78    },
79    ExpectingValue,
80    StringValue {
81        start: usize,
82        escaped: bool,
83    },
84    NumberValue {
85        start: usize,
86    },
87    TrueFalseNull {
88        start: usize,
89    },
90    EndOfValue,
91}
92
93// TODO always pops from key_chain **unless** we are in an array.
94// TODO there are complications using this in an iterator when we yielding numbers
95fn end_of_value(
96    idx: usize,
97    chr: char,
98    state: &mut State,
99    key_chain: &mut Vec<JSONKey<'_>>,
100    allow_comments: bool,
101) -> Result<(), JSONParseError> {
102    if chr == ',' {
103        if let Some(JSONKey::Index(i)) = key_chain.last_mut() {
104            *i += 1;
105            *state = State::ExpectingValue;
106        } else {
107            key_chain.pop();
108            *state = State::InObject;
109        }
110    } else if let ('}', Some(JSONKey::Slice(..))) = (chr, key_chain.last()) {
111        // TODO errors here if index
112        key_chain.pop();
113    } else if let (']', Some(JSONKey::Index(..))) = (chr, key_chain.last()) {
114        // TODO errors here if slice etc
115        key_chain.pop();
116    } else if let (true, c @ ('/' | '#')) = (allow_comments, chr) {
117        key_chain.pop();
118        *state = State::Comment {
119            last_was_asterisk: false,
120            start: idx,
121            multiline: false,
122            hash: c == '#',
123        };
124    } else if !chr.is_whitespace() {
125        return Err(JSONParseError {
126            at: idx,
127            reason: JSONParseErrorReason::ExpectedEndOfValue,
128        });
129    }
130    Ok(())
131}
132
133/// Returns the number of bytes parsed.
134/// `exit_on_first_value` returns once the first object has been parsed.
135///
136/// # Errors
137/// Returns an error if it tries to parse invalid JSON input
138#[allow(clippy::too_many_lines)]
139pub fn parse_with_exit_signal<'a>(
140    on: &'a str,
141    mut cb: impl for<'b> FnMut(&'b [JSONKey<'a>], RootJSONValue<'a>) -> bool,
142    exit_on_first_value: bool,
143    allow_comments: bool,
144) -> Result<usize, JSONParseError> {
145    let chars = on.char_indices();
146
147    let mut key_chain = Vec::new();
148    let mut state = State::ExpectingValue;
149
150    for (idx, chr) in chars {
151        match state {
152            State::InKey {
153                start,
154                ref mut escaped,
155            } => {
156                if !*escaped && chr == '"' {
157                    key_chain.push(JSONKey::Slice(&on[start..idx]));
158                    state = State::Colon;
159                } else {
160                    *escaped = chr == '\\';
161                }
162            }
163            State::StringValue {
164                start,
165                ref mut escaped,
166            } => {
167                if !*escaped && chr == '"' {
168                    state = State::EndOfValue;
169                    let res = cb(&key_chain, RootJSONValue::String(&on[start..idx]));
170                    if res {
171                        return Ok(idx + chr.len_utf8());
172                    }
173                } else {
174                    *escaped = chr == '\\';
175                }
176            }
177            State::Colon => {
178                if chr == ':' {
179                    state = State::ExpectingValue;
180                } else if !chr.is_whitespace() {
181                    return Err(JSONParseError {
182                        at: idx,
183                        reason: JSONParseErrorReason::ExpectedColon,
184                    });
185                }
186            }
187            State::EndOfValue => {
188                end_of_value(idx, chr, &mut state, &mut key_chain, allow_comments)?;
189
190                if exit_on_first_value && key_chain.is_empty() && chr != ',' {
191                    return Ok(idx + chr.len_utf8());
192                }
193            }
194            State::Comment {
195                ref mut last_was_asterisk,
196                ref mut multiline,
197                hash,
198                start,
199            } => {
200                if chr == '\n' && !*multiline {
201                    if let Some(JSONKey::Index(..)) = key_chain.last() {
202                        state = State::ExpectingValue;
203                    } else {
204                        state = State::InObject;
205                    }
206                } else if chr == '*' && start + 1 == idx && !hash {
207                    *multiline = true;
208                } else if *multiline {
209                    if *last_was_asterisk && chr == '/' {
210                        if let Some(JSONKey::Index(..)) = key_chain.last() {
211                            state = State::ExpectingValue;
212                        } else {
213                            state = State::InObject;
214                        }
215                    } else {
216                        *last_was_asterisk = chr == '*';
217                    }
218                }
219            }
220            State::ExpectingValue => {
221                state = match chr {
222                    '{' => State::InObject,
223                    '[' => {
224                        key_chain.push(JSONKey::Index(0));
225                        State::ExpectingValue
226                    }
227                    '"' => State::StringValue {
228                        start: idx + '"'.len_utf8(),
229                        escaped: false,
230                    },
231                    c @ ('/' | '#') if allow_comments => State::Comment {
232                        last_was_asterisk: false,
233                        start: idx,
234                        multiline: false,
235                        hash: c == '#',
236                    },
237                    '0'..='9' | '-' => State::NumberValue { start: idx },
238                    't' | 'f' | 'n' => State::TrueFalseNull { start: idx },
239                    chr if chr.is_whitespace() => state,
240                    _ => {
241                        return Err(JSONParseError {
242                            at: idx,
243                            reason: JSONParseErrorReason::ExpectedValue,
244                        })
245                    }
246                }
247            }
248            State::InObject => {
249                if chr == '"' {
250                    state = State::InKey {
251                        escaped: false,
252                        start: idx + '"'.len_utf8(),
253                    };
254                } else if chr == '}' {
255                    if let Some(JSONKey::Index(..)) = key_chain.last() {
256                        state = State::ExpectingValue;
257                    } else {
258                        state = State::InObject;
259                    }
260                } else if let (true, c @ ('/' | '#')) = (allow_comments, chr) {
261                    state = State::Comment {
262                        last_was_asterisk: false,
263                        start: idx,
264                        multiline: false,
265                        hash: c == '#',
266                    };
267                } else if !chr.is_whitespace() {
268                    return Err(JSONParseError {
269                        at: idx,
270                        reason: JSONParseErrorReason::ExpectedKey,
271                    });
272                }
273            }
274            State::NumberValue { start } => {
275                // TODO actual number handing
276                if chr.is_whitespace() || matches!(chr, '}' | ',' | ']') {
277                    let res = cb(&key_chain, RootJSONValue::Number(&on[start..idx]));
278                    if res {
279                        return Ok(idx);
280                    }
281                    state = State::EndOfValue;
282                    end_of_value(idx, chr, &mut state, &mut key_chain, allow_comments)?;
283                }
284            }
285            State::TrueFalseNull { start } => {
286                let diff = idx - start + 1;
287                if diff < 4 {
288                    // ...
289                } else if diff == 4 {
290                    match &on[start..=idx] {
291                        "true" => {
292                            let res = cb(&key_chain, RootJSONValue::Boolean(true));
293                            if res {
294                                return Ok(idx + chr.len_utf8());
295                            }
296                            state = State::EndOfValue;
297                        }
298                        "null" => {
299                            let res = cb(&key_chain, RootJSONValue::Null);
300                            if res {
301                                return Ok(idx + chr.len_utf8());
302                            }
303                            state = State::EndOfValue;
304                        }
305                        "fals" => {}
306                        _ => {
307                            return Err(JSONParseError {
308                                at: idx,
309                                reason: JSONParseErrorReason::ExpectedTrueFalseNull,
310                            })
311                        }
312                    }
313                } else if let "false" = &on[start..=idx] {
314                    let res = cb(&key_chain, RootJSONValue::Boolean(false));
315                    if res {
316                        return Ok(idx + chr.len_utf8());
317                    }
318                    state = State::EndOfValue;
319                } else {
320                    return Err(JSONParseError {
321                        at: idx,
322                        reason: JSONParseErrorReason::ExpectedTrueFalseNull,
323                    });
324                }
325            }
326        }
327    }
328
329    match state {
330        State::InKey { .. } | State::StringValue { .. } => {
331            return Err(JSONParseError {
332                at: on.len(),
333                reason: JSONParseErrorReason::ExpectedQuote,
334            })
335        }
336        State::Colon => {
337            return Err(JSONParseError {
338                at: on.len(),
339                reason: JSONParseErrorReason::ExpectedColon,
340            });
341        }
342        State::Comment { multiline, .. } => {
343            if multiline {
344                return Err(JSONParseError {
345                    at: on.len(),
346                    reason: JSONParseErrorReason::ExpectedEndOfMultilineComment,
347                });
348            }
349        }
350        State::EndOfValue | State::ExpectingValue => {
351            if !key_chain.is_empty() {
352                return Err(JSONParseError {
353                    at: on.len(),
354                    reason: JSONParseErrorReason::ExpectedBracket,
355                });
356            }
357        }
358        State::InObject => {
359            return Err(JSONParseError {
360                at: on.len(),
361                reason: JSONParseErrorReason::ExpectedBracket,
362            });
363        }
364        State::NumberValue { start } => {
365            // TODO actual number handing
366            let _result = cb(&key_chain, RootJSONValue::Number(&on[start..]));
367        }
368        State::TrueFalseNull { start: _ } => {
369            return Err(JSONParseError {
370                at: on.len(),
371                reason: JSONParseErrorReason::ExpectedTrueFalseNull,
372            })
373        }
374    }
375
376    Ok(on.len())
377}