Browse Source

Refactor `JSON.stringify` (#1495)

pull/1516/head
raskad 3 years ago committed by GitHub
parent
commit
3313ad6096
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 639
      boa/src/builtins/json/mod.rs
  2. 4
      boa/src/builtins/string/mod.rs
  3. 40
      boa/src/object/gcobject.rs
  4. 37
      boa/src/value/mod.rs

639
boa/src/builtins/json/mod.rs

@ -13,17 +13,20 @@
//! [json]: https://www.json.org/json-en.html
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON
use std::collections::HashSet;
use crate::{
builtins::BuiltIn,
object::Object,
object::ObjectInitializer,
property::{Attribute, PropertyDescriptor, PropertyKey},
builtins::{
string::{is_leading_surrogate, is_trailing_surrogate},
BuiltIn,
},
object::{JsObject, ObjectInitializer, RecursionLimiter},
property::{Attribute, PropertyKey, PropertyNameKind},
symbol::WellKnownSymbols,
value::IntegerOrInfinity,
BoaProfiler, Context, JsResult, JsValue,
BoaProfiler, Context, JsResult, JsString, JsValue,
};
use serde::Serialize;
use serde_json::{self, ser::PrettyFormatter, Serializer, Value as JSONValue};
use serde_json::{self, Value as JSONValue};
#[cfg(test)]
mod tests;
@ -128,7 +131,7 @@ impl Json {
///
/// This `JSON` method converts a JavaScript object or value to a JSON string.
///
/// This medhod optionally replaces values if a `replacer` function is specified or
/// This method optionally replaces values if a `replacer` function is specified or
/// optionally including only the specified properties if a replacer array is specified.
///
/// An optional `space` argument can be supplied of type `String` or `Number` that's used to insert
@ -145,133 +148,533 @@ impl Json {
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let object = match args.get(0) {
None => return Ok(JsValue::undefined()),
Some(obj) => obj,
};
const SPACE_INDENT: &str = " ";
let gap = if let Some(space) = args.get(2) {
let space = if let Some(space_obj) = space.as_object() {
if let Some(space) = space_obj.borrow().as_number() {
JsValue::new(space)
} else if let Some(space) = space_obj.borrow().as_string() {
JsValue::new(space)
} else {
space.clone()
}
} else {
space.clone()
};
if space.is_number() {
let space_mv = match space.to_integer_or_infinity(context)? {
IntegerOrInfinity::NegativeInfinity => 0,
IntegerOrInfinity::PositiveInfinity => 10,
IntegerOrInfinity::Integer(i) if i < 1 => 0,
IntegerOrInfinity::Integer(i) => std::cmp::min(i, 10) as usize,
};
JsValue::new(&SPACE_INDENT[..space_mv])
} else if let Some(string) = space.as_string() {
JsValue::new(&string[..std::cmp::min(string.len(), 10)])
// 1. Let stack be a new empty List.
let stack = Vec::new();
// 2. Let indent be the empty String.
let indent = JsString::new("");
// 3. Let PropertyList and ReplacerFunction be undefined.
let mut property_list = None;
let mut replacer_function = None;
let replacer = args.get(1).cloned().unwrap_or_default();
// 4. If Type(replacer) is Object, then
if let Some(replacer_obj) = replacer.as_object() {
// a. If IsCallable(replacer) is true, then
if replacer_obj.is_callable() {
// i. Set ReplacerFunction to replacer.
replacer_function = Some(replacer_obj)
// b. Else,
} else {
JsValue::new("")
// i. Let isArray be ? IsArray(replacer).
// ii. If isArray is true, then
if replacer_obj.is_array() {
// 1. Set PropertyList to a new empty List.
let mut property_set = HashSet::new();
// 2. Let len be ? LengthOfArrayLike(replacer).
let len = replacer_obj.length_of_array_like(context)?;
// 3. Let k be 0.
let mut k = 0;
// 4. Repeat, while k < len,
while k < len {
// a. Let prop be ! ToString(𝔽(k)).
// b. Let v be ? Get(replacer, prop).
let v = replacer_obj.get(k, context)?;
// c. Let item be undefined.
// d. If Type(v) is String, set item to v.
// e. Else if Type(v) is Number, set item to ! ToString(v).
// f. Else if Type(v) is Object, then
// g. If item is not undefined and item is not currently an element of PropertyList, then
// i. Append item to the end of PropertyList.
if let Some(s) = v.as_string() {
property_set.insert(s.to_owned());
} else if v.is_number() {
property_set.insert(
v.to_string(context)
.expect("ToString cannot fail on number value"),
);
} else if let Some(obj) = v.as_object() {
// i. If v has a [[StringData]] or [[NumberData]] internal slot, set item to ? ToString(v).
if obj.is_string() || obj.is_number() {
property_set.insert(v.to_string(context)?);
}
}
// h. Set k to k + 1.
k += 1;
}
property_list = Some(property_set.into_iter().collect());
}
}
}
let mut space = args.get(2).cloned().unwrap_or_default();
// 5. If Type(space) is Object, then
if let Some(space_obj) = space.as_object() {
// a. If space has a [[NumberData]] internal slot, then
if space_obj.is_number() {
// i. Set space to ? ToNumber(space).
space = space.to_number(context)?.into();
}
// b. Else if space has a [[StringData]] internal slot, then
else if space_obj.is_string() {
// i. Set space to ? ToString(space).
space = space.to_string(context)?.into();
}
}
// 6. If Type(space) is Number, then
let gap = if space.is_number() {
// a. Let spaceMV be ! ToIntegerOrInfinity(space).
// b. Set spaceMV to min(10, spaceMV).
// c. If spaceMV < 1, let gap be the empty String; otherwise let gap be the String value containing spaceMV occurrences of the code unit 0x0020 (SPACE).
match space
.to_integer_or_infinity(context)
.expect("ToIntegerOrInfinity cannot fail on number")
{
IntegerOrInfinity::PositiveInfinity => JsString::new(" "),
IntegerOrInfinity::NegativeInfinity => JsString::new(""),
IntegerOrInfinity::Integer(i) if i < 1 => JsString::new(""),
IntegerOrInfinity::Integer(i) => {
let mut s = String::new();
let i = std::cmp::min(10, i);
for _ in 0..i {
s.push(' ');
}
s.into()
}
}
// 7. Else if Type(space) is String, then
} else if let Some(s) = space.as_string() {
// a. If the length of space is 10 or less, let gap be space; otherwise let gap be the substring of space from 0 to 10.
String::from_utf16_lossy(&s.encode_utf16().take(10).collect::<Vec<u16>>()).into()
// 8. Else,
} else {
JsValue::new("")
// a. Let gap be the empty String.
JsString::new("")
};
// 9. Let wrapper be ! OrdinaryObjectCreate(%Object.prototype%).
let wrapper = context.construct_object();
// 10. Perform ! CreateDataPropertyOrThrow(wrapper, the empty String, value).
wrapper
.create_data_property_or_throw("", args.get(0).cloned().unwrap_or_default(), context)
.expect("CreateDataPropertyOrThrow should never fail here");
// 11. Let state be the Record { [[ReplacerFunction]]: ReplacerFunction, [[Stack]]: stack, [[Indent]]: indent, [[Gap]]: gap, [[PropertyList]]: PropertyList }.
let mut state = StateRecord {
replacer_function,
stack,
indent,
gap,
property_list,
};
let gap = &gap.to_string(context)?;
// 12. Return ? SerializeJSONProperty(state, the empty String, wrapper).
Ok(
Self::serialize_json_property(&mut state, JsString::new(""), wrapper, context)?
.map(|s| s.into())
.unwrap_or_default(),
)
}
/// `25.5.2.1 SerializeJSONProperty ( state, key, holder )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-serializejsonproperty
fn serialize_json_property(
state: &mut StateRecord,
key: JsString,
holder: JsObject,
context: &mut Context,
) -> JsResult<Option<JsString>> {
// 1. Let value be ? Get(holder, key).
let mut value = holder.get(key.clone(), context)?;
// 2. If Type(value) is Object or BigInt, then
if value.is_object() || value.is_bigint() {
// a. Let toJSON be ? GetV(value, "toJSON").
let to_json = value.get_field("toJSON", context)?;
// b. If IsCallable(toJSON) is true, then
if let Some(obj) = to_json.as_object() {
if obj.is_callable() {
// i. Set value to ? Call(toJSON, value, « key »).
value = obj.call(&value, &[key.clone().into()], context)?;
}
}
}
// 3. If state.[[ReplacerFunction]] is not undefined, then
if let Some(obj) = &state.replacer_function {
// a. Set value to ? Call(state.[[ReplacerFunction]], holder, « key, value »).
value = obj.call(&holder.into(), &[key.into(), value], context)?
}
let replacer = match args.get(1) {
Some(replacer) if replacer.is_object() => replacer,
_ => {
if let Some(value) = object.to_json(context)? {
return Ok(JsValue::new(json_to_pretty_string(&value, gap)));
// 4. If Type(value) is Object, then
if let Some(obj) = value.as_object() {
// a. If value has a [[NumberData]] internal slot, then
if obj.is_number() {
// i. Set value to ? ToNumber(value).
value = value.to_number(context)?.into();
}
// b. Else if value has a [[StringData]] internal slot, then
else if obj.is_string() {
// i. Set value to ? ToString(value).
value = value.to_string(context)?.into();
}
// c. Else if value has a [[BooleanData]] internal slot, then
else if let Some(boolean) = obj.borrow().as_boolean() {
// i. Set value to value.[[BooleanData]].
value = boolean.into()
}
// d. Else if value has a [[BigIntData]] internal slot, then
else if let Some(bigint) = obj.borrow().as_bigint() {
// i. Set value to value.[[BigIntData]].
value = bigint.clone().into()
}
}
// 5. If value is null, return "null".
if value.is_null() {
return Ok(Some(JsString::new("null")));
}
// 6. If value is true, return "true".
// 7. If value is false, return "false".
if value.is_boolean() {
return match value.to_boolean() {
true => Ok(Some(JsString::new("true"))),
false => Ok(Some(JsString::new("false"))),
};
}
// 8. If Type(value) is String, return QuoteJSONString(value).
if let Some(s) = value.as_string() {
return Ok(Some(Self::quote_json_string(s)));
}
// 9. If Type(value) is Number, then
if let Some(n) = value.as_number() {
// a. If value is finite, return ! ToString(value).
if n.is_finite() {
return Ok(Some(
value
.to_string(context)
.expect("ToString should never fail here"),
));
}
// b. Return "null".
return Ok(Some(JsString::new("null")));
}
// 10. If Type(value) is BigInt, throw a TypeError exception.
if value.is_bigint() {
return Err(context.construct_type_error("cannot serialize bigint to JSON"));
}
// 11. If Type(value) is Object and IsCallable(value) is false, then
if let Some(obj) = value.as_object() {
if !obj.is_callable() {
// a. Let isArray be ? IsArray(value).
// b. If isArray is true, return ? SerializeJSONArray(state, value).
// c. Return ? SerializeJSONObject(state, value).
return if obj.is_array() {
Ok(Some(Self::serialize_json_array(state, obj, context)?))
} else {
return Ok(JsValue::undefined());
Ok(Some(Self::serialize_json_object(state, obj, context)?))
};
}
}
// 12. Return undefined.
Ok(None)
}
/// `25.5.2.2 QuoteJSONString ( value )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-quotejsonstring
fn quote_json_string(value: &JsString) -> JsString {
// 1. Let product be the String value consisting solely of the code unit 0x0022 (QUOTATION MARK).
let mut product = String::from('"');
// 2. For each code point C of ! StringToCodePoints(value), do
for code_point in value.encode_utf16() {
match code_point {
// a. If C is listed in the “Code Point” column of Table 73, then
// i. Set product to the string-concatenation of product and the escape sequence for C as specified in the “Escape Sequence” column of the corresponding row.
0x8 => product.push_str("\\b"),
0x9 => product.push_str("\\t"),
0xA => product.push_str("\\n"),
0xC => product.push_str("\\f"),
0xD => product.push_str("\\r"),
0x22 => product.push_str("\\\""),
0x5C => product.push_str("\\\\"),
// b. Else if C has a numeric value less than 0x0020 (SPACE), or if C has the same numeric value as a leading surrogate or trailing surrogate, then
code_point
if is_leading_surrogate(code_point) || is_trailing_surrogate(code_point) =>
{
// i. Let unit be the code unit whose numeric value is that of C.
// ii. Set product to the string-concatenation of product and UnicodeEscape(unit).
product.push_str(&format!("\\\\uAA{:x}", code_point));
}
// c. Else,
code_point => {
// i. Set product to the string-concatenation of product and ! UTF16EncodeCodePoint(C).
product.push(
char::from_u32(code_point as u32)
.expect("char from code point cannot fail here"),
);
}
}
}
// 3. Set product to the string-concatenation of product and the code unit 0x0022 (QUOTATION MARK).
product.push('"');
// 4. Return product.
product.into()
}
/// `25.5.2.4 SerializeJSONObject ( state, value )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-serializejsonobject
fn serialize_json_object(
state: &mut StateRecord,
value: JsObject,
context: &mut Context,
) -> JsResult<JsString> {
// 1. If state.[[Stack]] contains value, throw a TypeError exception because the structure is cyclical.
let limiter = RecursionLimiter::new(&value);
if limiter.live {
return Err(context.construct_type_error("cyclic object value"));
}
// 2. Append value to state.[[Stack]].
state.stack.push(value.clone().into());
// 3. Let stepback be state.[[Indent]].
let stepback = state.indent.clone();
// 4. Set state.[[Indent]] to the string-concatenation of state.[[Indent]] and state.[[Gap]].
state.indent = JsString::concat(&state.indent, &state.gap);
// 5. If state.[[PropertyList]] is not undefined, then
let mut k = if let Some(p) = &state.property_list {
// a. Let K be state.[[PropertyList]].
p.clone()
// 6. Else,
} else {
// a. Let K be ? EnumerableOwnPropertyNames(value, key).
let keys = value.enumerable_own_property_names(PropertyNameKind::Key, context)?;
// Unwrap is safe, because EnumerableOwnPropertyNames with kind "key" only returns string values.
keys.iter().map(|v| v.to_string(context).unwrap()).collect()
};
let replacer_as_object = replacer
.as_object()
.expect("JSON.stringify replacer was an object");
if replacer_as_object.is_callable() {
object
.as_object()
.map(|obj| {
let object_to_return = JsValue::new(Object::default());
for key in obj.borrow().properties().keys() {
let val = obj.__get__(&key, obj.clone().into(), context)?;
let this_arg = object.clone();
object_to_return.set_property(
key.to_owned(),
PropertyDescriptor::builder()
.value(context.call(
replacer,
&this_arg,
&[JsValue::new(key.clone()), val.clone()],
)?)
.writable(true)
.enumerable(true)
.configurable(true),
)
}
if let Some(value) = object_to_return.to_json(context)? {
Ok(JsValue::new(json_to_pretty_string(&value, gap)))
} else {
Ok(JsValue::undefined())
}
})
.ok_or_else(JsValue::undefined)?
} else if replacer_as_object.is_array() {
let mut obj_to_return = serde_json::Map::new();
let replacer_as_object = replacer_as_object.borrow();
let fields = replacer_as_object.properties().keys().filter_map(|key| {
if key == "length" {
None
// Sort the property key list, because the internal property hashmap of objects does not sort the properties.
k.sort();
// 7. Let partial be a new empty List.
let mut partial = Vec::new();
// 8. For each element P of K, do
for p in &k {
// a. Let strP be ? SerializeJSONProperty(state, P, value).
let str_p = Self::serialize_json_property(state, p.clone(), value.clone(), context)?;
// b. If strP is not undefined, then
if let Some(str_p) = str_p {
// i. Let member be QuoteJSONString(P).
// ii. Set member to the string-concatenation of member and ":".
// iii. If state.[[Gap]] is not the empty String, then
// 1. Set member to the string-concatenation of member and the code unit 0x0020 (SPACE).
// iv. Set member to the string-concatenation of member and strP.
let member = if state.gap.is_empty() {
format!("{}:{}", Self::quote_json_string(p).as_str(), str_p.as_str())
} else {
Some(
replacer
.get_property(key)
.as_ref()
.map(|d| d.value())
.flatten()
.cloned()
.unwrap_or_default(),
format!(
"{}: {}",
Self::quote_json_string(p).as_str(),
str_p.as_str()
)
}
});
for field in fields {
let v = object.get_field(field.to_string(context)?, context)?;
if !v.is_undefined() {
if let Some(value) = v.to_json(context)? {
obj_to_return.insert(field.to_string(context)?.to_string(), value);
}
}
};
// v. Append member to partial.
partial.push(member);
}
Ok(JsValue::new(json_to_pretty_string(
&JSONValue::Object(obj_to_return),
gap,
)))
} else if let Some(value) = object.to_json(context)? {
Ok(JsValue::new(json_to_pretty_string(&value, gap)))
}
// 9. If partial is empty, then
let r#final = if partial.is_empty() {
// a. Let final be "{}".
JsString::new("{}")
// 10. Else,
} else {
Ok(JsValue::undefined())
// a. If state.[[Gap]] is the empty String, then
if state.gap.is_empty() {
// i. Let properties be the String value formed by concatenating all the element Strings of partial
// with each adjacent pair of Strings separated with the code unit 0x002C (COMMA).
// A comma is not inserted either before the first String or after the last String.
// ii. Let final be the string-concatenation of "{", properties, and "}".
format!("{{{}}}", partial.join(",")).into()
// b. Else,
} else {
// i. Let separator be the string-concatenation of the code unit 0x002C (COMMA),
// the code unit 0x000A (LINE FEED), and state.[[Indent]].
let separator = format!(",{}{}", '\u{A}', state.indent.as_str());
// ii. Let properties be the String value formed by concatenating all the element Strings of partial
// with each adjacent pair of Strings separated with separator.
// The separator String is not inserted either before the first String or after the last String.
let properties = partial.join(&separator);
// iii. Let final be the string-concatenation of "{", the code unit 0x000A (LINE FEED), state.[[Indent]], properties, the code unit 0x000A (LINE FEED), stepback, and "}".
format!(
"{{{}{}{}{}{}}}",
'\u{A}',
state.indent.as_str(),
&properties,
'\u{A}',
stepback.as_str()
)
.into()
}
};
// 11. Remove the last element of state.[[Stack]].
state.stack.pop();
// 12. Set state.[[Indent]] to stepback.
state.indent = stepback;
// 13. Return final.
Ok(r#final)
}
/// `25.5.2.5 SerializeJSONArray ( state, value )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-serializejsonarray
fn serialize_json_array(
state: &mut StateRecord,
value: JsObject,
context: &mut Context,
) -> JsResult<JsString> {
// 1. If state.[[Stack]] contains value, throw a TypeError exception because the structure is cyclical.
let limiter = RecursionLimiter::new(&value);
if limiter.live {
return Err(context.construct_type_error("cyclic object value"));
}
// 2. Append value to state.[[Stack]].
state.stack.push(value.clone().into());
// 3. Let stepback be state.[[Indent]].
let stepback = state.indent.clone();
// 4. Set state.[[Indent]] to the string-concatenation of state.[[Indent]] and state.[[Gap]].
state.indent = JsString::concat(&state.indent, &state.gap);
// 5. Let partial be a new empty List.
let mut partial = Vec::new();
// 6. Let len be ? LengthOfArrayLike(value).
let len = value.length_of_array_like(context)?;
// 7. Let index be 0.
let mut index = 0;
// 8. Repeat, while index < len,
while index < len {
// a. Let strP be ? SerializeJSONProperty(state, ! ToString(𝔽(index)), value).
let str_p = Self::serialize_json_property(
state,
index.to_string().into(),
value.clone(),
context,
)?;
// b. If strP is undefined, then
if let Some(str_p) = str_p {
// i. Append strP to partial.
partial.push(str_p)
// c. Else,
} else {
// i. Append "null" to partial.
partial.push("null".into())
}
// d. Set index to index + 1.
index += 1;
}
// 9. If partial is empty, then
let r#final = if partial.is_empty() {
// a. Let final be "[]".
JsString::from("[]")
// 10. Else,
} else {
// a. If state.[[Gap]] is the empty String, then
if state.gap.is_empty() {
// i. Let properties be the String value formed by concatenating all the element Strings of partial
// with each adjacent pair of Strings separated with the code unit 0x002C (COMMA).
// A comma is not inserted either before the first String or after the last String.
// ii. Let final be the string-concatenation of "[", properties, and "]".
format!("[{}]", partial.join(",")).into()
// b. Else,
} else {
// i. Let separator be the string-concatenation of the code unit 0x002C (COMMA),
// the code unit 0x000A (LINE FEED), and state.[[Indent]].
let separator = format!(",{}{}", '\u{A}', state.indent.as_str());
// ii. Let properties be the String value formed by concatenating all the element Strings of partial
// with each adjacent pair of Strings separated with separator.
// The separator String is not inserted either before the first String or after the last String.
let properties = partial.join(&separator);
// iii. Let final be the string-concatenation of "[", the code unit 0x000A (LINE FEED), state.[[Indent]], properties, the code unit 0x000A (LINE FEED), stepback, and "]".
format!(
"[{}{}{}{}{}]",
'\u{A}',
state.indent.as_str(),
&properties,
'\u{A}',
stepback.as_str()
)
.into()
}
};
// 11. Remove the last element of state.[[Stack]].
state.stack.pop();
// 12. Set state.[[Indent]] to stepback.
state.indent = stepback;
// 13. Return final.
Ok(r#final)
}
}
fn json_to_pretty_string(json: &JSONValue, gap: &str) -> String {
if gap.is_empty() {
return json.to_string();
}
let formatter = PrettyFormatter::with_indent(gap.as_bytes());
let mut writer = Vec::with_capacity(128);
let mut serializer = Serializer::with_formatter(&mut writer, formatter);
json.serialize(&mut serializer)
.expect("JSON serialization failed");
unsafe {
// The serde json serializer always produce correct UTF-8
String::from_utf8_unchecked(writer)
}
struct StateRecord {
replacer_function: Option<JsObject>,
stack: Vec<JsValue>,
indent: JsString,
gap: JsString,
property_list: Option<Vec<JsString>>,
}

4
boa/src/builtins/string/mod.rs

@ -71,11 +71,11 @@ pub(crate) fn is_trimmable_whitespace(c: char) -> bool {
)
}
fn is_leading_surrogate(value: u16) -> bool {
pub(crate) fn is_leading_surrogate(value: u16) -> bool {
(0xD800..=0xDBFF).contains(&value)
}
fn is_trailing_surrogate(value: u16) -> bool {
pub(crate) fn is_trailing_surrogate(value: u16) -> bool {
(0xDC00..=0xDFFF).contains(&value)
}

40
boa/src/object/gcobject.rs

@ -20,7 +20,6 @@ use crate::{
Context, Executable, JsResult, JsValue,
};
use gc::{Finalize, Gc, GcCell, GcCellRef, GcCellRefMut, Trace};
use serde_json::{map::Map, Value as JSONValue};
use std::{
cell::RefCell,
collections::HashMap,
@ -443,45 +442,6 @@ impl JsObject {
context.throw_type_error("cannot convert object to primitive value")
}
/// Converts an object to JSON, checking for reference cycles and throwing a TypeError if one is found
pub(crate) fn to_json(&self, context: &mut Context) -> JsResult<Option<JSONValue>> {
let rec_limiter = RecursionLimiter::new(self);
if rec_limiter.live {
Err(context.construct_type_error("cyclic object value"))
} else if self.is_array() {
let mut keys: Vec<u32> = self
.borrow()
.properties
.index_property_keys()
.cloned()
.collect();
keys.sort_unstable();
let mut arr: Vec<JSONValue> = Vec::with_capacity(keys.len());
let this = JsValue::new(self.clone());
for key in keys {
let value = this.get_field(key, context)?;
if let Some(value) = value.to_json(context)? {
arr.push(value);
} else {
arr.push(JSONValue::Null);
}
}
Ok(Some(JSONValue::Array(arr)))
} else {
let mut new_obj = Map::new();
let this = JsValue::new(self.clone());
let keys: Vec<PropertyKey> = self.borrow().properties.keys().collect();
for k in keys {
let key = k.clone();
let value = this.get_field(k.to_string(), context)?;
if let Some(value) = value.to_json(context)? {
new_obj.insert(key.to_string(), value);
}
}
Ok(Some(JSONValue::Object(new_obj)))
}
}
/// Return `true` if it is a native object and the native type is `T`.
///
/// # Panics

37
boa/src/value/mod.rs

@ -17,7 +17,7 @@ use crate::{
BoaProfiler, Context, JsBigInt, JsResult, JsString,
};
use gc::{Finalize, Trace};
use serde_json::{Number as JSONNumber, Value as JSONValue};
use serde_json::Value as JSONValue;
use std::{
collections::HashSet,
convert::TryFrom,
@ -173,41 +173,6 @@ impl JsValue {
}
}
/// Converts the `JsValue` to `JSON`.
pub fn to_json(&self, context: &mut Context) -> JsResult<Option<JSONValue>> {
let to_json = self.get_field("toJSON", context)?;
if to_json.is_function() {
let json_value = context.call(&to_json, self, &[])?;
return json_value.to_json(context);
}
if self.is_function() {
return Ok(None);
}
match *self {
Self::Null => Ok(Some(JSONValue::Null)),
Self::Boolean(b) => Ok(Some(JSONValue::Bool(b))),
Self::Object(ref obj) => obj.to_json(context),
Self::String(ref str) => Ok(Some(JSONValue::String(str.to_string()))),
Self::Rational(num) => {
if num.is_finite() {
Ok(Some(JSONValue::Number(
JSONNumber::from_str(&Number::to_native_string(num))
.expect("invalid number found"),
)))
} else {
Ok(Some(JSONValue::Null))
}
}
Self::Integer(val) => Ok(Some(JSONValue::Number(JSONNumber::from(val)))),
Self::BigInt(_) => {
Err(context.construct_type_error("BigInt value can't be serialized in JSON"))
}
Self::Symbol(_) | Self::Undefined => Ok(None),
}
}
/// This will tell us if we can exten an object or not, not properly implemented yet
///
/// For now always returns true.

Loading…
Cancel
Save