json_pest_parser/
lib.rs

1//  Copyright (C) 2019  Éloïs SANCHEZ
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU Affero General Public License as
5// published by the Free Software Foundation, either version 3 of the
6// License, or (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11// GNU Affero General Public License for more details.
12//
13// You should have received a copy of the GNU Affero General Public License
14// along with this program.  If not, see <https://www.gnu.org/licenses/>.
15
16//! JSON parser based on [pest](https://pest.rs).  
17//! It's is a personal crate for personal use.  
18//! The grammar used is a copy of the grammar proposed in the "pest book".  
19
20#![deny(
21    clippy::unwrap_used,
22    missing_debug_implementations,
23    missing_copy_implementations,
24    trivial_casts,
25    trivial_numeric_casts,
26    unsafe_code,
27    unstable_features,
28    unused_import_braces
29)]
30
31#[macro_use]
32extern crate pest_derive;
33
34use pest::iterators::Pair;
35use pest::Parser;
36use std::collections::HashMap;
37use std::str::FromStr;
38use thiserror::Error;
39use unwrap::unwrap;
40
41#[derive(Parser)]
42#[grammar = "json_grammar.pest"]
43struct JSONParser;
44
45#[derive(Debug, PartialEq)]
46pub enum JSONValue<'a, S: std::hash::BuildHasher> {
47    Object(HashMap<&'a str, JSONValue<'a, S>, S>),
48    Array(Vec<JSONValue<'a, S>>),
49    String(&'a str),
50    Number(Number),
51    Boolean(bool),
52    Null,
53}
54
55#[derive(Copy, Clone, Debug, PartialEq)]
56pub enum Number {
57    F64(f64),
58    U64(u64),
59}
60
61type JsonObject<'a, S> = HashMap<&'a str, JSONValue<'a, S>, S>;
62
63impl<'a, S: std::hash::BuildHasher> JSONValue<'a, S> {
64    pub fn is_object(&self) -> bool {
65        if let JSONValue::Object(_) = self {
66            true
67        } else {
68            false
69        }
70    }
71
72    pub fn to_object(&self) -> Option<&HashMap<&'a str, JSONValue<'a, S>, S>> {
73        if let JSONValue::Object(object) = self {
74            Some(object)
75        } else {
76            None
77        }
78    }
79
80    pub fn is_array(&self) -> bool {
81        if let JSONValue::Array(_) = self {
82            true
83        } else {
84            false
85        }
86    }
87
88    pub fn to_array(&self) -> Option<&Vec<JSONValue<'a, S>>> {
89        if let JSONValue::Array(array) = self {
90            Some(array)
91        } else {
92            None
93        }
94    }
95
96    pub fn is_str(&self) -> bool {
97        if let JSONValue::String(_) = self {
98            true
99        } else {
100            false
101        }
102    }
103
104    pub fn to_str(&self) -> Option<&'a str> {
105        if let JSONValue::String(string) = self {
106            Some(string)
107        } else {
108            None
109        }
110    }
111
112    pub fn is_number(&self) -> bool {
113        if let JSONValue::Number(_) = self {
114            true
115        } else {
116            false
117        }
118    }
119
120    pub fn to_f64(&self) -> Option<f64> {
121        if let JSONValue::Number(number) = self {
122            match number {
123                Number::F64(f64_) => Some(*f64_),
124                Number::U64(u64_) => Some(*u64_ as f64),
125            }
126        } else {
127            None
128        }
129    }
130
131    pub fn to_u64(&self) -> Option<u64> {
132        if let JSONValue::Number(number) = self {
133            if let Number::U64(u64_) = number {
134                Some(*u64_)
135            } else {
136                None
137            }
138        } else {
139            None
140        }
141    }
142
143    pub fn is_bool(&self) -> bool {
144        if let JSONValue::Boolean(_) = self {
145            true
146        } else {
147            false
148        }
149    }
150
151    pub fn to_bool(&self) -> Option<bool> {
152        if let JSONValue::Boolean(boolean) = self {
153            Some(*boolean)
154        } else {
155            None
156        }
157    }
158
159    pub fn is_null(&self) -> bool {
160        if let JSONValue::Null = self {
161            true
162        } else {
163            false
164        }
165    }
166}
167
168impl<'a, S: std::hash::BuildHasher> ToString for JSONValue<'a, S> {
169    fn to_string(&self) -> String {
170        match self {
171            JSONValue::Object(o) => {
172                let contents: Vec<_> = o
173                    .iter()
174                    .map(|(name, value)| format!("\"{}\":{}", name, value.to_string()))
175                    .collect();
176                format!("{{{}}}", contents.join(","))
177            }
178            JSONValue::Array(a) => {
179                let contents: Vec<_> = a.iter().map(Self::to_string).collect();
180                format!("[{}]", contents.join(","))
181            }
182            JSONValue::String(s) => format!("\"{}\"", s),
183            JSONValue::Number(n) => match n {
184                Number::F64(f64_) => format!("{}", f64_),
185                Number::U64(u64_) => format!("{}", u64_),
186            },
187            JSONValue::Boolean(b) => format!("{}", b),
188            JSONValue::Null => "null".to_owned(),
189        }
190    }
191}
192
193#[derive(Debug, Error)]
194#[error("Fail to parse JSON String : {:?}", cause)]
195pub struct ParseJsonError {
196    pub cause: String,
197}
198
199pub fn parse_json_string<'a>(
200    source: &'a str,
201) -> Result<
202    JSONValue<'a, std::hash::BuildHasherDefault<std::collections::hash_map::DefaultHasher>>,
203    ParseJsonError,
204> {
205    parse_json_string_with_specific_hasher::<
206        std::hash::BuildHasherDefault<std::collections::hash_map::DefaultHasher>,
207    >(source)
208}
209
210pub fn parse_json_string_with_specific_hasher<S: std::hash::BuildHasher + Default>(
211    source: &str,
212) -> Result<JSONValue<S>, ParseJsonError> {
213    match JSONParser::parse(Rule::json, source) {
214        Ok(mut pair) => Ok(parse_value(unwrap!(
215            pair.next(),
216            "Fail to parse Rule::json"
217        ))),
218        Err(pest_error) => Err(ParseJsonError {
219            cause: format!("{:?}", pest_error),
220        }),
221    }
222}
223
224fn parse_value<S: std::hash::BuildHasher + Default>(pair: Pair<Rule>) -> JSONValue<S> {
225    match pair.as_rule() {
226        Rule::object => JSONValue::Object(
227            pair.into_inner()
228                .map(|pair| {
229                    let mut inner_rules = pair.into_inner();
230                    let name = unwrap!(
231                        unwrap!(inner_rules.next(), "Fail to parse Rule::object::name")
232                            .into_inner()
233                            .next(),
234                        "Fail to parse Rule::object::name"
235                    )
236                    .as_str();
237                    let value = parse_value(unwrap!(
238                        inner_rules.next(),
239                        "Fail to parse Rule::object::value"
240                    ));
241                    (name, value)
242                })
243                .collect(),
244        ),
245        Rule::array => JSONValue::Array(pair.into_inner().map(parse_value).collect()),
246        Rule::string => JSONValue::String(
247            unwrap!(pair.into_inner().next(), "Fail to parse Rule::string").as_str(),
248        ),
249        Rule::number => {
250            if let Ok(number_u64) = u64::from_str(pair.as_str()) {
251                JSONValue::Number(Number::U64(number_u64))
252            } else {
253                JSONValue::Number(Number::F64(unwrap!(
254                    pair.as_str().parse(),
255                    "Fail to parse Rule::number as u64 and f64"
256                )))
257            }
258        }
259        Rule::boolean => JSONValue::Boolean(unwrap!(
260            pair.as_str().parse(),
261            "Fail to parse Rule::boolean"
262        )),
263        Rule::null => JSONValue::Null,
264        Rule::json
265        | Rule::EOI
266        | Rule::pair
267        | Rule::value
268        | Rule::inner_string
269        | Rule::char
270        | Rule::WHITESPACE => unreachable!(),
271    }
272}
273
274pub fn get_optional_usize<S: std::hash::BuildHasher>(
275    json_block: &HashMap<&str, JSONValue<S>, S>,
276    field: &str,
277) -> Result<Option<usize>, ParseJsonError> {
278    Ok(match json_block.get(field) {
279        Some(value) => {
280            if !value.is_null() {
281                Some(
282                    value
283                        .to_f64()
284                        .ok_or_else(|| ParseJsonError {
285                            cause: format!(
286                                "Fail to parse json : field '{}' must be a number !",
287                                field
288                            ),
289                        })?
290                        .trunc() as usize,
291                )
292            } else {
293                None
294            }
295        }
296        None => None,
297    })
298}
299
300pub fn get_optional_str_not_empty<'a, S: std::hash::BuildHasher>(
301    json_block: &'a HashMap<&str, JSONValue<S>, S>,
302    field: &str,
303) -> Result<Option<&'a str>, ParseJsonError> {
304    let result = get_optional_str(json_block, field);
305    if let Ok(Some(value)) = result {
306        if !value.is_empty() {
307            Ok(Some(value))
308        } else {
309            Ok(None)
310        }
311    } else {
312        result
313    }
314}
315
316pub fn get_optional_str<'a, S: std::hash::BuildHasher>(
317    json_block: &'a HashMap<&str, JSONValue<S>, S>,
318    field: &str,
319) -> Result<Option<&'a str>, ParseJsonError> {
320    Ok(match json_block.get(field) {
321        Some(value) => {
322            if !value.is_null() {
323                Some(value.to_str().ok_or_else(|| ParseJsonError {
324                    cause: format!("Fail to parse json : field '{}' must be a string !", field),
325                })?)
326            } else {
327                None
328            }
329        }
330        None => None,
331    })
332}
333
334pub fn get_u64<S: std::hash::BuildHasher>(
335    json_block: &HashMap<&str, JSONValue<S>, S>,
336    field: &str,
337) -> Result<u64, ParseJsonError> {
338    Ok(json_block
339        .get(field)
340        .ok_or_else(|| ParseJsonError {
341            cause: format!("Fail to parse json : field '{}' must exist !", field),
342        })?
343        .to_u64()
344        .ok_or_else(|| ParseJsonError {
345            cause: format!("Fail to parse json : field '{}' must be a number !", field),
346        })?)
347}
348
349pub fn get_number<S: std::hash::BuildHasher>(
350    json_block: &HashMap<&str, JSONValue<S>, S>,
351    field: &str,
352) -> Result<f64, ParseJsonError> {
353    Ok(json_block
354        .get(field)
355        .ok_or_else(|| ParseJsonError {
356            cause: format!("Fail to parse json : field '{}' must exist !", field),
357        })?
358        .to_f64()
359        .ok_or_else(|| ParseJsonError {
360            cause: format!("Fail to parse json : field '{}' must be a number !", field),
361        })?)
362}
363
364pub fn get_str<'a, S: std::hash::BuildHasher>(
365    json_block: &'a HashMap<&str, JSONValue<S>, S>,
366    field: &str,
367) -> Result<&'a str, ParseJsonError> {
368    Ok(json_block
369        .get(field)
370        .ok_or_else(|| ParseJsonError {
371            cause: format!("Fail to parse json : field '{}' must exist !", field),
372        })?
373        .to_str()
374        .ok_or_else(|| ParseJsonError {
375            cause: format!("Fail to parse json : field '{}' must be a string !", field),
376        })?)
377}
378
379pub fn get_str_array<'a, S: std::hash::BuildHasher>(
380    json_block: &'a HashMap<&str, JSONValue<S>, S>,
381    field: &str,
382) -> Result<Vec<&'a str>, ParseJsonError> {
383    json_block
384        .get(field)
385        .ok_or_else(|| ParseJsonError {
386            cause: format!("Fail to parse json : field '{}' must exist !", field),
387        })?
388        .to_array()
389        .ok_or_else(|| ParseJsonError {
390            cause: format!("Fail to parse json : field '{}' must be an array !", field),
391        })?
392        .iter()
393        .map(|v| {
394            v.to_str().ok_or_else(|| ParseJsonError {
395                cause: format!(
396                    "Fail to parse json : field '{}' must be an array of string !",
397                    field
398                ),
399            })
400        })
401        .collect()
402}
403
404pub fn get_array<'a, S: std::hash::BuildHasher>(
405    json_block: &'a HashMap<&str, JSONValue<S>, S>,
406    field: &str,
407) -> Result<Vec<&'a JSONValue<'a, S>>, ParseJsonError> {
408    Ok(json_block
409        .get(field)
410        .ok_or_else(|| ParseJsonError {
411            cause: format!("Fail to parse json : field '{}' must exist !", field),
412        })?
413        .to_array()
414        .ok_or_else(|| ParseJsonError {
415            cause: format!("Fail to parse json : field '{}' must be an array !", field),
416        })?
417        .iter()
418        .map(|v| v)
419        .collect())
420}
421
422pub fn get_object_array<'a, S: std::hash::BuildHasher>(
423    json_block: &'a JsonObject<'a, S>,
424    field: &str,
425) -> Result<Vec<&'a JsonObject<'a, S>>, ParseJsonError> {
426    json_block
427        .get(field)
428        .ok_or_else(|| ParseJsonError {
429            cause: format!("Fail to parse json : field '{}' must exist !", field),
430        })?
431        .to_array()
432        .ok_or_else(|| ParseJsonError {
433            cause: format!("Fail to parse json : field '{}' must be an array !", field),
434        })?
435        .iter()
436        .map(|v| {
437            v.to_object().ok_or_else(|| ParseJsonError {
438                cause: format!("Fail to parse json : field '{}' must be an object !", field),
439            })
440        })
441        .collect()
442}
443
444#[cfg(test)]
445mod tests {
446    use super::*;
447    use pretty_assertions::assert_eq;
448
449    #[test]
450    fn test_parse_too_large_number() {
451        assert_eq!(
452            Ok(100_010_200_000_006_940),
453            u64::from_str("100010200000006940"),
454        );
455
456        let json_string = "{
457            \"nonce\": 100010200000006940
458        }";
459
460        let json_value = parse_json_string(json_string).expect("Fail to parse json string !");
461
462        assert!(json_value.is_object());
463
464        let json_object = json_value.to_object().expect("safe unwrap");
465
466        assert_eq!(
467            json_object.get("nonce"),
468            Some(&JSONValue::Number(Number::U64(100_010_200_000_006_940)))
469        );
470    }
471
472    #[test]
473    fn test_parse_wrong_json_string() {
474        let json_string = "{";
475        assert!(parse_json_string(json_string).is_err());
476    }
477
478    fn test_parse_json_string_check_object_type(
479        json_value: &JSONValue<
480            std::hash::BuildHasherDefault<std::collections::hash_map::DefaultHasher>,
481        >,
482    ) {
483        assert!(json_value.is_object());
484        assert!(!json_value.is_array());
485        assert!(!json_value.is_str());
486        assert!(!json_value.is_number());
487        assert!(!json_value.is_bool());
488        assert!(!json_value.is_null());
489        assert_eq!(None, json_value.to_array());
490        assert_eq!(None, json_value.to_str());
491        assert_eq!(None, json_value.to_f64());
492        assert_eq!(None, json_value.to_u64());
493        assert_eq!(None, json_value.to_bool());
494    }
495
496    #[test]
497    fn test_parse_json_string() {
498        let json_string = "{
499            \"name\": \"toto\",
500            \"age\": 25,
501            \"legalAge\": true,
502            \"ratio\": 0.5,
503            \"friends\": [
504                \"titi\",
505                \"tata\"
506            ],
507            \"car\": null
508        }";
509
510        let json_value = parse_json_string(json_string).expect("Fail to parse json string !");
511
512        assert_eq!(
513            json_value.to_string(),
514            "{\"name\":\"toto\",\"legalAge\":true,\"ratio\":0.5,\"age\":25,\"friends\":[\"titi\",\"tata\"],\"car\":null}"
515        );
516
517        test_parse_json_string_check_object_type(&json_value);
518
519        let json_object = json_value.to_object().expect("safe unwrap");
520
521        let name_field = json_object.get("name").expect("name field must be exist");
522        assert!(name_field.is_str());
523        assert_eq!(name_field, &JSONValue::String("toto"));
524
525        let age_field = json_object.get("age").expect("age field must be exist");
526        assert!(age_field.is_number());
527        assert_eq!(age_field.to_f64(), Some(25.0f64));
528        assert_eq!(age_field.to_u64(), Some(25u64));
529
530        let legal_age_field = json_object
531            .get("legalAge")
532            .expect("legalAge field must be exist");
533        assert!(legal_age_field.is_bool());
534        assert_eq!(legal_age_field.to_bool(), Some(true));
535
536        let ratio_field = json_object.get("ratio").expect("ratio field must be exist");
537        assert!(ratio_field.is_number());
538        assert_eq!(ratio_field.to_f64(), Some(0.5f64));
539        assert_eq!(ratio_field.to_u64(), None);
540
541        let friends_field = json_object
542            .get("friends")
543            .expect("friends field must be exist");
544        assert!(!friends_field.is_object());
545        assert_eq!(None, friends_field.to_object());
546        assert!(friends_field.is_array());
547
548        let friends = friends_field
549            .to_array()
550            .expect("frinds_field must be an array");
551
552        assert_eq!(2, friends.len());
553        assert_eq!(
554            "titi",
555            friends[0]
556                .to_str()
557                .expect("friends field must be an array of String")
558        );
559        assert_eq!(
560            "tata",
561            friends[1]
562                .to_str()
563                .expect("friends field must be an array of String")
564        );
565
566        let car_field = json_object.get("car").expect("car field must be exist");
567        assert!(car_field.is_null());
568    }
569}