summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibJS/Runtime/JSONObject.cpp
diff options
context:
space:
mode:
authorIdan Horowitz <idan.horowitz@gmail.com>2021-07-01 03:24:04 +0300
committerAndreas Kling <kling@serenityos.org>2021-07-01 11:44:37 +0200
commit8d50cf492e9160ddc1059f24dbea77c6511e91f0 (patch)
tree26ab175a5b54868f26db085be42edbc5688d5c19 /Userland/Libraries/LibJS/Runtime/JSONObject.cpp
parent172d81a717a091f10f1dfb5049576031889fa0c7 (diff)
downloadserenity-8d50cf492e9160ddc1059f24dbea77c6511e91f0.zip
LibJS: Bring JSON.stringify closer to the specification
Diffstat (limited to 'Userland/Libraries/LibJS/Runtime/JSONObject.cpp')
-rw-r--r--Userland/Libraries/LibJS/Runtime/JSONObject.cpp157
1 files changed, 94 insertions, 63 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/JSONObject.cpp b/Userland/Libraries/LibJS/Runtime/JSONObject.cpp
index fc3245ab4b..6580f2a656 100644
--- a/Userland/Libraries/LibJS/Runtime/JSONObject.cpp
+++ b/Userland/Libraries/LibJS/Runtime/JSONObject.cpp
@@ -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>
@@ -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();
@@ -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;
}
@@ -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()) {
@@ -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())
@@ -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();
@@ -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 {};
}
@@ -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();
@@ -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 {};
@@ -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;
@@ -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);
}
}
}