diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index 79e138221b..f738292bcd 100644 --- a/boa/src/builtins/json/mod.rs +++ b/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 { - 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::>()).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> { + // 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 { + // 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 { + // 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, + stack: Vec, + indent: JsString, + gap: JsString, + property_list: Option>, } diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index d9d695a5a6..94471b6a0a 100644 --- a/boa/src/builtins/string/mod.rs +++ b/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) } diff --git a/boa/src/object/gcobject.rs b/boa/src/object/gcobject.rs index ffef7c9035..61bf6b8948 100644 --- a/boa/src/object/gcobject.rs +++ b/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> { - 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 = self - .borrow() - .properties - .index_property_keys() - .cloned() - .collect(); - keys.sort_unstable(); - let mut arr: Vec = 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 = 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 diff --git a/boa/src/value/mod.rs b/boa/src/value/mod.rs index cb70a598a5..23d3dec1dc 100644 --- a/boa/src/value/mod.rs +++ b/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> { - 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.