From 48bdfbeb03319eb21b5e73e69f525ba298af975c Mon Sep 17 00:00:00 2001 From: Joel Klinghed Date: Sun, 19 Oct 2025 00:08:49 +0200 Subject: json: Add new module Only has methods for writing JSON for now. Will let you create invalid json, but should assert if you do. --- src/json.cc | 305 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/json.hh | 46 +++++++++ 2 files changed, 351 insertions(+) create mode 100644 src/json.cc create mode 100644 src/json.hh (limited to 'src') diff --git a/src/json.cc b/src/json.cc new file mode 100644 index 0000000..94ff0d4 --- /dev/null +++ b/src/json.cc @@ -0,0 +1,305 @@ +#include "json.hh" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace json { + +namespace { + +struct StackEntry { + enum class Type : uint8_t { + kRoot, + kObject, + kArray, + }; + + Type type; + bool need_comma{false}; + + explicit StackEntry(Type type) : type(type) {} +}; + +class BaseWriter : public Writer { + public: + BaseWriter() : stack_({StackEntry(StackEntry::Type::kRoot)}) {} + + void value(std::string_view value) override { + before_value(); + quote(value); + } + + void value(int64_t value) override { + before_value(); + write(value); + } + + void value(uint64_t value) override { + before_value(); + write(value); + } + + void value(float value) override { + before_value(); + write(value); + } + + void value(double value) override { + before_value(); + write(value); + } + + void value(bool value) override { + before_value(); + write(value); + } + + void start_array() override { + before_value(); + stack_.emplace_back(StackEntry::Type::kArray); + write('['); + } + + void end_array() override { + if (stack_.empty()) { + assert(false); + return; + } + assert(stack_.back().type == StackEntry::Type::kArray); + stack_.pop_back(); + write(']'); + if (!stack_.empty()) + stack_.back().need_comma = true; + } + + void start_object() override { + before_value(); + stack_.emplace_back(StackEntry::Type::kObject); + write('{'); + } + + void key(std::string_view name) override { + if (stack_.empty()) { + assert(false); + return; + } + assert(stack_.back().type == StackEntry::Type::kObject); + if (stack_.back().need_comma) { + write(','); + stack_.back().need_comma = false; + } + quote(name); + write(':'); + } + + void end_object() override { + if (stack_.empty()) { + assert(false); + return; + } + assert(stack_.back().type == StackEntry::Type::kObject); + stack_.pop_back(); + write('}'); + if (!stack_.empty()) + stack_.back().need_comma = true; + } + + void clear() override { + stack_.clear(); + stack_.emplace_back(StackEntry::Type::kRoot); + } + + protected: + virtual void write(int64_t value) = 0; + virtual void write(uint64_t value) = 0; + virtual void write(float value) = 0; + virtual void write(double value) = 0; + virtual void write(bool value) = 0; + virtual void write(char value) = 0; + virtual void write(std::string_view value) = 0; + + void write(char const* value) { this->write(std::string_view(value)); } + + private: + void before_value() { + if (stack_.empty()) { + assert(false); + return; + } + if (stack_.back().need_comma) { + assert(stack_.back().type != StackEntry::Type::kRoot); + write(','); + } else { + stack_.back().need_comma = true; + } + } + + void quote(std::string_view str) { + write('"'); + size_t last = 0; + while (true) { + auto pos = need_quote(str, last); + if (pos == std::string_view::npos) { + write(str.substr(last)); + break; + } + write(str.substr(last, pos - last)); + switch (str[pos]) { + case '"': + write("\\\""); + break; + case '\\': + write("\\\\"); + break; + case '\n': + write("\\n"); + break; + case '\r': + write("\\r"); + break; + case '\t': + write("\\t"); + break; + case '\b': + write("\\b"); + break; + case '\f': + write("\\f"); + break; + default: { + char tmp[4]; + write("\\u"); + auto [ptr, ec] = std::to_chars(tmp, tmp + sizeof(tmp), str[pos], 16); + if (ec == std::errc()) { + size_t len = ptr - tmp; + assert(len > 0); + for (size_t i = 4; i > len; --i) + write('0'); + write(std::string_view(tmp, len)); + } else { + assert(false); + } + break; + }; + } + last = pos + 1; + } + write('"'); + } + + static inline size_t need_quote(std::string_view str, size_t offset) { + for (; offset < str.size(); ++offset) { + if (str[offset] == '\\' || str[offset] == '"' || + (str[offset] >= 0 && str[offset] < ' ')) + return offset; + } + return std::string_view::npos; + } + + std::vector stack_; +}; + +class IosWriter : public BaseWriter { + public: + explicit IosWriter(std::ostream& out) : out_(out) {} + + void write(std::string_view value) override { out_ << value; } + + void write(int64_t value) override { out_ << value; } + + void write(uint64_t value) override { out_ << value; } + + void write(float value) override { out_ << value; } + + void write(double value) override { out_ << value; } + + void write(bool value) override { out_ << value; } + + void write(char value) override { out_ << value; } + + private: + std::ostream& out_; +}; + +class StringWriter : public BaseWriter { + public: + explicit StringWriter(std::string& out) : out_(out) {} + + void write(std::string_view value) override { out_.append(value); } + + void write(int64_t value) override { + auto [ptr, ec] = std::to_chars(tmp_, tmp_ + sizeof(tmp_), value); + if (ec == std::errc()) { + out_.append(tmp_, ptr - tmp_); + } else { + assert(false); + } + } + + void write(uint64_t value) override { + auto [ptr, ec] = std::to_chars(tmp_, tmp_ + sizeof(tmp_), value); + if (ec == std::errc()) { + out_.append(tmp_, ptr - tmp_); + } else { + assert(false); + } + } + + void write(float value) override { + auto [ptr, ec] = std::to_chars(tmp_, tmp_ + sizeof(tmp_), value); + if (ec == std::errc()) { + out_.append(tmp_, ptr - tmp_); + } else { + assert(false); + } + } + + void write(double value) override { + auto [ptr, ec] = std::to_chars(tmp_, tmp_ + sizeof(tmp_), value); + if (ec == std::errc()) { + out_.append(tmp_, ptr - tmp_); + } else { + assert(false); + } + } + + void write(bool value) override { out_.append(value ? "true" : "false"); } + + void write(char value) override { out_.push_back(value); } + + void clear() override { + BaseWriter::clear(); + out_.clear(); + } + + private: + std::string& out_; + char tmp_[100]; +}; + +} // namespace + +void Writer::value(char const* value) { this->value(std::string_view(value)); } + +void Writer::value(int value) { + static_assert(sizeof(int) <= sizeof(int64_t)); + this->value(static_cast(value)); +} + +std::unique_ptr writer(std::string& out) { + return std::make_unique(out); +} + +std::unique_ptr writer(std::ostream& out) { + return std::make_unique(out); +} + +} // namespace json diff --git a/src/json.hh b/src/json.hh new file mode 100644 index 0000000..d872dde --- /dev/null +++ b/src/json.hh @@ -0,0 +1,46 @@ +#ifndef JSON_HH +#define JSON_HH + +#include +#include +#include +#include +#include + +namespace json { + +class Writer { + public: + virtual ~Writer() = default; + + virtual void value(std::string_view value) = 0; + virtual void value(int64_t value) = 0; + virtual void value(uint64_t value) = 0; + virtual void value(float value) = 0; + virtual void value(double value) = 0; + virtual void value(bool value) = 0; + + void value(char const* value); + void value(int value); + + virtual void start_array() = 0; + virtual void end_array() = 0; + + virtual void start_object() = 0; + virtual void key(std::string_view name) = 0; + virtual void end_object() = 0; + + virtual void clear() = 0; + + protected: + Writer() = default; + Writer(Writer const&) = delete; + Writer& operator=(Writer const&) = delete; +}; + +std::unique_ptr writer(std::string& out); +std::unique_ptr writer(std::ostream& out); + +} // namespace json + +#endif // JSON_HH -- cgit v1.2.3-70-g09d2