Browse Source

Add support for the reviver function to JSON.parse (#410)

pull/436/head
abhi 4 years ago committed by GitHub
parent
commit
8255c3a83a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 47
      boa/src/builtins/json/mod.rs
  2. 49
      boa/src/builtins/json/tests.rs
  3. 10
      boa/src/builtins/value/mod.rs

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

@ -37,8 +37,7 @@ mod tests;
///
/// [spec]: https://tc39.es/ecma262/#sec-json.parse
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
// TODO: implement optional revever argument.
pub fn parse(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
pub fn parse(_: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue {
match serde_json::from_str::<JSONValue>(
&args
.get(0)
@ -46,11 +45,53 @@ pub fn parse(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue
.clone()
.to_string(),
) {
Ok(json) => Ok(Value::from(json)),
Ok(json) => {
let j = Value::from(json);
match args.get(1) {
Some(reviver) if reviver.is_function() => {
let mut holder = Value::new_object(None);
holder.set_field(Value::from(""), j);
walk(reviver, interpreter, &mut holder, Value::from(""))
}
_ => Ok(j),
}
}
Err(err) => Err(Value::from(err.to_string())),
}
}
/// This is a translation of the [Polyfill implementation][polyfill]
///
/// This function recursively walks the structure, passing each key-value pair to the reviver function
/// for possible transformation.
///
/// [polyfill]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
fn walk(
reviver: &Value,
interpreter: &mut Interpreter,
holder: &mut Value,
key: Value,
) -> ResultValue {
let mut value = holder.get_field(key.clone());
let obj = value.as_object().as_deref().cloned();
if let Some(obj) = obj {
for key in obj.properties.keys() {
let v = walk(reviver, interpreter, &mut value, Value::from(key.as_str()));
match v {
Ok(v) if !v.is_undefined() => {
value.set_field(Value::from(key.as_str()), v);
}
Ok(_) => {
value.remove_property(key.as_str());
}
Err(_v) => {}
}
}
}
interpreter.call(reviver, holder, &[key, value])
}
/// `JSON.stringify( value[, replacer[, space]] )`
///
/// This `JSON` method converts a JavaScript object or value to a JSON string.

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

@ -1,4 +1,4 @@
use crate::{exec::Interpreter, forward, realm::Realm};
use crate::{exec::Interpreter, forward, forward_val, realm::Realm};
#[test]
fn json_sanity() {
@ -189,3 +189,50 @@ fn json_stringify_return_undefined() {
assert_eq!(actual_function, expected);
assert_eq!(actual_symbol, expected);
}
#[test]
fn json_parse_array_with_reviver() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let result = forward_val(
&mut engine,
r#"JSON.parse('[1,2,3,4]', function(k, v){
if (typeof v == 'number') {
return v * 2;
} else {
v
}})"#,
)
.unwrap();
assert_eq!(result.get_field("0").to_number() as u8, 2u8);
assert_eq!(result.get_field("1").to_number() as u8, 4u8);
assert_eq!(result.get_field("2").to_number() as u8, 6u8);
assert_eq!(result.get_field("3").to_number() as u8, 8u8);
}
#[test]
fn json_parse_object_with_reviver() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let result = forward(
&mut engine,
r#"
var myObj = new Object();
myObj.firstname = "boa";
myObj.lastname = "snake";
var jsonString = JSON.stringify(myObj);
function dataReviver(key, value) {
if (key == 'lastname') {
return 'interpreter';
} else {
return value;
}
}
var jsonObj = JSON.parse(jsonString, dataReviver);
JSON.stringify(jsonObj);"#,
);
assert_eq!(result, r#"{"firstname":"boa","lastname":"interpreter"}"#);
}

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

@ -690,7 +690,10 @@ impl ValueData {
for (idx, json) in vs.iter().enumerate() {
new_obj.properties.insert(
idx.to_string(),
Property::default().value(Value::from(json.clone())),
Property::default()
.value(Value::from(json.clone()))
.writable(true)
.configurable(true),
);
}
new_obj.properties.insert(
@ -704,7 +707,10 @@ impl ValueData {
for (key, json) in obj.iter() {
new_obj.properties.insert(
key.clone(),
Property::default().value(Value::from(json.clone())),
Property::default()
.value(Value::from(json.clone()))
.writable(true)
.configurable(true),
);
}

Loading…
Cancel
Save