Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 11 commits into from
Jun 1, 2020
47 changes: 44 additions & 3 deletions boa/src/builtins/json/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,61 @@ 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)
.expect("cannot get argument for JSON.parse")
.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.
Expand Down
49 changes: 48 additions & 1 deletion boa/src/builtins/json/tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{exec::Interpreter, forward, realm::Realm};
use crate::{exec::Interpreter, forward, forward_val, realm::Realm};

#[test]
fn json_sanity() {
Expand Down Expand Up @@ -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 changes: 8 additions & 2 deletions boa/src/builtins/value/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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),
);
}

Expand Down