diff options
author | Matthew Olsson <matthewcolsson@gmail.com> | 2020-06-10 11:01:00 -0700 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-06-13 12:43:22 +0200 |
commit | 39576b22385a2e6b6fc4fbf5e90e6b72157e9ee2 (patch) | |
tree | 780b45d855c52048c36a73adac9caaf49ff918e7 /Libraries/LibJS/Runtime/JSONObject.cpp | |
parent | b4577ffcf31ef521dcf914d783403f5a57af96b5 (diff) | |
download | serenity-39576b22385a2e6b6fc4fbf5e90e6b72157e9ee2.zip |
LibJS: Add JSON.stringify
Diffstat (limited to 'Libraries/LibJS/Runtime/JSONObject.cpp')
-rw-r--r-- | Libraries/LibJS/Runtime/JSONObject.cpp | 373 |
1 files changed, 373 insertions, 0 deletions
diff --git a/Libraries/LibJS/Runtime/JSONObject.cpp b/Libraries/LibJS/Runtime/JSONObject.cpp new file mode 100644 index 0000000000..dae2b5af5f --- /dev/null +++ b/Libraries/LibJS/Runtime/JSONObject.cpp @@ -0,0 +1,373 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <AK/StringBuilder.h> +#include <LibJS/Interpreter.h> +#include <LibJS/Runtime/Array.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/JSONObject.h> +#include <LibJS/Runtime/Object.h> + +namespace JS { + +JSONObject::JSONObject() + : Object(interpreter().global_object().object_prototype()) +{ + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function("stringify", stringify, 3, attr); + define_native_function("parse", parse, 1, attr); +} + +JSONObject::~JSONObject() +{ +} + +Value JSONObject::stringify(Interpreter& interpreter) +{ + if (!interpreter.argument_count()) + return js_undefined(); + auto value = interpreter.argument(0); + auto replacer = interpreter.argument(1); + auto space = interpreter.argument(2); + + StringifyState state; + + if (replacer.is_object()) { + if (replacer.as_object().is_function()) { + state.replacer_function = &replacer.as_function(); + } else if (replacer.is_array()) { + auto& replacer_object = replacer.as_object(); + auto replacer_length = length_of_array_like(interpreter, replacer); + if (interpreter.exception()) + return {}; + Vector<String> list; + for (size_t i = 0; i < replacer_length; ++i) { + auto replacer_value = replacer_object.get(i); + if (interpreter.exception()) + return {}; + String item; + if (replacer_value.is_string() || replacer_value.is_number()) { + item = replacer_value.to_string(interpreter); + if (interpreter.exception()) + return {}; + } else if (replacer_value.is_object()) { + auto& value_object = replacer_value.as_object(); + if (value_object.is_string_object() || value_object.is_number_object()) { + item = value_object.value_of().to_string(interpreter); + if (interpreter.exception()) + return { }; + } + } + if (!item.is_null() && !list.contains_slow(item)) { + list.append(item); + } + } + state.property_list = list; + } + } + + if (space.is_object()) { + auto& space_obj = space.as_object(); + if (space_obj.is_string_object() || space_obj.is_number_object()) + space = space_obj.value_of(); + } + + 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(); + } else if (space.is_string()) { + auto string = space.as_string().string(); + if (string.length() <= 10) { + state.gap = string; + } else { + state.gap = string.substring(0, 10); + } + } else { + state.gap = String::empty(); + } + + auto* wrapper = Object::create_empty(interpreter, interpreter.global_object()); + wrapper->define_property(String::empty(), value); + if (interpreter.exception()) + return {}; + auto result = serialize_json_property(interpreter, state, String::empty(), wrapper); + if (interpreter.exception()) + return {}; + if (result.is_null()) + return js_undefined(); + return js_string(interpreter, result); +} + +String JSONObject::serialize_json_property(Interpreter& interpreter, StringifyState& state, const PropertyName& key, Object* holder) +{ + auto value = holder->get(key); + if (value.is_object()) { + auto to_json = value.as_object().get("toJSON"); + if (interpreter.exception()) + return {}; + if (to_json.is_function()) { + MarkedValueList arguments(interpreter.heap()); + arguments.append(js_string(interpreter, key.to_string())); + value = interpreter.call(to_json.as_function(), value, move(arguments)); + if (interpreter.exception()) + return {}; + } + } + + if (state.replacer_function) { + MarkedValueList arguments(interpreter.heap()); + arguments.values().append(js_string(interpreter, key.to_string())); + arguments.values().append(value); + value = interpreter.call(*state.replacer_function, holder, move(arguments)); + if (interpreter.exception()) + return {}; + } + + if (value.is_object()) { + auto& value_object = value.as_object(); + if (value_object.is_number_object() || value_object.is_boolean_object() || value_object.is_string_object() || value_object.is_bigint_object()) + value = value_object.value_of(); + } + + if (value.is_null()) + return "null"; + if (value.is_boolean()) + return value.as_bool() ? "true" : "false"; + if (value.is_string()) + return quote_json_string(value.as_string().string()); + if (value.is_number()) { + if (value.is_finite_number()) + return value.to_string(interpreter); + return "null"; + } + if (value.is_object() && !value.is_function()) { + if (value.is_array()) + return serialize_json_array(interpreter, state, static_cast<Array&>(value.as_object())); + return serialize_json_object(interpreter, state, value.as_object()); + } + if (value.is_bigint()) + interpreter.throw_exception<TypeError>(ErrorType::JsonBigInt); + return {}; +} + +String JSONObject::serialize_json_object(Interpreter& interpreter, StringifyState& state, Object& object) +{ + if (state.seen_objects.contains(&object)) { + interpreter.throw_exception<TypeError>(ErrorType::JsonCircular); + return {}; + } + + state.seen_objects.set(&object); + String previous_indent = state.indent; + state.indent = String::format("%s%s", state.indent.characters(), state.gap.characters()); + Vector<String> property_strings; + + auto process_property = [&](const PropertyName& key) { + auto serialized_property_string = serialize_json_property(interpreter, state, key, &object); + if (interpreter.exception()) + return; + if (!serialized_property_string.is_null()) { + property_strings.append(String::format( + "%s:%s%s", + quote_json_string(key.to_string()).characters(), + state.gap.is_empty() ? "" : " ", + serialized_property_string.characters() + )); + } + }; + + if (state.property_list.has_value()) { + auto property_list = state.property_list.value(); + for (auto& property : property_list) { + process_property(property); + if (interpreter.exception()) + 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 (interpreter.exception()) + return {}; + } + for (auto&[key, metadata] : object.shape().property_table_ordered()) { + if (!metadata.attributes.is_enumerable()) + continue; + process_property(key); + if (interpreter.exception()) + return {}; + } + } + StringBuilder builder; + if (property_strings.is_empty()) { + builder.append("{}"); + } else { + bool first = true; + builder.append('{'); + if (state.gap.is_empty()) { + for (auto& property_string : property_strings) { + if (!first) + builder.append(','); + first = false; + builder.append(property_string); + } + } else { + builder.append('\n'); + builder.append(state.indent); + auto separator = String::format(",\n%s", state.indent.characters()); + for (auto& property_string : property_strings) { + if (!first) + builder.append(separator); + first = false; + builder.append(property_string); + } + builder.append('\n'); + builder.append(previous_indent); + } + builder.append('}'); + } + + state.seen_objects.remove(&object); + state.indent = previous_indent; + return builder.to_string(); +} + +String JSONObject::serialize_json_array(Interpreter& interpreter, StringifyState& state, Object& object) +{ + if (state.seen_objects.contains(&object)) { + interpreter.throw_exception<TypeError>(ErrorType::JsonCircular); + return {}; + } + + state.seen_objects.set(&object); + String previous_indent = state.indent; + state.indent = String::format("%s%s", state.indent.characters(), state.gap.characters()); + Vector<String> property_strings; + + auto length = length_of_array_like(interpreter, Value(&object)); + if (interpreter.exception()) + return {}; + for (size_t i = 0; i < length; ++i) { + if (interpreter.exception()) + return {}; + auto serialized_property_string = serialize_json_property(interpreter, state, i, &object); + if (interpreter.exception()) + return {}; + if (serialized_property_string.is_null()) { + property_strings.append("null"); + } else { + property_strings.append(serialized_property_string); + } + } + + StringBuilder builder; + if (property_strings.is_empty()) { + builder.append("[]"); + } else { + if (state.gap.is_empty()) { + builder.append('['); + bool first = true; + for (auto& property_string : property_strings) { + if (!first) + builder.append(','); + first = false; + builder.append(property_string); + } + builder.append(']'); + } else { + builder.append("[\n"); + builder.append(state.indent); + auto separator = String::format(",\n%s", state.indent.characters()); + bool first = true; + for (auto& property_string : property_strings) { + if (!first) + builder.append(separator); + first = false; + builder.append(property_string); + } + builder.append('\n'); + builder.append(previous_indent); + builder.append(']'); + } + } + + state.seen_objects.remove(&object); + state.indent = previous_indent; + return builder.to_string(); +} + +String JSONObject::quote_json_string(String string) +{ + // FIXME: Handle UTF16 + StringBuilder builder; + builder.append('"'); + for (auto& ch : string) { + switch (ch) { + case '\b': + builder.append("\\b"); + break; + case '\t': + builder.append("\\t"); + break; + case '\n': + builder.append("\\n"); + break; + case '\f': + builder.append("\\f"); + break; + case '\r': + builder.append("\\r"); + break; + case '"': + builder.append("\\\""); + break; + case '\\': + builder.append("\\\\"); + break; + default: + if (ch < 0x20) { + builder.append("\\u%#08x", ch); + } else { + builder.append(ch); + } + } + } + builder.append('"'); + return builder.to_string(); +} + +Value JSONObject::parse(Interpreter&) +{ + return js_undefined(); +} + +} |