Browse Source

Json stringify replacer (#402)

pull/436/head
n14little 4 years ago committed by GitHub
parent
commit
82908dfdf4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 66
      boa/src/builtins/json/mod.rs
  2. 173
      boa/src/builtins/json/tests.rs
  3. 36
      boa/src/builtins/value/mod.rs

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

@ -15,6 +15,8 @@
use crate::builtins::{
function::make_builtin_fn,
object::ObjectKind,
property::Property,
value::{ResultValue, Value},
};
use crate::exec::Interpreter;
@ -65,10 +67,66 @@ pub fn parse(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue
///
/// [spec]: https://tc39.es/ecma262/#sec-json.stringify
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
pub fn stringify(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let obj = args.get(0).expect("cannot get argument for JSON.stringify");
let json = obj.to_json().to_string();
Ok(Value::from(json))
pub fn stringify(_: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue {
let object = match args.get(0) {
Some(obj) if obj.is_symbol() || obj.is_function() => return Ok(Value::undefined()),
None => return Ok(Value::undefined()),
Some(obj) => obj,
};
let replacer = match args.get(1) {
Some(replacer) if replacer.is_object() => replacer,
_ => return Ok(Value::from(object.to_json().to_string())),
};
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 = Value::new_object(None);
for (key, val) in obj
.properties
.iter()
.filter_map(|(k, v)| v.value.as_ref().map(|value| (k, value)))
{
let mut this_arg = object.clone();
object_to_return.set_property(
key.to_owned(),
Property::default().value(interpreter.call(
replacer,
&mut this_arg,
&[Value::string(key), val.clone()],
)?),
);
}
Ok(Value::from(object_to_return.to_json().to_string()))
})
.ok_or_else(Value::undefined)?
} else if replacer_as_object.kind == ObjectKind::Array {
let mut obj_to_return =
serde_json::Map::with_capacity(replacer_as_object.properties.len() - 1);
let fields = replacer_as_object.properties.keys().filter_map(|key| {
if key == "length" {
None
} else {
Some(replacer.get_field(key.to_string()))
}
});
for field in fields {
if let Some(value) = object
.get_property(&field.to_string())
.map(|prop| prop.value.as_ref().map(|v| v.to_json()))
.flatten()
{
obj_to_return.insert(field.to_string(), value);
}
}
Ok(Value::from(JSONValue::Object(obj_to_return).to_string()))
} else {
Ok(Value::from(object.to_json().to_string()))
}
}
/// Create a new `JSON` object.

173
boa/src/builtins/json/tests.rs

@ -16,3 +16,176 @@ fn json_sanity() {
"true"
);
}
#[test]
fn json_stringify_remove_undefined_values_from_objects() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let actual = forward(
&mut engine,
r#"JSON.stringify({ aaa: undefined, bbb: 'ccc' })"#,
);
let expected = r#"{"bbb":"ccc"}"#;
assert_eq!(actual, expected);
}
#[test]
fn json_stringify_remove_function_values_from_objects() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let actual = forward(
&mut engine,
r#"JSON.stringify({ aaa: () => {}, bbb: 'ccc' })"#,
);
let expected = r#"{"bbb":"ccc"}"#;
assert_eq!(actual, expected);
}
#[test]
#[ignore]
// there is a bug for setting a symbol as a field's value
fn json_stringify_remove_symbols_from_objects() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let actual = forward(
&mut engine,
r#"JSON.stringify({ aaa: Symbol(), bbb: 'ccc' })"#,
);
let expected = r#"{"bbb":"ccc"}"#;
assert_eq!(actual, expected);
}
#[test]
fn json_stringify_replacer_array_strings() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let actual = forward(
&mut engine,
r#"JSON.stringify({aaa: 'bbb', bbb: 'ccc', ccc: 'ddd'}, ['aaa', 'bbb'])"#,
);
let expected = forward(&mut engine, r#"'{"aaa":"bbb","bbb":"ccc"}'"#);
assert_eq!(actual, expected);
}
#[test]
fn json_stringify_replacer_array_numbers() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let actual = forward(
&mut engine,
r#"JSON.stringify({ 0: 'aaa', 1: 'bbb', 2: 'ccc'}, [1, 2])"#,
);
let expected = forward(&mut engine, r#"'{"1":"bbb","2":"ccc"}'"#);
assert_eq!(actual, expected);
}
#[test]
fn json_stringify_replacer_function() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let actual = forward(
&mut engine,
r#"JSON.stringify({ aaa: 1, bbb: 2}, (key, value) => {
if (key === 'aaa') {
return undefined;
}
return value;
})"#,
);
let expected = forward(&mut engine, r#"'{"bbb":2}'"#);
assert_eq!(actual, expected);
}
#[test]
fn json_stringify_arrays() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let actual = forward(&mut engine, r#"JSON.stringify(['a', 'b'])"#);
let expected = forward(&mut engine, r#"'["a","b"]'"#);
assert_eq!(actual, expected);
}
#[test]
fn json_stringify_object_array() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let actual = forward(&mut engine, r#"JSON.stringify([{a: 'b'}, {b: 'c'}])"#);
let expected = forward(&mut engine, r#"'[{"a":"b"},{"b":"c"}]'"#);
assert_eq!(actual, expected);
}
#[test]
fn json_stringify_array_converts_undefined_to_null() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let actual = forward(&mut engine, r#"JSON.stringify([undefined])"#);
let expected = forward(&mut engine, r#"'[null]'"#);
assert_eq!(actual, expected);
}
#[test]
fn json_stringify_array_converts_function_to_null() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let actual = forward(&mut engine, r#"JSON.stringify([() => {}])"#);
let expected = forward(&mut engine, r#"'[null]'"#);
assert_eq!(actual, expected);
}
#[test]
#[ignore]
fn json_stringify_array_converts_symbol_to_null() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let actual = forward(&mut engine, r#"JSON.stringify([Symbol()])"#);
let expected = forward(&mut engine, r#"'[null]'"#);
assert_eq!(actual, expected);
}
#[test]
fn json_stringify_function_replacer_propogate_error() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let actual = forward(
&mut engine,
r#"
let thrown = 0;
try {
JSON.stringify({x: 1}, (key, value) => { throw 1 })
} catch (err) {
thrown = err;
}
thrown
"#,
);
let expected = forward(&mut engine, r#"1"#);
assert_eq!(actual, expected);
}
#[test]
fn json_stringify_return_undefined() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let actual_no_args = forward(&mut engine, r#"JSON.stringify()"#);
let actual_function = forward(&mut engine, r#"JSON.stringify(() => {})"#);
let actual_symbol = forward(&mut engine, r#"JSON.stringify(Symbol())"#);
let expected = forward(&mut engine, r#"undefined"#);
assert_eq!(actual_no_args, expected);
assert_eq!(actual_function, expected);
assert_eq!(actual_symbol, expected);
}

36
boa/src/builtins/value/mod.rs

@ -717,16 +717,33 @@ impl ValueData {
/// Conversts the `Value` to `JSON`.
pub fn to_json(&self) -> JSONValue {
match *self {
Self::Null | Self::Symbol(_) | Self::Undefined => JSONValue::Null,
Self::Null => JSONValue::Null,
Self::Boolean(b) => JSONValue::Bool(b),
Self::Object(ref obj) => {
let new_obj = obj
.borrow()
.properties
.iter()
.map(|(k, _)| (k.clone(), self.get_field(k.as_str()).to_json()))
.collect::<Map<String, JSONValue>>();
JSONValue::Object(new_obj)
if obj.borrow().kind == ObjectKind::Array {
let mut arr: Vec<JSONValue> = Vec::new();
obj.borrow().properties.keys().for_each(|k| {
if k != "length" {
let value = self.get_field(k.to_string());
if value.is_undefined() || value.is_function() {
arr.push(JSONValue::Null);
} else {
arr.push(self.get_field(k.to_string()).to_json());
}
}
});
JSONValue::Array(arr)
} else {
let mut new_obj = Map::new();
obj.borrow().properties.keys().for_each(|k| {
let key = k.clone();
let value = self.get_field(k.to_string());
if !value.is_undefined() && !value.is_function() {
new_obj.insert(key, value.to_json());
}
});
JSONValue::Object(new_obj)
}
}
Self::String(ref str) => JSONValue::String(str.clone()),
Self::Rational(num) => JSONValue::Number(
@ -737,6 +754,9 @@ impl ValueData {
// TODO: throw TypeError
panic!("TypeError: \"BigInt value can't be serialized in JSON\"");
}
Self::Symbol(_) | Self::Undefined => {
unreachable!("Symbols and Undefined JSON Values depend on parent type");
}
}
}

Loading…
Cancel
Save