diff options
author | Idan Horowitz <idan.horowitz@gmail.com> | 2021-07-01 03:24:04 +0300 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-07-01 11:44:37 +0200 |
commit | 8d50cf492e9160ddc1059f24dbea77c6511e91f0 (patch) | |
tree | 26ab175a5b54868f26db085be42edbc5688d5c19 /Userland/Libraries/LibJS/Runtime/JSONObject.cpp | |
parent | 172d81a717a091f10f1dfb5049576031889fa0c7 (diff) | |
download | serenity-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.cpp | 157 |
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); } } } |