use crate::object::Object;
use crate::{
builtins::BuiltIn,
object::ObjectInitializer,
property::{Attribute, DataDescriptor, PropertyKey},
value::IntegerOrInfinity,
BoaProfiler, Context, Result, Value,
};
use serde::Serialize;
use serde_json::{self, ser::PrettyFormatter, Serializer, Value as JSONValue};
#[cfg(test)]
mod tests;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct Json;
impl BuiltIn for Json {
const NAME: &'static str = "JSON";
fn attribute() -> Attribute {
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE
}
fn init(context: &mut Context) -> (&'static str, Value, Attribute) {
let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
let json_object = ObjectInitializer::new(context)
.function(Self::parse, "parse", 2)
.function(Self::stringify, "stringify", 3)
.build();
(Self::NAME, json_object.into(), Self::attribute())
}
}
impl Json {
pub(crate) fn parse(_: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
let arg = args
.get(0)
.cloned()
.unwrap_or_else(Value::undefined)
.to_string(context)?;
match serde_json::from_str::<JSONValue>(&arg) {
Ok(json) => {
let j = Value::from_json(json, context);
match args.get(1) {
Some(reviver) if reviver.is_function() => {
let mut holder = Value::object(Object::default());
holder.set_field("", j, context)?;
Self::walk(reviver, context, &mut holder, &PropertyKey::from(""))
}
_ => Ok(j),
}
}
Err(err) => context.throw_syntax_error(err.to_string()),
}
}
fn walk(
reviver: &Value,
context: &mut Context,
holder: &mut Value,
key: &PropertyKey,
) -> Result<Value> {
let value = holder.get_field(key.clone(), context)?;
if let Value::Object(ref object) = value {
let keys: Vec<_> = object.borrow().keys().collect();
for key in keys {
let v = Self::walk(reviver, context, &mut value.clone(), &key);
match v {
Ok(v) if !v.is_undefined() => {
value.set_field(key, v, context)?;
}
Ok(_) => {
value.remove_property(key);
}
Err(_v) => {}
}
}
}
context.call(reviver, holder, &[key.into(), value])
}
pub(crate) fn stringify(_: &Value, args: &[Value], context: &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,
};
const SPACE_INDENT: &str = " ";
let gap = if let Some(space) = args.get(2) {
let space = if let Some(space_obj) = space.as_object() {
if let Some(space) = space_obj.borrow().as_number() {
Value::from(space)
} else if let Some(space) = space_obj.borrow().as_string() {
Value::from(space)
} else {
space.clone()
}
} else {
space.clone()
};
if space.is_number() {
let space_mv = match space.to_integer_or_infinity(context)? {
IntegerOrInfinity::NegativeInfinity => 0,
IntegerOrInfinity::PositiveInfinity => 10,
IntegerOrInfinity::Integer(i) if i < 1 => 0,
IntegerOrInfinity::Integer(i) => std::cmp::min(i, 10) as usize,
};
Value::from(&SPACE_INDENT[..space_mv])
} else if let Some(string) = space.as_string() {
Value::from(&string[..std::cmp::min(string.len(), 10)])
} else {
Value::from("")
}
} else {
Value::from("")
};
let gap = &gap.to_string(context)?;
let replacer = match args.get(1) {
Some(replacer) if replacer.is_object() => replacer,
_ => {
return Ok(Value::from(json_to_pretty_string(
&object.to_json(context)?,
gap,
)))
}
};
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::object(Object::default());
for key in obj.borrow().keys() {
let val = obj.get(&key, obj.clone().into(), context)?;
let this_arg = object.clone();
object_to_return.set_property(
key.to_owned(),
DataDescriptor::new(
context.call(
replacer,
&this_arg,
&[Value::from(key.clone()), val.clone()],
)?,
Attribute::all(),
),
);
}
Ok(Value::from(json_to_pretty_string(
&object_to_return.to_json(context)?,
gap,
)))
})
.ok_or_else(Value::undefined)?
} else if replacer_as_object.is_array() {
let mut obj_to_return = serde_json::Map::new();
let replacer_as_object = replacer_as_object.borrow();
let fields = replacer_as_object.keys().filter_map(|key| {
if key == "length" {
None
} else {
Some(
replacer
.get_property(key)
.as_ref()
.and_then(|p| p.as_data_descriptor())
.map(|d| d.value())
.unwrap_or_else(Value::undefined),
)
}
});
for field in fields {
let v = object.get_field(field.to_string(context)?, context)?;
if !v.is_undefined() {
let value = v.to_json(context)?;
obj_to_return.insert(field.to_string(context)?.to_string(), value);
}
}
Ok(Value::from(json_to_pretty_string(
&JSONValue::Object(obj_to_return),
gap,
)))
} else {
Ok(Value::from(json_to_pretty_string(
&object.to_json(context)?,
gap,
)))
}
}
}
fn json_to_pretty_string(json: &JSONValue, gap: &str) -> String {
if gap.is_empty() {
return json.to_string();
}
let formatter = PrettyFormatter::with_indent(gap.as_bytes());
let mut writer = Vec::with_capacity(128);
let mut serializer = Serializer::with_formatter(&mut writer, formatter);
json.serialize(&mut serializer)
.expect("JSON serialization failed");
unsafe {
String::from_utf8_unchecked(writer)
}
}