Browse Source

Added conversions from and to serde_json's Value type (#1851)

This Pull Request closes #1693.

It changes the following:

- It adds a fallible conversion from `serde_json::Value` to `JsValue`, which requires a context.
- It adds a fallible conversion from `JsValue` to `serde_json::Value`, which requires a context.
- Added examples to the documentation of both methods.
- Removed some duplicate and non-needed code that I found while doing this.


Co-authored-by: RageKnify <RageKnify@gmail.com>
pull/1859/head
Iban Eguia 3 years ago
parent
commit
8c92a8fa25
  1. 54
      boa_engine/src/object/mod.rs
  2. 10
      boa_engine/src/value/conversions.rs
  3. 1
      boa_engine/src/value/mod.rs
  4. 211
      boa_engine/src/value/serde_json.rs

54
boa_engine/src/object/mod.rs

@ -456,17 +456,6 @@ impl Object {
)
}
#[inline]
pub fn as_array(&self) -> Option<()> {
match self.data {
ObjectData {
kind: ObjectKind::Array,
..
} => Some(()),
_ => None,
}
}
/// Checks if it is an `ArrayIterator` object.
#[inline]
pub fn is_array_iterator(&self) -> bool {
@ -793,17 +782,6 @@ impl Object {
)
}
#[inline]
pub fn as_error(&self) -> Option<()> {
match self.data {
ObjectData {
kind: ObjectKind::Error,
..
} => Some(()),
_ => None,
}
}
/// Checks if it a Boolean object.
#[inline]
pub fn is_boolean(&self) -> bool {
@ -1159,7 +1137,10 @@ impl Object {
&self.properties
}
/// Helper function for property insertion.
/// Inserts a field in the object `properties` without checking if it's writable.
///
/// If a field was already in the object with the same name, then a `Some` is returned
/// with that field's value, otherwise, `None` is returned.
#[inline]
pub(crate) fn insert<K, P>(&mut self, key: K, property: P) -> Option<PropertyDescriptor>
where
@ -1174,19 +1155,6 @@ impl Object {
pub(crate) fn remove(&mut self, key: &PropertyKey) -> Option<PropertyDescriptor> {
self.properties.remove(key)
}
/// Inserts a field in the object `properties` without checking if it's writable.
///
/// If a field was already in the object with the same name that a `Some` is returned
/// with that field, otherwise None is returned.
#[inline]
pub fn insert_property<K, P>(&mut self, key: K, property: P) -> Option<PropertyDescriptor>
where
K: Into<PropertyKey>,
P: Into<PropertyDescriptor>,
{
self.insert(key, property)
}
}
/// The functions binding.
@ -1406,8 +1374,8 @@ impl<'context> FunctionBuilder<'context> {
.writable(false)
.enumerable(false)
.configurable(true);
object.insert_property("name", property.clone().value(self.name.clone()));
object.insert_property("length", property.value(self.length));
object.insert("name", property.clone().value(self.name.clone()));
object.insert("length", property.value(self.length));
}
}
@ -1473,7 +1441,7 @@ impl<'context> ObjectInitializer<'context> {
.constructor(false)
.build();
self.object.borrow_mut().insert_property(
self.object.borrow_mut().insert(
binding.binding,
PropertyDescriptor::builder()
.value(function)
@ -1595,7 +1563,7 @@ impl<'context> ConstructorBuilder<'context> {
.constructor(false)
.build();
self.prototype.borrow_mut().insert_property(
self.prototype.borrow_mut().insert(
binding.binding,
PropertyDescriptor::builder()
.value(function)
@ -1624,7 +1592,7 @@ impl<'context> ConstructorBuilder<'context> {
.constructor(false)
.build();
self.constructor_object.borrow_mut().insert_property(
self.constructor_object.borrow_mut().insert(
binding.binding,
PropertyDescriptor::builder()
.value(function)
@ -1842,7 +1810,7 @@ impl<'context> ConstructorBuilder<'context> {
}
if self.constructor_has_prototype {
constructor.insert_property(
constructor.insert(
PROTOTYPE,
PropertyDescriptor::builder()
.value(self.prototype.clone())
@ -1855,7 +1823,7 @@ impl<'context> ConstructorBuilder<'context> {
{
let mut prototype = self.prototype.borrow_mut();
prototype.insert_property(
prototype.insert(
"constructor",
PropertyDescriptor::builder()
.value(self.constructor_object.clone())

10
boa_engine/src/value/conversions.rs

@ -127,16 +127,6 @@ impl From<JsObject> for JsValue {
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct TryFromObjectError;
impl Display for TryFromObjectError {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Could not convert value to an Object type")
}
}
impl From<()> for JsValue {
#[inline]
fn from(_: ()) -> Self {

1
boa_engine/src/value/mod.rs

@ -33,6 +33,7 @@ pub(crate) mod display;
mod equality;
mod hash;
mod operations;
mod serde_json;
mod r#type;
pub use conversions::*;

211
boa_engine/src/value/serde_json.rs

@ -0,0 +1,211 @@
//! This module implements the conversions from and into [`serde_json::Value`].
use super::JsValue;
use crate::{
builtins::Array,
property::{PropertyDescriptor, PropertyKey},
Context, JsResult,
};
use serde_json::{Map, Value};
impl JsValue {
/// Converts a [`serde_json::Value`] to a `JsValue`.
///
/// # Example
///
/// ```
/// use boa_engine::{Context, JsValue};
///
/// let data = r#"
/// {
/// "name": "John Doe",
/// "age": 43,
/// "phones": [
/// "+44 1234567",
/// "+44 2345678"
/// ]
/// }"#;
///
/// let json: serde_json::Value = serde_json::from_str(data).unwrap();
///
/// let mut context = Context::default();
/// let value = JsValue::from_json(&json, &mut context).unwrap();
/// #
/// # assert_eq!(json, value.to_json(&mut context).unwrap());
/// ```
pub fn from_json(json: &Value, context: &mut Context) -> JsResult<Self> {
/// Biggest possible integer, as i64.
const MAX_INT: i64 = i32::MAX as i64;
/// Smallest possible integer, as i64.
const MIN_INT: i64 = i32::MIN as i64;
match json {
Value::Null => Ok(Self::Null),
Value::Bool(b) => Ok(Self::Boolean(*b)),
Value::Number(num) => num
.as_i64()
.filter(|n| (MIN_INT..=MAX_INT).contains(n))
.map(|i| Self::Integer(i as i32))
.or_else(|| num.as_f64().map(Self::Rational))
.ok_or_else(|| {
context.construct_type_error(format!(
"could not convert JSON number {num} to JsValue"
))
}),
Value::String(string) => Ok(Self::from(string.as_str())),
Value::Array(vec) => {
let mut arr = Vec::with_capacity(vec.len());
for val in vec {
arr.push(Self::from_json(val, context)?);
}
Ok(Array::create_array_from_list(arr, context).into())
}
Value::Object(obj) => {
let js_obj = context.construct_object();
for (key, value) in obj {
let property = PropertyDescriptor::builder()
.value(Self::from_json(value, context)?)
.writable(true)
.enumerable(true)
.configurable(true);
js_obj.borrow_mut().insert(key.as_str(), property);
}
Ok(js_obj.into())
}
}
}
/// Converts the `JsValue` to a [`serde_json::Value`].
///
/// # Example
///
/// ```
/// use boa_engine::{Context, JsValue};
///
/// let data = r#"
/// {
/// "name": "John Doe",
/// "age": 43,
/// "phones": [
/// "+44 1234567",
/// "+44 2345678"
/// ]
/// }"#;
///
/// let json: serde_json::Value = serde_json::from_str(data).unwrap();
///
/// let mut context = Context::default();
/// let value = JsValue::from_json(&json, &mut context).unwrap();
///
/// let back_to_json = value.to_json(&mut context).unwrap();
/// #
/// # assert_eq!(json, back_to_json);
/// ```
pub fn to_json(&self, context: &mut Context) -> JsResult<Value> {
match self {
Self::Null => Ok(Value::Null),
Self::Undefined => todo!("undefined to JSON"),
&Self::Boolean(b) => Ok(b.into()),
Self::String(string) => Ok(string.as_str().into()),
&Self::Rational(rat) => Ok(rat.into()),
&Self::Integer(int) => Ok(int.into()),
Self::BigInt(_bigint) => context.throw_type_error("cannot convert bigint to JSON"),
Self::Object(obj) => {
if obj.is_array() {
let len = obj.length_of_array_like(context)?;
let mut arr = Vec::with_capacity(len);
let obj = obj.borrow();
for k in 0..len as u32 {
let val = obj.properties().get(&k.into()).map_or(Self::Null, |desc| {
desc.value().cloned().unwrap_or(Self::Null)
});
arr.push(val.to_json(context)?);
}
Ok(Value::Array(arr))
} else {
let mut map = Map::new();
for (key, property) in obj.borrow().properties().iter() {
let key = match &key {
PropertyKey::String(string) => string.as_str().to_owned(),
PropertyKey::Index(i) => i.to_string(),
PropertyKey::Symbol(_sym) => {
return context.throw_type_error("cannot convert Symbol to JSON")
}
};
let value = match property.value() {
Some(val) => val.to_json(context)?,
None => Value::Null,
};
map.insert(key, value);
}
Ok(Value::Object(map))
}
}
Self::Symbol(_sym) => context.throw_type_error("cannot convert Symbol to JSON"),
}
}
}
#[cfg(test)]
mod tests {
use crate::object::JsArray;
#[test]
fn ut_json_conversions() {
use crate::{Context, JsValue};
let data = r#"
{
"name": "John Doe",
"age": 43,
"minor": false,
"adult": true,
"extra": {
"address": null
},
"phones": [
"+44 1234567",
-45,
{},
true
]
}"#;
let json: serde_json::Value = serde_json::from_str(data).unwrap();
assert!(json.is_object());
let mut context = Context::default();
let value = JsValue::from_json(&json, &mut context).unwrap();
let obj = value.as_object().unwrap();
assert_eq!(obj.get("name", &mut context).unwrap(), "John Doe".into());
assert_eq!(obj.get("age", &mut context).unwrap(), 43_i32.into());
assert_eq!(obj.get("minor", &mut context).unwrap(), false.into());
assert_eq!(obj.get("adult", &mut context).unwrap(), true.into());
{
let extra = obj.get("extra", &mut context).unwrap();
let extra = extra.as_object().unwrap();
assert!(extra.get("address", &mut context).unwrap().is_null());
}
{
let phones = obj.get("phones", &mut context).unwrap();
let phones = phones.as_object().unwrap();
let arr = JsArray::from_object(phones.clone(), &mut context).unwrap();
assert_eq!(arr.at(0, &mut context).unwrap(), "+44 1234567".into());
assert_eq!(arr.at(1, &mut context).unwrap(), JsValue::from(-45_i32));
assert!(arr.at(2, &mut context).unwrap().is_object());
assert_eq!(arr.at(3, &mut context).unwrap(), true.into());
}
assert_eq!(json, value.to_json(&mut context).unwrap());
}
}
Loading…
Cancel
Save