Serialization and Deserialization of Dynamic with serde

Rhai’s Dynamic type supports serialization and deserialization by serde via the serde feature.

Dynamic works both as a serialization format as well as a data type that is serializable.

Serialize/Deserialize a Dynamic

With the serde feature turned on, Dynamic implements serde::Serialize and serde::Deserialize, so it can easily be serialized and deserialized with serde.


#![allow(unused)]
fn main() {
let value: Dynamic = ...;

// Serialize 'Dynamic' to JSON
let json = serde_json::to_string(&value);

// Deserialize 'Dynamic' from JSON
let result: Dynamic = serde_json::from_str(&json);
}

Custom types are serialized as text strings of the value’s type name.

Dynamic as Serialization Format

A Dynamic can be seamlessly converted to and from any type that implements serde::Serialize and/or serde::Deserialize, acting as a serialization format.

Serialize Any Type to Dynamic

The function rhai::serde::to_dynamic automatically converts any Rust type that implements serde::Serialize into a Dynamic.

For primary types, this is usually not necessary because using Dynamic::from is much easier and is essentially the same thing. The only difference is treatment for integer values. Dynamic::from keeps different integer types intact, while rhai::serde::to_dynamic converts them all into INT (i.e. the system integer type which is i64 or i32 depending on the only_i32 feature).

Rust struct‘s (or any type that is marked as a serde map) are converted into object maps while Rust Vec‘s (or any type that is marked as a serde sequence) are converted into arrays.

While it is also simple to serialize a Rust type to JSON via serde, then use Engine::parse_json to convert it into an object map, rhai::serde::to_dynamic serializes it to Dynamic directly via serde without going through the JSON step.


#![allow(unused)]
fn main() {
use rhai::{Dynamic, Map};
use rhai::serde::to_dynamic;

#[derive(Debug, serde::Serialize)]
struct Point {
    x: f64,
    y: f64
}

#[derive(Debug, serde::Serialize)]
struct MyStruct {
    a: i64,
    b: Vec<String>,
    c: bool,
    d: Point
}

let x = MyStruct {
    a: 42,
    b: vec![ "hello".into(), "world".into() ],
    c: true,
    d: Point { x: 123.456, y: 999.0 }
};

// Convert the 'MyStruct' into a 'Dynamic'
let map: Dynamic = to_dynamic(x);

map.is::<Map>() == true;
}

Deserialize a Dynamic into Any Type

The function rhai::serde::from_dynamic automatically converts a Dynamic value into any Rust type that implements serde::Deserialize.

In particular, object maps are converted into Rust struct‘s (or any type that is marked as a serde map) while arrays are converted into Rust Vec‘s (or any type that is marked as a serde sequence).


#![allow(unused)]
fn main() {
use rhai::{Engine, Dynamic};
use rhai::serde::from_dynamic;

#[derive(Debug, serde::Deserialize)]
struct Point {
    x: f64,
    y: f64
}

#[derive(Debug, serde::Deserialize)]
struct MyStruct {
    a: i64,
    b: Vec<String>,
    c: bool,
    d: Point
}

let engine = Engine::new();

let result: Dynamic = engine.eval(r#"
            #{
                a: 42,
                b: [ "hello", "world" ],
                c: true,
                d: #{ x: 123.456, y: 999.0 }
            }
        "#)?;

// Convert the 'Dynamic' object map into 'MyStruct'
let x: MyStruct = from_dynamic(&result)?;
}

Cannot Deserialize Shared Values

A Dynamic containing a shared value cannot be deserialized – i.e. it will give a type error.

Use Dynamic::flatten to obtain a cloned copy before deserialization (if the value is not shared, it is simply returned and not cloned).

Shared values are turned off via the no_closure feature.

Lighter Alternative

The serde crate is quite heavy.

If only simple JSON parsing (i.e. only deserialization) of a hash object into a Rhai object map is required, the Engine::parse_json method is available as a cheap alternative, but it does not provide the same level of correctness, nor are there any configurable options.