Kaynağa Gözat

LibJS: Bring JSON.stringify closer to the specification

Idan Horowitz 4 yıl önce
ebeveyn
işleme
8d50cf492e
1 değiştirilmiş dosya ile 94 ekleme ve 63 silme
  1. 94 63
      Userland/Libraries/LibJS/Runtime/JSONObject.cpp

+ 94 - 63
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);
             }
         }
     }