Add support for deserialization of custom type/shared value from into another Dynamic.

This commit is contained in:
Stephen Chung 2023-04-28 23:16:00 +08:00
parent 72cfd42ab7
commit ae59a3321b
6 changed files with 65 additions and 19 deletions

View File

@ -28,6 +28,7 @@ Enhancements
------------ ------------
* `Engine::is_symbol_disabled` is added to test whether a particular keyword/symbol is disabled. * `Engine::is_symbol_disabled` is added to test whether a particular keyword/symbol is disabled.
* Support is added to deserialize a `Dynamic` value containing custom types or shared values back into another `Dynamic` (essentially a straight cloned copy).
Version 1.13.0 Version 1.13.0

View File

@ -57,7 +57,7 @@ pub fn by_value<T: Variant + Clone>(data: &mut Dynamic) -> T {
} }
if TypeId::of::<T>() == TypeId::of::<String>() { if TypeId::of::<T>() == TypeId::of::<String>() {
// If T is `String`, data must be `ImmutableString`, so map directly to it // If T is `String`, data must be `ImmutableString`, so map directly to it
return reify! { data.take().into_string().expect("`ImmutableString`") => T }; return reify! { data.take().into_string().expect("`ImmutableString`") => !!! T };
} }
// We consume the argument and then replace it with () - the argument is not supposed to be used again. // We consume the argument and then replace it with () - the argument is not supposed to be used again.

View File

