Treat leading #{ in Engine::parse_json.

This commit is contained in:
Stephen Chung 2020-08-18 22:01:13 +08:00
parent f9807a3c1e
commit e3f2157c6a
3 changed files with 54 additions and 10 deletions

View File

@ -9,6 +9,7 @@ New features
* Adds `Engine::register_get_result`, `Engine::register_set_result`, `Engine::register_indexer_get_result`, `Engine::register_indexer_set_result` API.
* Adds `Module::combine` to combine two modules.
* `Engine::parse_json` now also accepts a JSON object starting with `#{`.
Version 0.18.1

View File

@ -7,7 +7,7 @@ The syntax for an [object map] is extremely similar to JSON, with the exception
technically be mapped to [`()`]. A valid JSON string does not start with a hash character `#` while a
Rhai [object map] does - that's the major difference!
Use the `Engine::parse_json` method to parse a piece of JSON into an object map:
Use the `Engine::parse_json` method to parse a piece of _simple_ JSON into an object map:
```rust
// JSON string - notice that JSON property names are always quoted
@ -26,7 +26,7 @@ let json = r#"{
// Set the second boolean parameter to true in order to map 'null' to '()'
let map = engine.parse_json(json, true)?;
map.len() == 6; // 'map' contains all properties in the JSON string
map.len() == 6; // 'map' contains all properties in the JSON string
// Put the object map into a 'Scope'
let mut scope = Scope::new();
@ -34,7 +34,7 @@ scope.push("map", map);
let result = engine.eval_with_scope::<INT>(r#"map["^^^!!!"].len()"#)?;
result == 3; // the object map is successfully used in the script
result == 3; // the object map is successfully used in the script
```
Representation of Numbers
@ -45,3 +45,28 @@ the [`no_float`] feature is not used. Most common generators of JSON data disti
integer and floating-point values by always serializing a floating-point number with a decimal point
(i.e. `123.0` instead of `123` which is assumed to be an integer). This style can be used successfully
with Rhai [object maps].
Parse JSON with Sub-Objects
--------------------------
`Engine::parse_json` depends on the fact that the [object map] literal syntax in Rhai is _almost_
the same as a JSON object. However, it is _almost_ because the syntax for a sub-object in JSON
(i.e. "`{ ... }`") is different from a Rhai [object map] literal (i.e. "`#{ ... }`").
When `Engine::parse_json` encounters JSON with sub-objects, it fails with a syntax error.
If it is certain that no text string in the JSON will ever contain the character '`{`',
then it is possible to parse it by first replacing all occupance of '`{`' with "`#{`".
```rust
// JSON with sub-object 'b'.
let json = r#"{"a":1, "b":{"x":true, "y":false}}"#;
let new_json = json.replace("{" "#{");
// The leading '{' will also be replaced to '#{', but parse_json can handle this.
let map = engine.parse_json(&new_json, false)?;
map.len() == 2; // 'map' contains two properties: 'a' and 'b'
```

View File

@ -898,21 +898,34 @@ impl Engine {
/// Set `has_null` to `true` in order to map `null` values to `()`.
/// Setting it to `false` will cause a _variable not found_ error during parsing.
///
/// # JSON With Sub-Objects
///
/// This method assumes no sub-objects in the JSON string. That is because the syntax
/// of a JSON sub-object (or object hash), `{ .. }`, is different from Rhai's syntax, `#{ .. }`.
/// Parsing a JSON string with sub-objects will cause a syntax error.
///
/// If it is certain that the character `{` never appears in any text string within the JSON object,
/// then globally replace `{` with `#{` before calling this method.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// use rhai::Engine;
/// use rhai::{Engine, Map};
///
/// let engine = Engine::new();
///
/// let map = engine.parse_json(r#"{"a":123, "b":42, "c":false, "d":null}"#, true)?;
/// let map = engine.parse_json(
/// r#"{"a":123, "b":42, "c":{"x":false, "y":true}, "d":null}"#
/// .replace("{", "#{").as_str(), true)?;
///
/// assert_eq!(map.len(), 4);
/// assert_eq!(map.get("a").cloned().unwrap().cast::<i64>(), 123);
/// assert_eq!(map.get("b").cloned().unwrap().cast::<i64>(), 42);
/// assert_eq!(map.get("c").cloned().unwrap().cast::<bool>(), false);
/// assert_eq!(map.get("d").cloned().unwrap().cast::<()>(), ());
/// assert_eq!(map["a"].as_int().unwrap(), 123);
/// assert_eq!(map["b"].as_int().unwrap(), 42);
/// assert!(map["d"].is::<()>());
///
/// let c = map["c"].read_lock::<Map>().unwrap();
/// assert_eq!(c["x"].as_bool().unwrap(), false);
/// # Ok(())
/// # }
/// ```
@ -921,7 +934,12 @@ impl Engine {
let mut scope = Scope::new();
// Trims the JSON string and add a '#' in front
let scripts = ["#", json.trim()];
let json = json.trim();
let scripts = if json.starts_with(Token::MapStart.syntax().as_ref()) {
[json, ""]
} else {
["#", json]
};
let stream = lex(
&scripts,
if has_null {