rhai/api/
json.rs

1//! Module that defines JSON manipulation functions for [`Engine`].
2#![cfg(not(feature = "no_object"))]
3
4use crate::parser::{ParseSettingFlags, ParseState};
5use crate::tokenizer::Token;
6use crate::types::dynamic::Union;
7use crate::{Dynamic, Engine, LexError, Map, RhaiResultOf};
8use std::fmt::Write;
9#[cfg(feature = "no_std")]
10use std::prelude::v1::*;
11
12impl Engine {
13    /// Parse a JSON string into an [object map][Map].
14    ///
15    /// This is a light-weight alternative to using, say, [`serde_json`](https://crates.io/crates/serde_json)
16    /// to deserialize the JSON.
17    ///
18    /// Not available under `no_object`.
19    ///
20    /// The JSON string must be an object hash.  It cannot be a simple primitive value.
21    ///
22    /// Set `has_null` to `true` in order to map `null` values to `()`.
23    /// Setting it to `false` causes a syntax error for any `null` value.
24    ///
25    /// JSON sub-objects are handled transparently.
26    ///
27    /// This function can be used together with [`format_map_as_json`] to work with JSON texts
28    /// without using the [`serde_json`](https://crates.io/crates/serde_json) crate (which is heavy).
29    ///
30    /// # Example
31    ///
32    /// ```
33    /// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
34    /// use rhai::{Engine, Map};
35    ///
36    /// let engine = Engine::new();
37    ///
38    /// let map = engine.parse_json(r#"
39    /// {
40    ///     "a": 123,
41    ///     "b": 42,
42    ///     "c": {
43    ///         "x": false,
44    ///         "y": true,
45    ///         "z": '$'
46    ///     },
47    ///     "d": null
48    /// }"#, true)?;
49    ///
50    /// assert_eq!(map.len(), 4);
51    /// assert_eq!(map["a"].as_int().expect("a should exist"), 123);
52    /// assert_eq!(map["b"].as_int().expect("b should exist"), 42);
53    /// assert_eq!(map["d"].as_unit().expect("d should exist"), ());
54    ///
55    /// let c = map["c"].as_map_ref().expect("c should exist");
56    /// assert_eq!(c["x"].as_bool().expect("x should be bool"), false);
57    /// assert_eq!(c["y"].as_bool().expect("y should be bool"), true);
58    /// assert_eq!(c["z"].as_char().expect("z should be char"), '$');
59    /// # Ok(())
60    /// # }
61    /// ```
62    #[inline]
63    pub fn parse_json(&self, json: impl AsRef<str>, has_null: bool) -> RhaiResultOf<Map> {
64        let scripts = [json.as_ref()];
65
66        let (stream, tokenizer_control) = self.lex_raw(
67            &scripts,
68            Some(if has_null {
69                &|token, _, _| {
70                    match token {
71                        // `null` => `()`
72                        Token::Reserved(s) if &*s == "null" => Token::Unit,
73                        // `{` => `#{`
74                        Token::LeftBrace => Token::MapStart,
75                        // Disallowed syntax
76                        t @ (Token::Unit | Token::MapStart) => Token::LexError(
77                            LexError::ImproperSymbol(t.literal_syntax().to_string(), String::new())
78                                .into(),
79                        ),
80                        Token::InterpolatedString(..) => Token::LexError(
81                            LexError::ImproperSymbol(
82                                "interpolated string".to_string(),
83                                String::new(),
84                            )
85                            .into(),
86                        ),
87                        // All others
88                        _ => token,
89                    }
90                }
91            } else {
92                &|token, _, _| {
93                    match token {
94                        Token::Reserved(s) if &*s == "null" => Token::LexError(
95                            LexError::ImproperSymbol("null".to_string(), String::new()).into(),
96                        ),
97                        // `{` => `#{`
98                        Token::LeftBrace => Token::MapStart,
99                        // Disallowed syntax
100                        t @ (Token::Unit | Token::MapStart) => Token::LexError(
101                            LexError::ImproperSymbol(t.literal_syntax().to_string(), String::new())
102                                .into(),
103                        ),
104                        Token::InterpolatedString(..) => Token::LexError(
105                            LexError::ImproperSymbol(
106                                "interpolated string".to_string(),
107                                String::new(),
108                            )
109                            .into(),
110                        ),
111                        // All others
112                        _ => token,
113                    }
114                }
115            }),
116        );
117
118        let ast = {
119            let input = &mut stream.peekable();
120            let lib = &mut <_>::default();
121            let state = ParseState::new(None, input, tokenizer_control, lib);
122
123            self.parse_global_expr(
124                state,
125                |s| s.flags |= ParseSettingFlags::DISALLOW_UNQUOTED_MAP_PROPERTIES,
126                #[cfg(not(feature = "no_optimize"))]
127                crate::OptimizationLevel::None,
128            )?
129        };
130
131        self.eval_ast(&ast)
132    }
133}
134
135/// Return the JSON representation of an [object map][Map].
136///
137/// Not available under `no_std`.
138///
139/// This function can be used together with [`Engine::parse_json`] to work with JSON texts
140/// without using the [`serde_json`](https://crates.io/crates/serde_json) crate (which is heavy).
141///
142/// # Data types
143///
144/// Only the following data types should be kept inside the object map: [`INT`][crate::INT],
145/// [`FLOAT`][crate::FLOAT], [`ImmutableString`][crate::ImmutableString], `char`, `bool`, `()`,
146/// [`Array`][crate::Array], [`Map`].
147///
148/// # Errors
149///
150/// Data types not supported by JSON serialize into formats that may invalidate the result.
151#[inline]
152#[must_use]
153pub fn format_map_as_json(map: &Map) -> String {
154    let mut result = String::from('{');
155
156    for (key, value) in map {
157        if result.len() > 1 {
158            result += ",";
159        }
160
161        write!(result, "{key:?}").unwrap();
162        result += ":";
163
164        format_dynamic_as_json(&mut result, value);
165    }
166
167    result += "}";
168
169    result
170}
171
172/// Format a [`Dynamic`] value as JSON.
173fn format_dynamic_as_json(result: &mut String, value: &Dynamic) {
174    match value.0 {
175        Union::Unit(..) => *result += "null",
176        Union::FnPtr(ref f, _, _) if f.is_curried() => {
177            *result += "[";
178            write!(result, "{:?}", f.fn_name()).unwrap();
179            f.iter_curry().for_each(|value| {
180                *result += ",";
181                format_dynamic_as_json(result, value);
182            });
183            *result += "]";
184        }
185        Union::FnPtr(ref f, _, _) => write!(result, "{:?}", f.fn_name()).unwrap(),
186        Union::Map(ref m, ..) => *result += &format_map_as_json(m),
187        #[cfg(not(feature = "no_index"))]
188        Union::Array(ref a, _, _) => {
189            *result += "[";
190            for (i, x) in a.iter().enumerate() {
191                if i > 0 {
192                    *result += ",";
193                }
194                format_dynamic_as_json(result, x);
195            }
196            *result += "]";
197        }
198        #[cfg(not(feature = "no_index"))]
199        Union::Blob(ref b, _, _) => {
200            *result += "[";
201            for (i, x) in b.iter().enumerate() {
202                if i > 0 {
203                    *result += ",";
204                }
205                write!(result, "{x}").unwrap();
206            }
207            *result += "]";
208        }
209        #[cfg(not(feature = "no_closure"))]
210        Union::Shared(ref v, _, _) => {
211            let value = &*crate::func::locked_read(v).unwrap();
212            format_dynamic_as_json(result, value)
213        }
214        _ => write!(result, "{value:?}").unwrap(),
215    }
216}