use crate::{
builtins::function::make_builtin_fn,
property::{Property, PropertyKey},
BoaProfiler, Context, Result, Value,
};
use serde_json::{self, Value as JSONValue};
#[cfg(test)]
mod tests;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct Json;
impl Json {
pub(crate) const NAME: &'static str = "JSON";
pub(crate) fn parse(_: &Value, args: &[Value], ctx: &mut Context) -> Result<Value> {
match serde_json::from_str::<JSONValue>(
&args
.get(0)
.expect("cannot get argument for JSON.parse")
.to_string(ctx)?,
) {
Ok(json) => {
let j = Value::from_json(json, ctx);
match args.get(1) {
Some(reviver) if reviver.is_function() => {
let mut holder = Value::new_object(None);
holder.set_field("", j);
Self::walk(reviver, ctx, &mut holder, &PropertyKey::from(""))
}
_ => Ok(j),
}
}
Err(err) => Err(Value::from(err.to_string())),
}
}
fn walk(
reviver: &Value,
ctx: &mut Context,
holder: &mut Value,
key: &PropertyKey,
) -> Result<Value> {
let value = holder.get_field(key.clone());
if let Value::Object(ref object) = value {
let keys: Vec<_> = object.borrow().keys().collect();
for key in keys {
let v = Self::walk(reviver, ctx, &mut value.clone(), &key);
match v {
Ok(v) if !v.is_undefined() => {
value.set_field(key, v);
}
Ok(_) => {
value.remove_property(key);
}
Err(_v) => {}
}
}
}
ctx.call(reviver, holder, &[key.into(), value])
}
pub(crate) fn stringify(_: &Value, args: &[Value], ctx: &mut Context) -> Result<Value> {
let object = match args.get(0) {
Some(obj) if obj.is_symbol() || obj.is_function() || obj.is_undefined() => {
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(ctx)?.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
.iter()
.filter_map(|(k, v)| v.value.as_ref().map(|value| (k, value)))
{
let this_arg = object.clone();
object_to_return.set_property(
key.to_owned(),
Property::default().value(ctx.call(
replacer,
&this_arg,
&[Value::from(key.clone()), val.clone()],
)?),
);
}
Ok(Value::from(object_to_return.to_json(ctx)?.to_string()))
})
.ok_or_else(Value::undefined)?
} else if replacer_as_object.is_array() {
let mut obj_to_return = serde_json::Map::new();
let fields = replacer_as_object.keys().filter_map(|key| {
if key == "length" {
None
} else {
Some(replacer.get_field(key))
}
});
for field in fields {
if let Some(value) = object
.get_property(field.to_string(ctx)?)
.and_then(|prop| prop.value.as_ref().map(|v| v.to_json(ctx)))
.transpose()?
{
obj_to_return.insert(field.to_string(ctx)?.to_string(), value);
}
}
Ok(Value::from(JSONValue::Object(obj_to_return).to_string()))
} else {
Ok(Value::from(object.to_json(ctx)?.to_string()))
}
}
#[inline]
pub(crate) fn init(interpreter: &mut Context) -> (&'static str, Value) {
let global = interpreter.global_object();
let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
let json = Value::new_object(Some(global));
make_builtin_fn(Self::parse, "parse", &json, 2, interpreter);
make_builtin_fn(Self::stringify, "stringify", &json, 3, interpreter);
(Self::NAME, json)
}
}