rhai/src/api/json.rs

181 lines
6.4 KiB
Rust
Raw Normal View History

2022-04-21 06:15:21 +02:00
//! Module that defines JSON manipulation functions for [`Engine`].
#![cfg(not(feature = "no_object"))]
2022-11-24 09:05:23 +01:00
use crate::func::native::locked_write;
2022-11-23 04:36:30 +01:00
use crate::parser::{ParseSettingFlags, ParseState};
2022-04-21 10:01:20 +02:00
use crate::tokenizer::Token;
2022-11-23 10:23:54 +01:00
use crate::{Engine, LexError, Map, OptimizationLevel, RhaiResultOf, Scope};
2022-04-21 10:01:20 +02:00
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
2022-04-21 06:15:21 +02:00
impl Engine {
/// Parse a JSON string into an [object map][Map].
///
/// This is a light-weight alternative to using, say, [`serde_json`](https://crates.io/crates/serde_json)
/// to deserialize the JSON.
///
/// Not available under `no_object`.
///
/// The JSON string must be an object hash. It cannot be a simple primitive value.
///
/// Set `has_null` to `true` in order to map `null` values to `()`.
/// Setting it to `false` causes a syntax error for any `null` value.
///
/// JSON sub-objects are handled transparently.
///
/// This function can be used together with [`format_map_as_json`] to work with JSON texts
2022-09-29 16:46:59 +02:00
/// without using the [`serde_json`](https://crates.io/crates/serde_json) crate (which is heavy).
2022-04-21 06:15:21 +02:00
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// use rhai::{Engine, Map};
///
/// let engine = Engine::new();
///
/// let map = engine.parse_json(r#"
/// {
/// "a": 123,
/// "b": 42,
/// "c": {
/// "x": false,
/// "y": true,
/// "z": '$'
/// },
/// "d": null
/// }"#, true)?;
///
/// assert_eq!(map.len(), 4);
/// assert_eq!(map["a"].as_int().expect("a should exist"), 123);
/// assert_eq!(map["b"].as_int().expect("b should exist"), 42);
/// assert_eq!(map["d"].as_unit().expect("d should exist"), ());
///
/// let c = map["c"].read_lock::<Map>().expect("c should exist");
/// assert_eq!(c["x"].as_bool().expect("x should be bool"), false);
/// assert_eq!(c["y"].as_bool().expect("y should be bool"), true);
/// assert_eq!(c["z"].as_char().expect("z should be char"), '$');
/// # Ok(())
/// # }
/// ```
2022-04-21 10:01:20 +02:00
#[inline]
2022-04-21 06:15:21 +02:00
pub fn parse_json(&self, json: impl AsRef<str>, has_null: bool) -> RhaiResultOf<Map> {
let scripts = [json.as_ref()];
let (stream, tokenizer_control) = self.lex_raw(
&scripts,
2022-11-28 16:24:22 +01:00
Some(if has_null {
&|token, _, _| {
2022-04-21 06:15:21 +02:00
match token {
// `null` => `()`
Token::Reserved(s) if &*s == "null" => Token::Unit,
// `{` => `#{`
Token::LeftBrace => Token::MapStart,
// Disallowed syntax
t @ (Token::Unit | Token::MapStart) => Token::LexError(
2022-08-21 11:35:44 +02:00
LexError::ImproperSymbol(t.literal_syntax().to_string(), String::new())
.into(),
2022-04-21 06:15:21 +02:00
),
Token::InterpolatedString(..) => Token::LexError(
LexError::ImproperSymbol(
"interpolated string".to_string(),
2022-08-21 11:35:44 +02:00
String::new(),
2022-04-21 06:15:21 +02:00
)
.into(),
),
// All others
_ => token,
}
2022-11-28 16:24:22 +01:00
}
2022-04-21 06:15:21 +02:00
} else {
2022-11-28 16:24:22 +01:00
&|token, _, _| {
2022-04-21 06:15:21 +02:00
match token {
Token::Reserved(s) if &*s == "null" => Token::LexError(
2022-08-21 11:35:44 +02:00
LexError::ImproperSymbol("null".to_string(), String::new()).into(),
2022-04-21 06:15:21 +02:00
),
// `{` => `#{`
Token::LeftBrace => Token::MapStart,
// Disallowed syntax
t @ (Token::Unit | Token::MapStart) => Token::LexError(
2022-11-28 16:24:22 +01:00
LexError::ImproperSymbol(t.literal_syntax().to_string(), String::new())
.into(),
2022-04-21 06:15:21 +02:00
),
Token::InterpolatedString(..) => Token::LexError(
LexError::ImproperSymbol(
"interpolated string".to_string(),
2022-11-28 16:24:22 +01:00
String::new(),
2022-04-21 06:15:21 +02:00
)
.into(),
),
// All others
_ => token,
}
2022-11-28 16:24:22 +01:00
}
}),
2022-04-21 06:15:21 +02:00
);
2022-11-24 08:10:17 +01:00
let ast = {
let scope = Scope::new();
2022-11-24 09:05:23 +01:00
let interned_strings = &mut *locked_write(&self.interned_strings);
2022-11-25 13:42:16 +01:00
let state = &mut ParseState::new(&scope, interned_strings, tokenizer_control);
2022-04-21 06:15:21 +02:00
2022-11-24 08:10:17 +01:00
self.parse_global_expr(
2022-11-25 13:42:16 +01:00
stream.peekable(),
state,
2022-11-24 08:10:17 +01:00
|s| s.flags |= ParseSettingFlags::DISALLOW_UNQUOTED_MAP_PROPERTIES,
#[cfg(not(feature = "no_optimize"))]
OptimizationLevel::None,
#[cfg(feature = "no_optimize")]
OptimizationLevel::default(),
)?
};
2022-04-21 06:15:21 +02:00
self.eval_ast(&ast)
}
}
/// Return the JSON representation of an [object map][Map].
///
2022-04-21 10:01:20 +02:00
/// Not available under `no_std`.
///
2022-04-21 06:15:21 +02:00
/// This function can be used together with [`Engine::parse_json`] to work with JSON texts
2022-09-29 16:46:59 +02:00
/// without using the [`serde_json`](https://crates.io/crates/serde_json) crate (which is heavy).
2022-04-21 06:15:21 +02:00
///
/// # Data types
///
/// Only the following data types should be kept inside the object map: [`INT`][crate::INT],
/// [`FLOAT`][crate::FLOAT], [`ImmutableString`][crate::ImmutableString], `char`, `bool`, `()`,
/// [`Array`][crate::Array], [`Map`].
///
/// # Errors
///
/// Data types not supported by JSON serialize into formats that may invalidate the result.
2022-04-21 10:01:20 +02:00
#[inline]
2022-07-27 12:04:59 +02:00
#[must_use]
2022-04-21 06:15:21 +02:00
pub fn format_map_as_json(map: &Map) -> String {
let mut result = String::from('{');
for (key, value) in map {
2022-05-07 10:29:20 +02:00
use std::fmt::Write;
2022-04-21 06:15:21 +02:00
if result.len() > 1 {
result.push(',');
}
2022-05-07 10:29:20 +02:00
write!(result, "{:?}", key).unwrap();
2022-04-21 06:15:21 +02:00
result.push(':');
if let Some(val) = value.read_lock::<Map>() {
2022-11-23 06:24:14 +01:00
result.push_str(&format_map_as_json(&val));
2022-11-09 05:44:57 +01:00
} else if value.is_unit() {
2022-04-21 06:15:21 +02:00
result.push_str("null");
} else {
2022-05-07 10:29:20 +02:00
write!(result, "{:?}", value).unwrap();
2022-04-21 06:15:21 +02:00
}
}
result.push('}');
result
}