4#ifndef DASMIG_CORE_JSON_HPP
5#define DASMIG_CORE_JSON_HPP
19#include <system_error>
54 _out.reserve(initial_capacity_);
55 _stack.reserve(initial_depth_);
65 _stack.push_back({.type = kind::object});
75 if (_stack.empty() || _stack.back().type != kind::object || _stack.back().key_pending)
77 throw std::logic_error(
"json_writer: end_object without a matching open object");
92 _stack.push_back({.type = kind::array});
101 if (_stack.empty() || _stack.back().type != kind::array)
103 throw std::logic_error(
"json_writer: end_array without a matching open array");
118 if (_stack.empty() || _stack.back().type != kind::object || _stack.back().key_pending)
120 throw std::logic_error(
"json_writer: key outside an object or after another key");
122 if (_stack.back().has_items) _out +=
',';
125 _stack.back().key_pending =
true;
152 _out += b ?
"true" :
"false";
171 template <std::
integral T>
172 requires(!std::same_as<T, bool> && !std::same_as<T, char> &&
173 !std::same_as<T, wchar_t> && !std::same_as<T, char8_t> &&
174 !std::same_as<T, char16_t> && !std::same_as<T, char32_t>)
178 std::array<char, 24> buf{};
179 const auto res = std::to_chars(buf.data(), std::to_address(buf.end()), n);
180 _out.append(buf.data(), res.ptr);
192 if (!std::isfinite(d))
194 throw std::invalid_argument(
"json_writer: NaN and infinity are not JSON");
197 std::array<char, 32> buf{};
198 const auto res = std::to_chars(buf.data(), std::to_address(buf.end()), d);
199 _out.append(buf.data(), res.ptr);
208 template <
typename V>
217 [[nodiscard]]
bool complete() const noexcept {
return _done && _stack.empty(); }
222 [[nodiscard]]
const std::string&
str()
const
226 throw std::logic_error(
"json_writer: document is incomplete");
235 static void escape(std::string& out, std::string_view text)
240 std::size_t plain = 0;
241 while (plain < text.size() && needs_no_escape(text[plain]) &&
static_cast<std::uint8_t
>(text[plain]) < 0x80U)
245 if (plain < text.size() && !
is_utf8(text.substr(plain)))
247 throw std::invalid_argument(
"json_writer: string is not valid UTF-8");
249 constexpr std::string_view hex =
"0123456789abcdef";
253 for (std::size_t i = plain; i < text.size(); ++i)
255 const char c = text[i];
256 if (needs_no_escape(c))
continue;
257 const auto b =
static_cast<std::uint8_t
>(c);
258 out.append(text.substr(run, i - run));
262 case '"': out +=
"\\\"";
break;
263 case '\\': out +=
"\\\\";
break;
264 case '\b': out +=
"\\b";
break;
265 case '\f': out +=
"\\f";
break;
266 case '\n': out +=
"\\n";
break;
267 case '\r': out +=
"\\r";
break;
268 case '\t': out +=
"\\t";
break;
272 out += hex[b & 0x0FU];
275 out.append(text.substr(run));
280 enum class kind : std::uint8_t
288 static constexpr std::size_t initial_capacity_ = 1024;
289 static constexpr std::size_t initial_depth_ = 8;
292 [[nodiscard]]
static constexpr bool needs_no_escape(
char c)
noexcept
294 return static_cast<std::uint8_t
>(c) >= 0x20U && c !=
'"' && c !=
'\\';
300 bool has_items{
false};
301 bool key_pending{
false};
308 if (_done)
throw std::logic_error(
"json_writer: a document holds one value");
311 auto& top = _stack.back();
312 if (top.type == kind::object)
314 if (!top.key_pending)
316 throw std::logic_error(
"json_writer: object value without a key");
319 else if (top.has_items)
332 auto& top = _stack.back();
333 top.has_items =
true;
334 top.key_pending =
false;
337 void write_string(std::string_view text) {
escape(_out, text); }
340 std::vector<frame> _stack;
Builds one JSON document.
json_writer & begin_object()
Open an object.
bool complete() const noexcept
Whether exactly one complete value has been written.
json_writer()
An empty writer, with room for a typical document.
json_writer & key(std::string_view name)
Write an object key; the next call writes its value.
json_writer & field(std::string_view name, const V &v)
Write a key and its value.
json_writer & value(T n)
Write an integer.
json_writer & begin_array()
Open an array.
json_writer & value(std::nullptr_t)
Write null.
static void escape(std::string &out, std::string_view text)
Append a JSON string literal for text to out.
json_writer & value(const char *text)
Write a string value (keeps string literals from binding to bool).
json_writer & value(std::string_view text)
Write a string value.
json_writer & value(double d)
Write a number, in the shortest form that reads back exactly.
json_writer & end_object()
Close the innermost object.
const std::string & str() const
The document.
json_writer & value(bool b)
Write a boolean.
json_writer & end_array()
Close the innermost array.
Conversion between UTF-8 and std::wstring.
constexpr bool is_utf8(std::string_view text) noexcept
Whether a byte string is well-formed UTF-8.