mirror of https://github.com/boa-dev/boa.git
Browse Source
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
4 changed files with 223 additions and 53 deletions
@ -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…
Reference in new issue