@ -8,6 +8,8 @@
/// * `reify! { `_variable_ or _expression_` => |`_temp-variable_`: `_type_`|` _code_ `)` /// * `reify! { `_variable_ or _expression_` => |`_temp-variable_`: `_type_`|` _code_ `)`
/// * `reify! { `_variable_ or _expression_ `=>` `Option<`_type_`>` `)` /// * `reify! { `_variable_ or _expression_ `=>` `Option<`_type_`>` `)`
/// * `reify! { `_variable_ or _expression_ `=>` _type_ `)` /// * `reify! { `_variable_ or _expression_ `=>` _type_ `)`
///
/// * `reify! { `_expression_ `=> !!!` _type_ `)` (unsafe, no type checks!)
macro_rules! reify { macro_rules! reify {
($old:ident => |$new:ident : $t:ty| $code:expr, || $fallback:expr) => {{ ($old:ident => |$new:ident : $t:ty| $code:expr, || $fallback:expr) => {{
#[allow(clippy::redundant_else)] #[allow(clippy::redundant_else)]
@ -45,4 +47,11 @@ macro_rules! reify {
($old:expr => $t:ty) => { ($old:expr => $t:ty) => {
reify! { $old => |v: $t| v, || unreachable!() } reify! { $old => |v: $t| v, || unreachable!() }
}; };
($old:expr => !!! $t:ty) => {{
let old_value = $old;
let new_value: $t =
unsafe { std::mem::transmute_copy(&std::mem::ManuallyDrop::new(old_value)) };
new_value
}};
} }

View File

@ -124,6 +124,10 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> {
type Error = RhaiError; type Error = RhaiError;
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> RhaiResultOf<V::Value> { fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> RhaiResultOf<V::Value> {
if type_name::<V::Value>() == type_name::<Dynamic>() {
return Ok(reify! { self.0.clone() => !!! V::Value });
}
match self.0 .0 { match self.0 .0 {
Union::Unit(..) => self.deserialize_unit(visitor), Union::Unit(..) => self.deserialize_unit(visitor),
Union::Bool(..) => self.deserialize_bool(visitor), Union::Bool(..) => self.deserialize_bool(visitor),

View File

@ -1184,89 +1184,89 @@ impl Dynamic {
self.flatten_in_place(); self.flatten_in_place();
if TypeId::of::<T>() == TypeId::of::<Self>() { if TypeId::of::<T>() == TypeId::of::<Self>() {
return Some(reify! { self => T }); return Some(reify! { self => !!! T });
} }
if TypeId::of::<T>() == TypeId::of::<()>() { if TypeId::of::<T>() == TypeId::of::<()>() {
return match self.0 { return match self.0 {
Union::Unit(..) => Some(reify! { () => T }), Union::Unit(..) => Some(reify! { () => !!! T }),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<INT>() { if TypeId::of::<T>() == TypeId::of::<INT>() {
return match self.0 { return match self.0 {
Union::Int(n, ..) => Some(reify! { n => T }), Union::Int(n, ..) => Some(reify! { n => !!! T }),
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
if TypeId::of::<T>() == TypeId::of::<crate::FLOAT>() { if TypeId::of::<T>() == TypeId::of::<crate::FLOAT>() {
return match self.0 { return match self.0 {
Union::Float(v, ..) => Some(reify! { *v => T }), Union::Float(v, ..) => Some(reify! { *v => !!! T }),
_ => None, _ => None,
}; };
} }
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
if TypeId::of::<T>() == TypeId::of::<rust_decimal::Decimal>() { if TypeId::of::<T>() == TypeId::of::<rust_decimal::Decimal>() {
return match self.0 { return match self.0 {
Union::Decimal(v, ..) => Some(reify! { *v => T }), Union::Decimal(v, ..) => Some(reify! { *v => !!! T }),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<bool>() { if TypeId::of::<T>() == TypeId::of::<bool>() {
return match self.0 { return match self.0 {
Union::Bool(b, ..) => Some(reify! { b => T }), Union::Bool(b, ..) => Some(reify! { b => !!! T }),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<ImmutableString>() { if TypeId::of::<T>() == TypeId::of::<ImmutableString>() {
return match self.0 { return match self.0 {
Union::Str(s, ..) => Some(reify! { s => T }), Union::Str(s, ..) => Some(reify! { s => !!! T }),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<String>() { if TypeId::of::<T>() == TypeId::of::<String>() {
return match self.0 { return match self.0 {
Union::Str(s, ..) => Some(reify! { s.to_string() => T }), Union::Str(s, ..) => Some(reify! { s.to_string() => !!! T }),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<char>() { if TypeId::of::<T>() == TypeId::of::<char>() {
return match self.0 { return match self.0 {
Union::Char(c, ..) => Some(reify! { c => T }), Union::Char(c, ..) => Some(reify! { c => !!! T }),
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
if TypeId::of::<T>() == TypeId::of::<crate::Array>() { if TypeId::of::<T>() == TypeId::of::<crate::Array>() {
return match self.0 { return match self.0 {
Union::Array(a, ..) => Some(reify! { *a => T }), Union::Array(a, ..) => Some(reify! { *a => !!! T }),
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
if TypeId::of::<T>() == TypeId::of::<crate::Blob>() { if TypeId::of::<T>() == TypeId::of::<crate::Blob>() {
return match self.0 { return match self.0 {
Union::Blob(b, ..) => Some(reify! { *b => T }), Union::Blob(b, ..) => Some(reify! { *b => !!! T }),
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
if TypeId::of::<T>() == TypeId::of::<crate::Map>() { if TypeId::of::<T>() == TypeId::of::<crate::Map>() {
return match self.0 { return match self.0 {
Union::Map(m, ..) => Some(reify! { *m => T }), Union::Map(m, ..) => Some(reify! { *m => !!! T }),
_ => None, _ => None,
}; };
} }
if TypeId::of::<T>() == TypeId::of::<FnPtr>() { if TypeId::of::<T>() == TypeId::of::<FnPtr>() {
return match self.0 { return match self.0 {
Union::FnPtr(f, ..) => Some(reify! { *f => T }), Union::FnPtr(f, ..) => Some(reify! { *f => !!! T }),
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_time"))] #[cfg(not(feature = "no_time"))]
if TypeId::of::<T>() == TypeId::of::<Instant>() { if TypeId::of::<T>() == TypeId::of::<Instant>() {
return match self.0 { return match self.0 {
Union::TimeStamp(t, ..) => Some(reify! { *t => T }), Union::TimeStamp(t, ..) => Some(reify! { *t => !!! T }),
_ => None, _ => None,
}; };
} }
@ -1306,7 +1306,7 @@ impl Dynamic {
pub fn cast<T: Any + Clone>(self) -> T { pub fn cast<T: Any + Clone>(self) -> T {
// Bail out early if the return type needs no cast // Bail out early if the return type needs no cast
if TypeId::of::<T>() == TypeId::of::<Self>() { if TypeId::of::<T>() == TypeId::of::<Self>() {
return reify! { self => T }; return reify! { self => !!! T };
} }
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
@ -2038,7 +2038,7 @@ impl Dynamic {
}) })
.collect(), .collect(),
Union::Blob(b, ..) if TypeId::of::<T>() == TypeId::of::<u8>() => { Union::Blob(b, ..) if TypeId::of::<T>() == TypeId::of::<u8>() => {
Ok(reify! { *b => Vec<T> }) Ok(reify! { *b => !!! Vec<T> })
} }
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(ref cell, ..) => { Union::Shared(ref cell, ..) => {
@ -2061,7 +2061,7 @@ impl Dynamic {
.collect() .collect()
} }
Union::Blob(ref b, ..) if TypeId::of::<T>() == TypeId::of::<u8>() => { Union::Blob(ref b, ..) if TypeId::of::<T>() == TypeId::of::<u8>() => {
Ok(reify! { b.clone() => Vec<T> }) Ok(reify! { b.clone() => !!! Vec<T> })
} }
_ => Err(cell.type_name()), _ => Err(cell.type_name()),
} }

View File

@ -4,8 +4,9 @@ use rhai::{
serde::{from_dynamic, to_dynamic}, serde::{from_dynamic, to_dynamic},
Dynamic, Engine, EvalAltResult, ImmutableString, Scope, INT, Dynamic, Engine, EvalAltResult, ImmutableString, Scope, INT,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
use serde_json::json; use serde_json::json;
use std::sync::Arc;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use rhai::Array; use rhai::Array;
@ -383,6 +384,37 @@ fn test_serde_de_primary_types() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[cfg(not(feature = "no_object"))]
#[test]
fn test_serde_de_variants() -> Result<(), Box<EvalAltResult>> {
#[derive(Debug)]
struct Foo;
#[derive(Debug, Deserialize)]
struct Bar {
#[serde(deserialize_with = "deserialize_foo")]
value: Arc<Foo>,
}
fn deserialize_foo<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Arc<Foo>, D::Error> {
let value = <Dynamic as Deserialize>::deserialize(deserializer)?;
value
.try_cast::<Arc<Foo>>()
.ok_or_else(|| serde::de::Error::custom("type error"))
}
let value = Arc::new(Foo);
let mut map = Map::new();
map.insert("value".into(), Dynamic::from(value.clone()));
let x = Dynamic::from(map);
let bar = from_dynamic::<Bar>(&x)?;
assert!(Arc::ptr_eq(&bar.value, &value));
Ok(())
}
#[test] #[test]
fn test_serde_de_integer_types() -> Result<(), Box<EvalAltResult>> { fn test_serde_de_integer_types() -> Result<(), Box<EvalAltResult>> {
assert_eq!(42, from_dynamic::<i8>(&Dynamic::from(42 as INT))?); assert_eq!(42, from_dynamic::<i8>(&Dynamic::from(42 as INT))?);