From 8255c3a83a12db6f4affdf44167bde2745cecf39 Mon Sep 17 00:00:00 2001 From: abhi Date: Mon, 1 Jun 2020 14:10:41 +0530 Subject: [PATCH] Add support for the reviver function to JSON.parse (#410) --- boa/src/builtins/json/mod.rs | 47 +++++++++++++++++++++++++++++--- boa/src/builtins/json/tests.rs | 49 +++++++++++++++++++++++++++++++++- boa/src/builtins/value/mod.rs | 10 +++++-- 3 files changed, 100 insertions(+), 6 deletions(-) diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index ad7cd378fd..035f4fead5 100644 --- a/boa/src/builtins/json/mod.rs +++ b/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::( &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. diff --git a/boa/src/builtins/json/tests.rs b/boa/src/builtins/json/tests.rs index e5e419540a..90ceb1dd2f 100644 --- a/boa/src/builtins/json/tests.rs +++ b/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"}"#); +} diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index ec682c7b09..3721ed82ce 100644 --- a/boa/src/builtins/value/mod.rs +++ b/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), ); }