diff --git a/core/engine/src/value/conversions/try_from_js.rs b/core/engine/src/value/conversions/try_from_js.rs index a4b5a465606..dc860a33496 100644 --- a/core/engine/src/value/conversions/try_from_js.rs +++ b/core/engine/src/value/conversions/try_from_js.rs @@ -4,6 +4,8 @@ use num_bigint::BigInt; use crate::{js_string, Context, JsBigInt, JsNativeError, JsObject, JsResult, JsString, JsValue}; +mod collections; + /// This trait adds a fallible and efficient conversions from a [`JsValue`] to Rust types. pub trait TryFromJs: Sized { /// This function tries to convert a JavaScript value into `Self`. @@ -364,3 +366,48 @@ fn value_into_vec() { ), ]); } + +#[test] +fn value_into_map() { + use boa_engine::{run_test_actions, TestAction}; + use indoc::indoc; + + run_test_actions([ + TestAction::assert_with_op(indoc! {r#" ({ a: 1, b: 2, c: 3 }) "#}, |value, context| { + let value = std::collections::BTreeMap::::try_from_js(&value, context); + + match value { + Ok(value) => { + value + == vec![ + ("a".to_string(), 1), + ("b".to_string(), 2), + ("c".to_string(), 3), + ] + .into_iter() + .collect::>() + } + _ => false, + } + }), + TestAction::assert_with_op(indoc! {r#" ({ a: 1, b: 2, c: 3 }) "#}, |value, context| { + let value = std::collections::HashMap::::try_from_js(&value, context); + + match value { + Ok(value) => { + value + == std::collections::HashMap::from_iter( + vec![ + ("a".to_string(), 1), + ("b".to_string(), 2), + ("c".to_string(), 3), + ] + .into_iter() + .collect::>(), + ) + } + _ => false, + } + }), + ]); +} diff --git a/core/engine/src/value/conversions/try_from_js/collections.rs b/core/engine/src/value/conversions/try_from_js/collections.rs new file mode 100644 index 00000000000..37d649e9630 --- /dev/null +++ b/core/engine/src/value/conversions/try_from_js/collections.rs @@ -0,0 +1,64 @@ +//! [`JsValue`] conversions for std collections. + +use std::collections::{BTreeMap, HashMap}; +use std::hash::Hash; + +use crate::value::TryFromJs; +use crate::{Context, JsNativeError, JsResult, JsValue}; + +impl TryFromJs for BTreeMap +where + K: TryFromJs + Ord, + V: TryFromJs, +{ + fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult { + let JsValue::Object(object) = value else { + return Err(JsNativeError::typ() + .with_message("cannot convert value to a BTreeMap") + .into()); + }; + + let keys = object.__own_property_keys__(context)?; + + keys.into_iter() + .map(|key| { + let js_value = object.get(key.clone(), context)?; + let js_key: JsValue = key.into(); + + let key = K::try_from_js(&js_key, context)?; + let value = V::try_from_js(&js_value, context)?; + + Ok((key, value)) + }) + .collect() + } +} + +impl TryFromJs for HashMap +where + K: TryFromJs + Ord + Hash, + V: TryFromJs, + S: std::hash::BuildHasher + Default, +{ + fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult { + let JsValue::Object(object) = value else { + return Err(JsNativeError::typ() + .with_message("cannot convert value to a BTreeMap") + .into()); + }; + + let keys = object.__own_property_keys__(context)?; + + keys.into_iter() + .map(|key| { + let js_value = object.get(key.clone(), context)?; + let js_key: JsValue = key.into(); + + let key = K::try_from_js(&js_key, context)?; + let value = V::try_from_js(&js_value, context)?; + + Ok((key, value)) + }) + .collect() + } +}