From fe2dff4944955e3a36ffc28c2ef0f6d46d8ed7f5 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 17 Feb 2025 15:08:17 -0500 Subject: [PATCH] AK+Everywhere: Convert JSON value serialization to String This removes the use of StringBuilder::OutputType (which was ByteString, and only used by the JSON classes). And it removes the StringBuilder template parameter from the serialization methods; this was only ever used with StringBuilder, so a template is pretty overkill here. --- AK/CMakeLists.txt | 1 + AK/JsonArray.cpp | 30 ++++++++++++++++ AK/JsonArray.h | 25 ++----------- AK/JsonObject.cpp | 21 ++++++++++- AK/JsonObject.h | 52 ++------------------------- AK/JsonValue.cpp | 25 +++++++++++++ AK/JsonValue.h | 9 ++--- AK/StringBuilder.h | 1 - Libraries/LibCore/ArgsParser.cpp | 2 +- Libraries/LibDevTools/Connection.cpp | 5 ++- Libraries/LibIPC/Encoder.cpp | 2 +- Libraries/LibTest/TestRunner.h | 2 +- Libraries/LibWeb/WebDriver/Client.cpp | 8 ++--- Tests/AK/TestJSON.cpp | 6 ++-- Tests/LibJS/test262-runner.cpp | 4 +-- 15 files changed, 98 insertions(+), 95 deletions(-) create mode 100644 AK/JsonArray.cpp diff --git a/AK/CMakeLists.txt b/AK/CMakeLists.txt index 3fbdc65b90a..6705f945379 100644 --- a/AK/CMakeLists.txt +++ b/AK/CMakeLists.txt @@ -13,6 +13,7 @@ set(SOURCES Format.cpp GenericLexer.cpp Hex.cpp + JsonArray.cpp JsonObject.cpp JsonParser.cpp JsonValue.cpp diff --git a/AK/JsonArray.cpp b/AK/JsonArray.cpp new file mode 100644 index 00000000000..64d6d57d69f --- /dev/null +++ b/AK/JsonArray.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace AK { + +String JsonArray::serialized() const +{ + StringBuilder builder; + serialize(builder); + + return MUST(builder.to_string()); +} + +void JsonArray::serialize(StringBuilder& builder) const +{ + auto serializer = MUST(JsonArraySerializer<>::try_create(builder)); + for_each([&](auto const& value) { + MUST(serializer.add(value)); + }); + MUST(serializer.finish()); +} + +} diff --git a/AK/JsonArray.h b/AK/JsonArray.h index 955c3ebe8ac..bd0106d1b31 100644 --- a/AK/JsonArray.h +++ b/AK/JsonArray.h @@ -8,8 +8,8 @@ #include #include -#include #include +#include #include namespace AK { @@ -72,11 +72,8 @@ public: ErrorOr append(JsonValue value) { return m_values.try_append(move(value)); } void set(size_t index, JsonValue value) { m_values.at(index) = move(value); } - template - typename Builder::OutputType serialized() const; - - template - void serialize(Builder&) const; + String serialized() const; + void serialize(StringBuilder&) const; template void for_each(Callback callback) @@ -118,22 +115,6 @@ private: Vector m_values; }; -template -inline void JsonArray::serialize(Builder& builder) const -{ - auto serializer = MUST(JsonArraySerializer<>::try_create(builder)); - for_each([&](auto& value) { MUST(serializer.add(value)); }); - MUST(serializer.finish()); -} - -template -inline typename Builder::OutputType JsonArray::serialized() const -{ - Builder builder; - serialize(builder); - return builder.to_byte_string(); -} - } #if USING_AK_GLOBALLY diff --git a/AK/JsonObject.cpp b/AK/JsonObject.cpp index 25c275481f6..294667ca076 100644 --- a/AK/JsonObject.cpp +++ b/AK/JsonObject.cpp @@ -6,7 +6,9 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include "JsonObject.h" +#include +#include +#include namespace AK { @@ -275,4 +277,21 @@ bool JsonObject::remove(StringView key) return m_members.remove(key); } +String JsonObject::serialized() const +{ + StringBuilder builder; + serialize(builder); + + return MUST(builder.to_string()); +} + +void JsonObject::serialize(StringBuilder& builder) const +{ + auto serializer = MUST(JsonObjectSerializer<>::try_create(builder)); + for_each_member([&](auto& key, auto& value) { + MUST(serializer.add(key, value)); + }); + MUST(serializer.finish()); +} + } diff --git a/AK/JsonObject.h b/AK/JsonObject.h index a6ba51be7df..b6ecd217362 100644 --- a/AK/JsonObject.h +++ b/AK/JsonObject.h @@ -8,12 +8,10 @@ #pragma once -#include #include #include #include #include -#include #include #include @@ -107,59 +105,13 @@ public: bool remove(StringView key); - template - typename Builder::OutputType serialized() const; - - template - void serialize(Builder&) const; + String serialized() const; + void serialize(StringBuilder&) const; private: OrderedHashMap m_members; }; -template -inline void JsonObject::serialize(Builder& builder) const -{ - auto serializer = MUST(JsonObjectSerializer<>::try_create(builder)); - for_each_member([&](auto& key, auto& value) { - MUST(serializer.add(key, value)); - }); - MUST(serializer.finish()); -} - -template -inline typename Builder::OutputType JsonObject::serialized() const -{ - Builder builder; - serialize(builder); - return builder.to_byte_string(); -} - -template -inline void JsonValue::serialize(Builder& builder) const -{ - m_value.visit( - [&](Empty const&) { builder.append("null"sv); }, - [&](bool const& value) { builder.append(value ? "true"sv : "false"sv); }, - [&](Arithmetic auto const& value) { builder.appendff("{}", value); }, - [&](String const& value) { - builder.append('\"'); - builder.append_escaped_for_json(value.bytes()); - builder.append('\"'); - }, - [&](auto const& array_or_object) { - array_or_object->serialize(builder); - }); -} - -template -inline typename Builder::OutputType JsonValue::serialized() const -{ - Builder builder; - serialize(builder); - return builder.to_byte_string(); -} - } #if USING_AK_GLOBALLY diff --git a/AK/JsonValue.cpp b/AK/JsonValue.cpp index 7f576ca7fa3..a816172f0e5 100644 --- a/AK/JsonValue.cpp +++ b/AK/JsonValue.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include namespace AK { @@ -192,4 +193,28 @@ ErrorOr JsonValue::from_string(StringView input) return JsonParser(input).parse(); } +String JsonValue::serialized() const +{ + StringBuilder builder; + serialize(builder); + + return MUST(builder.to_string()); +} + +void JsonValue::serialize(StringBuilder& builder) const +{ + m_value.visit( + [&](Empty const&) { builder.append("null"sv); }, + [&](bool const& value) { builder.append(value ? "true"sv : "false"sv); }, + [&](Arithmetic auto const& value) { builder.appendff("{}", value); }, + [&](String const& value) { + builder.append('\"'); + builder.append_escaped_for_json(value.bytes()); + builder.append('\"'); + }, + [&](auto const& array_or_object) { + array_or_object->serialize(builder); + }); +} + } diff --git a/AK/JsonValue.h b/AK/JsonValue.h index d0fee56f229..c5dde523ec9 100644 --- a/AK/JsonValue.h +++ b/AK/JsonValue.h @@ -11,7 +11,6 @@ #include #include #include -#include namespace AK { @@ -77,10 +76,8 @@ public: JsonValue& operator=(JsonArray&&); JsonValue& operator=(JsonObject&&); - template - typename Builder::OutputType serialized() const; - template - void serialize(Builder&) const; + String serialized() const; + void serialize(StringBuilder&) const; Optional get_int() const { return get_integer(); } Optional get_i32() const { return get_integer(); } @@ -224,7 +221,7 @@ template<> struct Formatter : Formatter { ErrorOr format(FormatBuilder& builder, JsonValue const& value) { - return Formatter::format(builder, value.serialized()); + return Formatter::format(builder, value.serialized()); } }; diff --git a/AK/StringBuilder.h b/AK/StringBuilder.h index d7146dd4bc8..e1cbc320971 100644 --- a/AK/StringBuilder.h +++ b/AK/StringBuilder.h @@ -19,7 +19,6 @@ public: static constexpr size_t inline_capacity = 256; using Buffer = Detail::ByteBuffer; - using OutputType = ByteString; static ErrorOr create(size_t initial_capacity = inline_capacity); diff --git a/Libraries/LibCore/ArgsParser.cpp b/Libraries/LibCore/ArgsParser.cpp index 724defa9954..9afbd5851b9 100644 --- a/Libraries/LibCore/ArgsParser.cpp +++ b/Libraries/LibCore/ArgsParser.cpp @@ -754,7 +754,7 @@ void ArgsParser::autocomplete(FILE* file, StringView program_name, ReadonlySpan< object.set("invariant_offset"sv, has_invariant ? option_to_complete.length() : 0u); object.set("display_trivia"sv, StringView { option.help_string, strlen(option.help_string) }); object.set("trailing_trivia"sv, option.argument_mode == OptionArgumentMode::Required ? " "sv : ""sv); - outln(file, "{}", object.serialized()); + outln(file, "{}", object.serialized()); }; if (option_to_complete.starts_with("--"sv)) { diff --git a/Libraries/LibDevTools/Connection.cpp b/Libraries/LibDevTools/Connection.cpp index e34646f9315..3bf9941893f 100644 --- a/Libraries/LibDevTools/Connection.cpp +++ b/Libraries/LibDevTools/Connection.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include @@ -34,7 +33,7 @@ Connection::~Connection() = default; // https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#packets void Connection::send_message(JsonValue const& message) { - auto serialized = message.serialized(); + auto serialized = message.serialized(); if constexpr (DEVTOOLS_DEBUG) { if (message.is_object() && message.as_object().get("error"sv).has_value()) @@ -43,7 +42,7 @@ void Connection::send_message(JsonValue const& message) dbgln("\x1b[1;32m<<\x1b[0m {}", serialized); } - if (m_socket->write_formatted("{}:{}", serialized.length(), serialized).is_error()) { + if (m_socket->write_formatted("{}:{}", serialized.byte_count(), serialized).is_error()) { if (on_connection_closed) on_connection_closed(); } diff --git a/Libraries/LibIPC/Encoder.cpp b/Libraries/LibIPC/Encoder.cpp index 22c8e3ef90e..8d7d76216e9 100644 --- a/Libraries/LibIPC/Encoder.cpp +++ b/Libraries/LibIPC/Encoder.cpp @@ -82,7 +82,7 @@ ErrorOr encode(Encoder& encoder, ByteBuffer const& value) template<> ErrorOr encode(Encoder& encoder, JsonValue const& value) { - return encoder.encode(value.serialized()); + return encoder.encode(value.serialized()); } template<> diff --git a/Libraries/LibTest/TestRunner.h b/Libraries/LibTest/TestRunner.h index 1303563f1ae..5670e442d7e 100644 --- a/Libraries/LibTest/TestRunner.h +++ b/Libraries/LibTest/TestRunner.h @@ -274,7 +274,7 @@ inline void TestRunner::print_test_results_as_json() const root.set("files_total"sv, m_counts.files_total); root.set("duration"sv, m_total_elapsed_time_in_ms / 1000.0); } - outln("{}", root.serialized()); + outln("{}", root.serialized()); } } diff --git a/Libraries/LibWeb/WebDriver/Client.cpp b/Libraries/LibWeb/WebDriver/Client.cpp index 24106c2920a..cb44ffe1e6a 100644 --- a/Libraries/LibWeb/WebDriver/Client.cpp +++ b/Libraries/LibWeb/WebDriver/Client.cpp @@ -298,7 +298,7 @@ ErrorOr Client::send_success_response(HTTP::HttpRequ keep_alive = it->value.trim_whitespace().equals_ignoring_ascii_case("keep-alive"sv); result = make_success_response(move(result)); - auto content = result.serialized(); + auto content = result.serialized(); StringBuilder builder; builder.append("HTTP/1.1 200 OK\r\n"sv); @@ -309,7 +309,7 @@ ErrorOr Client::send_success_response(HTTP::HttpRequ builder.append("Connection: keep-alive\r\n"sv); builder.append("Cache-Control: no-cache\r\n"sv); builder.append("Content-Type: application/json; charset=utf-8\r\n"sv); - builder.appendff("Content-Length: {}\r\n", content.length()); + builder.appendff("Content-Length: {}\r\n", content.byte_count()); builder.append("\r\n"sv); builder.append(content); @@ -338,13 +338,13 @@ ErrorOr Client::send_error_response(HTTP::HttpReques JsonObject result; result.set("value"sv, move(error_response)); - auto content = result.serialized(); + auto content = result.serialized(); StringBuilder builder; builder.appendff("HTTP/1.1 {} {}\r\n", error.http_status, reason); builder.append("Cache-Control: no-cache\r\n"sv); builder.append("Content-Type: application/json; charset=utf-8\r\n"sv); - builder.appendff("Content-Length: {}\r\n", content.length()); + builder.appendff("Content-Length: {}\r\n", content.byte_count()); builder.append("\r\n"sv); builder.append(content); diff --git a/Tests/AK/TestJSON.cpp b/Tests/AK/TestJSON.cpp index ac692315692..2bb84107470 100644 --- a/Tests/AK/TestJSON.cpp +++ b/Tests/AK/TestJSON.cpp @@ -160,13 +160,13 @@ TEST_CASE(json_duplicate_keys) json.set("test"sv, "foo"sv); json.set("test"sv, "bar"sv); json.set("test"sv, "baz"sv); - EXPECT_EQ(json.serialized(), "{\"test\":\"baz\"}"); + EXPECT_EQ(json.serialized(), "{\"test\":\"baz\"}"); } TEST_CASE(json_u64_roundtrip) { auto big_value = 0xffffffffffffffffull; - auto json = JsonValue(big_value).serialized(); + auto json = JsonValue(big_value).serialized(); auto value = JsonValue::from_string(json); EXPECT_EQ_FORCE(value.is_error(), false); EXPECT_EQ(value.value().as_integer(), big_value); @@ -531,7 +531,7 @@ TEST_CASE(json_array_serialized) auto raw_json = R"(["Hello",2,3.14,4,"World"])"sv; auto json_value = MUST(JsonValue::from_string(raw_json)); auto array = json_value.as_array(); - auto const& serialized_json = array.serialized(); + auto const& serialized_json = array.serialized(); EXPECT_EQ(serialized_json, raw_json); } diff --git a/Tests/LibJS/test262-runner.cpp b/Tests/LibJS/test262-runner.cpp index 1ac8e6abb69..4ca0745ab33 100644 --- a/Tests/LibJS/test262-runner.cpp +++ b/Tests/LibJS/test262-runner.cpp @@ -532,7 +532,7 @@ static bool g_in_assert = false; assert_fail_result.set("assert_fail"sv, true); assert_fail_result.set("result"sv, "assert_fail"sv); assert_fail_result.set("output"sv, StringView { assert_failed_message, strlen(assert_failed_message) }); - outln(saved_stdout_fd, "RESULT {}{}", assert_fail_result.serialized(), '\0'); + outln(saved_stdout_fd, "RESULT {}{}", assert_fail_result.serialized(), '\0'); // (Attempt to) Ensure that messages are written before quitting. fflush(saved_stdout_fd); fflush(stderr); @@ -733,7 +733,7 @@ int main(int argc, char** argv) result_object.set("test"sv, path); ScopeGuard output_guard = [&] { - outln(saved_stdout_fd, "RESULT {}{}", result_object.serialized(), '\0'); + outln(saved_stdout_fd, "RESULT {}{}", result_object.serialized(), '\0'); fflush(saved_stdout_fd); };