Skip to content

Commit

Permalink
LibJS: Bring JSON.stringify closer to the specification
Browse files Browse the repository at this point in the history
  • Loading branch information
IdanHo authored and awesomekling committed Jul 1, 2021
1 parent 172d81a commit 8d50cf4
Showing 1 changed file with 94 additions and 63 deletions.
157 changes: 94 additions & 63 deletions Userland/Libraries/LibJS/Runtime/JSONObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <AK/JsonObject.h>
#include <AK/JsonParser.h>
#include <AK/StringBuilder.h>
#include <AK/Utf8View.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/BigIntObject.h>
Expand Down Expand Up @@ -43,6 +44,7 @@ JSONObject::~JSONObject()
{
}

// 25.5.2 JSON.stringify ( value [ , replacer [ , space ] ] ), https://tc39.es/ecma262/#sec-json.stringify
String JSONObject::stringify_impl(GlobalObject& global_object, Value value, Value replacer, Value space)
{
auto& vm = global_object.vm();
Expand All @@ -51,71 +53,74 @@ String JSONObject::stringify_impl(GlobalObject& global_object, Value value, Valu
if (replacer.is_object()) {
if (replacer.as_object().is_function()) {
state.replacer_function = &replacer.as_function();
} else if (replacer.is_array(global_object)) {
auto& replacer_object = replacer.as_object();
auto replacer_length = length_of_array_like(global_object, replacer_object);
} else {
auto is_array = replacer.is_array(global_object);
if (vm.exception())
return {};
Vector<String> list;
for (size_t i = 0; i < replacer_length; ++i) {
auto replacer_value = replacer_object.get(i);
if (is_array) {
auto& replacer_object = replacer.as_object();
auto replacer_length = length_of_array_like(global_object, replacer_object);
if (vm.exception())
return {};
String item;
if (replacer_value.is_string() || replacer_value.is_number()) {
item = replacer_value.to_string(global_object);
Vector<String> list;
for (size_t i = 0; i < replacer_length; ++i) {
auto replacer_value = replacer_object.get(i);
if (vm.exception())
return {};
} else if (replacer_value.is_object()) {
auto& value_object = replacer_value.as_object();
if (is<StringObject>(value_object) || is<NumberObject>(value_object)) {
item = value_object.value_of().to_string(global_object);
if (vm.exception())
return {};
String item;
if (replacer_value.is_string()) {
item = replacer_value.as_string().string();
} else if (replacer_value.is_number()) {
item = replacer_value.to_string(global_object);
} else if (replacer_value.is_object()) {
auto& value_object = replacer_value.as_object();
if (is<StringObject>(value_object) || is<NumberObject>(value_object)) {
item = replacer_value.to_string(global_object);
if (vm.exception())
return {};
}
}
if (!item.is_null() && !list.contains_slow(item)) {
list.append(item);
}
}
if (!item.is_null() && !list.contains_slow(item)) {
list.append(item);
}
state.property_list = list;
}
state.property_list = list;
}
if (vm.exception())
return {};
}

if (space.is_object()) {
auto& space_obj = space.as_object();
if (is<StringObject>(space_obj) || is<NumberObject>(space_obj))
space = space_obj.value_of();
auto& space_object = space.as_object();
if (is<NumberObject>(space_object)) {
space = space.to_number(global_object);
if (vm.exception())
return {};
} else if (is<StringObject>(space_object)) {
space = space.to_primitive_string(global_object);
if (vm.exception())
return {};
}
}

if (space.is_number()) {
StringBuilder gap_builder;
auto gap_size = min(10, space.as_i32());
for (auto i = 0; i < gap_size; ++i)
gap_builder.append(' ');
state.gap = gap_builder.to_string();
auto space_mv = space.to_integer_or_infinity(global_object);
space_mv = min(10, space_mv);
state.gap = space_mv < 1 ? String::empty() : String::repeated(' ', space_mv);
} else if (space.is_string()) {
auto string = space.as_string().string();
if (string.length() <= 10) {
if (string.length() <= 10)
state.gap = string;
} else {
else
state.gap = string.substring(0, 10);
}
} else {
state.gap = String::empty();
}

auto* wrapper = Object::create(global_object, global_object.object_prototype());
wrapper->define_property(String::empty(), value);
if (vm.exception())
return {};
auto result = serialize_json_property(global_object, state, String::empty(), wrapper);
if (vm.exception())
return {};
if (result.is_null())
return {};

return result;
}
Expand All @@ -137,14 +142,18 @@ JS_DEFINE_NATIVE_FUNCTION(JSONObject::stringify)
return js_string(vm, string);
}

// 25.5.2.1 SerializeJSONProperty ( state, key, holder ), https://tc39.es/ecma262/#sec-serializejsonproperty
String JSONObject::serialize_json_property(GlobalObject& global_object, StringifyState& state, const PropertyName& key, Object* holder)
{
auto& vm = global_object.vm();
auto value = holder->get(key);
auto value = holder->get(key).value_or(js_undefined());
if (vm.exception())
return {};
if (value.is_object()) {
auto to_json = value.as_object().get(vm.names.toJSON);
if (value.is_object() || value.is_bigint()) {
auto* value_object = value.to_object(global_object);
if (vm.exception())
return {};
auto to_json = value_object->get(vm.names.toJSON);
if (vm.exception())
return {};
if (to_json.is_function()) {
Expand All @@ -162,8 +171,19 @@ String JSONObject::serialize_json_property(GlobalObject& global_object, Stringif

if (value.is_object()) {
auto& value_object = value.as_object();
if (is<NumberObject>(value_object) || is<BooleanObject>(value_object) || is<StringObject>(value_object) || is<BigIntObject>(value_object))
value = value_object.value_of();
if (is<NumberObject>(value_object)) {
value = value.to_number(global_object);
if (vm.exception())
return {};
} else if (is<StringObject>(value_object)) {
value = value.to_primitive_string(global_object);
if (vm.exception())
return {};
} else if (is<BooleanObject>(value_object)) {
value = static_cast<BooleanObject&>(value_object).value_of();
} else if (is<BigIntObject>(value_object)) {
value = static_cast<BigIntObject&>(value_object).value_of();
}
}

if (value.is_null())
Expand All @@ -177,18 +197,29 @@ String JSONObject::serialize_json_property(GlobalObject& global_object, Stringif
return value.to_string(global_object);
return "null";
}
if (value.is_bigint()) {
vm.throw_exception<TypeError>(global_object, ErrorType::JsonBigInt);
return {};
}
if (value.is_object() && !value.is_function()) {
if (value.is_array(global_object))
return serialize_json_array(global_object, state, static_cast<Array&>(value.as_object()));
auto is_array = value.is_array(global_object);
if (vm.exception())
return {};
if (is_array) {
auto result = serialize_json_array(global_object, state, static_cast<Array&>(value.as_object()));
if (vm.exception())
return {};
return result;
}
auto result = serialize_json_object(global_object, state, value.as_object());
if (vm.exception())
return {};
return serialize_json_object(global_object, state, value.as_object());
return result;
}
if (value.is_bigint())
vm.throw_exception<TypeError>(global_object, ErrorType::JsonBigInt);
return {};
}

// 25.5.2.4 SerializeJSONObject ( state, value ), https://tc39.es/ecma262/#sec-serializejsonobject
String JSONObject::serialize_json_object(GlobalObject& global_object, StringifyState& state, Object& object)
{
auto& vm = global_object.vm();
Expand Down Expand Up @@ -225,18 +256,11 @@ String JSONObject::serialize_json_object(GlobalObject& global_object, StringifyS
return {};
}
} else {
for (auto& entry : object.indexed_properties()) {
auto value_and_attributes = entry.value_and_attributes(&object);
if (!value_and_attributes.attributes.is_enumerable())
continue;
process_property(entry.index());
if (vm.exception())
return {};
}
for (auto& [key, metadata] : object.shape().property_table_ordered()) {
if (!metadata.attributes.is_enumerable())
continue;
process_property(key);
auto property_list = object.get_enumerable_own_property_names(PropertyKind::Key);
if (vm.exception())
return {};
for (auto& property : property_list) {
process_property(property.as_string().string());
if (vm.exception())
return {};
}
Expand Down Expand Up @@ -275,6 +299,7 @@ String JSONObject::serialize_json_object(GlobalObject& global_object, StringifyS
return builder.to_string();
}

// 25.5.2.5 SerializeJSONArray ( state, value ), https://tc39.es/ecma262/#sec-serializejsonarray
String JSONObject::serialize_json_array(GlobalObject& global_object, StringifyState& state, Object& object)
{
auto& vm = global_object.vm();
Expand All @@ -291,6 +316,10 @@ String JSONObject::serialize_json_array(GlobalObject& global_object, StringifySt
auto length = length_of_array_like(global_object, object);
if (vm.exception())
return {};

// Optimization
property_strings.ensure_capacity(length);

for (size_t i = 0; i < length; ++i) {
if (vm.exception())
return {};
Expand Down Expand Up @@ -340,13 +369,15 @@ String JSONObject::serialize_json_array(GlobalObject& global_object, StringifySt
return builder.to_string();
}

// 25.5.2.2 QuoteJSONString ( value ), https://tc39.es/ecma262/#sec-quotejsonstring
String JSONObject::quote_json_string(String string)
{
// FIXME: Handle UTF16
StringBuilder builder;
builder.append('"');
for (auto& ch : string) {
switch (ch) {
auto utf_view = Utf8View(string);
for (auto code_point : utf_view) {
switch (code_point) {
case '\b':
builder.append("\\b");
break;
Expand All @@ -369,10 +400,10 @@ String JSONObject::quote_json_string(String string)
builder.append("\\\\");
break;
default:
if (ch < 0x20) {
builder.appendff("\\u{:04x}", ch);
if (code_point < 0x20) {
builder.appendff("\\u{:04x}", code_point);
} else {
builder.append(ch);
builder.append_code_point(code_point);
}
}
}
Expand Down

0 comments on commit 8d50cf4

Please sign in to comment.