microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
json.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Jarosław Pelczar <jarek@jpelczar.com>
2//
3// SPDX-License-Identifier: BSD-2-Clause
4
5#pragma once
6
7#include "../microfmt.hpp"
8#include <cstddef>
9#include <cstdint>
10#include <string_view>
11#include <type_traits>
12#include <utility>
13
22namespace microfmt::json {
23
24namespace detail {
25
26template <typename T> void write_integer(const sink &out, T value) noexcept {
27 using unsigned_type = typename std::make_unsigned<T>::type;
28 if constexpr (std::is_signed<T>::value) {
29 if (value < 0) {
30 out.put('-');
31 const auto magnitude = static_cast<unsigned_type>(0) - static_cast<unsigned_type>(value);
32 microfmt::detail::format_unsigned<microfmt::detail::radix::decimal>(out, static_cast<uint64_t>(magnitude), false,
33 0);
34 return;
35 }
36 }
37 microfmt::detail::format_unsigned<microfmt::detail::radix::decimal>(
38 out, static_cast<uint64_t>(static_cast<unsigned_type>(value)), false, 0);
39}
40
41} // namespace detail
42
43// ============================================================================
44// Escaped String Serializer
45// ============================================================================
46
48inline void write_escaped_string(const sink &out, microfmt::string_view str) noexcept {
49 out.put('"');
50 for (char c : str) {
51 switch (c) {
52 case '"':
53 out.write("\\\"");
54 break;
55 case '\\':
56 out.write("\\\\");
57 break;
58 case '\b':
59 out.write("\\b");
60 break;
61 case '\f':
62 out.write("\\f");
63 break;
64 case '\n':
65 out.write("\\n");
66 break;
67 case '\r':
68 out.write("\\r");
69 break;
70 case '\t':
71 out.write("\\t");
72 break;
73 default:
74 if (static_cast<uint8_t>(c) < 0x20) {
75 // Control character escape: \u00XX
76 out.write("\\u00");
77 const auto b = static_cast<uint8_t>(c);
78 out.put(microfmt::detail::hex_digits_upper[(b >> 4) & 0x0F]);
79 out.put(microfmt::detail::hex_digits_upper[b & 0x0F]);
80 } else {
81 out.put(c);
82 }
83 break;
84 }
85 }
86 out.put('"');
87}
88
89// ============================================================================
90// RAII Scoped Array & Object Writers
91// ============================================================================
92
93class array_writer;
94
101class MICROFMT_API_CLASS RELOCO_CONSUMABLE(unconsumed) RELOCO_POINTER object_writer {
102public:
103 explicit object_writer(sink out) noexcept RELOCO_RETURN_TYPESTATE(unconsumed) : out_(std::move(out)) {
104 out_.put('{');
105 }
106
107 ~object_writer() noexcept {
108 if (!closed_) {
109 out_.put('}');
110 }
111 }
112
113 // Non-copyable, movable
114 object_writer(const object_writer &) = delete;
115 object_writer &operator=(const object_writer &) = delete;
116 object_writer(object_writer &&other) noexcept RELOCO_RETURN_TYPESTATE(unconsumed)
117 : out_(std::move(other.out_)), first_(other.first_), closed_(other.closed_) {
118 other.closed_ = true;
119 }
120
121 // Helper for Clang if object state is unknown
122 object_writer &as_known() & noexcept RELOCO_CALLABLE_WHEN("unconsumed", "unknown")
123 RELOCO_RETURN_TYPESTATE(unconsumed) {
124 RELOCO_ASSERT(!closed_, "Attempt to reuse consumed state");
125 return *this;
126 }
127
128 // Key-Value primitives
129 object_writer &key(microfmt::string_view k) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
130 prefix();
131 write_escaped_string(out_, k);
132 out_.put(':');
133 return *this;
134 }
135
136 object_writer &kv(microfmt::string_view k, microfmt::string_view val) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
137 key(k);
138 write_escaped_string(out_, val);
139 return *this;
140 }
141
142 object_writer &kv(microfmt::string_view k, const char *val) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
143 key(k);
144 if (val == nullptr) {
145 out_.write("null");
146 } else {
147 write_escaped_string(out_, val);
148 }
149 return *this;
150 }
151
152 template <std::size_t N>
153 object_writer &kv(microfmt::string_view k, const char (&val)[N]) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
154 return kv(k, microfmt::string_view(val, N - 1));
155 }
156
157 object_writer &kv(microfmt::string_view k, bool val) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
158 key(k);
159 out_.write(val ? "true" : "false");
160 return *this;
161 }
162
163 object_writer &kv(microfmt::string_view k, std::nullptr_t) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
164 key(k);
165 out_.write("null");
166 return *this;
167 }
168
169 template <typename T,
170 typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, bool>::value, int>::type = 0>
171 object_writer &kv(microfmt::string_view k, T val) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
172 key(k);
173 detail::write_integer(out_, val);
174 return *this;
175 }
176
177 // Nested Object
178 [[nodiscard]] object_writer nested_object(microfmt::string_view k) noexcept RELOCO_CALLABLE_WHEN("unconsumed")
179 RELOCO_RETURN_TYPESTATE(unconsumed) {
180 key(k);
181 return object_writer(out_);
182 }
183
184 // Nested Array
185 [[nodiscard]] array_writer nested_array(microfmt::string_view k) noexcept RELOCO_CALLABLE_WHEN("unconsumed")
186 RELOCO_RETURN_TYPESTATE(unconsumed);
187
188 void end() noexcept RELOCO_CALLABLE_WHEN("unconsumed", "consumed") RELOCO_SET_TYPESTATE(consumed) {
189 if (!closed_) {
190 out_.put('}');
191 closed_ = true;
192 }
193 }
194
195private:
196 void prefix() noexcept {
197 if (!first_) {
198 out_.put(',');
199 }
200 first_ = false;
201 }
202
203 sink out_;
204 bool first_{true};
205 bool closed_{false};
206};
207
212class MICROFMT_API_CLASS RELOCO_CONSUMABLE(unconsumed) array_writer {
213public:
214 explicit array_writer(sink out) noexcept RELOCO_RETURN_TYPESTATE(unconsumed) : out_(std::move(out)) { out_.put('['); }
215
216 ~array_writer() noexcept {
217 if (!closed_) {
218 out_.put(']');
219 closed_ = true;
220 }
221 }
222
223 array_writer(const array_writer &) = delete;
224 array_writer &operator=(const array_writer &) = delete;
225 array_writer(array_writer &&other) noexcept RELOCO_RETURN_TYPESTATE(unconsumed)
226 : out_(std::move(other.out_)), first_(other.first_), closed_(other.closed_) {
227 other.closed_ = true;
228 }
229
230 // Helper for Clang if object state is unknown
231 array_writer &as_known() & noexcept RELOCO_CALLABLE_WHEN("unconsumed", "unknown")
232 RELOCO_RETURN_TYPESTATE(unconsumed) {
233 RELOCO_ASSERT(!closed_, "Attempt to reuse consumed state");
234 return *this;
235 }
236
237 array_writer &val(microfmt::string_view v) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
238 prefix();
239 write_escaped_string(out_, v);
240 return *this;
241 }
242
243 // low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked
244 // here.
245 RELOCO_BEGIN_UNSAFE_CODE
246 array_writer &val(const char *v) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
247 prefix();
248 if (v == nullptr) {
249 out_.write("null");
250 } else {
251 write_escaped_string(out_, v);
252 }
253 return *this;
254 }
256
257 template <std::size_t N> array_writer &val(const char (&v)[N]) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
258 return val(microfmt::string_view(v, N - 1));
259 }
260
261 array_writer &val(bool v) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
262 prefix();
263 out_.write(v ? "true" : "false");
264 return *this;
265 }
266
267 array_writer &val(std::nullptr_t) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
268 prefix();
269 out_.write("null");
270 return *this;
271 }
272
273 template <typename T,
274 typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, bool>::value, int>::type = 0>
275 array_writer &val(T v) & noexcept RELOCO_CALLABLE_WHEN("unconsumed") {
276 prefix();
277 detail::write_integer(out_, v);
278 return *this;
279 }
280
281 [[nodiscard]] object_writer obj() & noexcept RELOCO_LIFETIMEBOUND RELOCO_CALLABLE_WHEN("unconsumed")
282 RELOCO_RETURN_TYPESTATE(unconsumed) {
283 prefix();
284 return object_writer(out_);
285 }
286
287 void end() noexcept RELOCO_CALLABLE_WHEN("unconsumed", "consumed") RELOCO_SET_TYPESTATE(consumed) {
288 if (!closed_) {
289 out_.put(']');
290 closed_ = true;
291 }
292 }
293
294private:
295 void prefix() noexcept {
296 if (!first_) {
297 out_.put(',');
298 }
299 first_ = false;
300 }
301
302 sink out_;
303 bool first_{true};
304 bool closed_{false};
305};
306
307inline array_writer object_writer::nested_array(microfmt::string_view k) noexcept {
308 key(k);
309 return array_writer(out_);
310}
311
312// ============================================================================
313// Lambda-Based JSON Stream Adapter for format_to
314// ============================================================================
315
316template <typename Fn> struct json_obj_view {
317 Fn fn;
318};
319
321template <typename Fn> [[nodiscard]] constexpr auto json_obj(Fn &&fn) noexcept {
322 return json_obj_view<std::decay_t<Fn>>{std::forward<Fn>(fn)};
323}
324
325} // namespace microfmt::json
326
327namespace microfmt {
328
329template <typename Fn> struct formatter<json::json_obj_view<Fn>> {
330 constexpr void parse(format_parse_context &) noexcept {}
331
332 void format(const json::json_obj_view<Fn> &v, const sink &out) const noexcept {
333 json::object_writer w(out);
334 v.fn(w);
335 // Closed by object_writer destructor
336 }
337};
338
339} // namespace microfmt
Definition microfmt.hpp:881
#define MICROFMT_API_CLASS
Definition compat.hpp:181
void write_integer(const sink &out, T value) noexcept
Definition json.hpp:26
Definition json.hpp:22
constexpr auto json_obj(Fn &&fn) noexcept
Wraps an object-writing callable for use as a format argument.
Definition json.hpp:321
void write_escaped_string(const sink &out, microfmt::string_view str) noexcept
Writes a quoted, JSON-escaped string to out.
Definition json.hpp:48
class MICROFMT_API_CLASS RELOCO_CONSUMABLE(unconsumed) RELOCO_POINTER object_writer
RAII writer for a streaming JSON object.
Definition json.hpp:101
Definition assert.hpp:19
reloco::sink sink
Type-erased, zero-allocation output sink.
Definition microfmt.hpp:46
RELOCO_END_UNSAFE_CODE
Definition remote_object.hpp:132
void format(const json::json_obj_view< Fn > &v, const sink &out) const noexcept
Definition json.hpp:332
constexpr void parse(format_parse_context &) noexcept
Definition json.hpp:330
Definition microfmt.hpp:985
Definition json.hpp:316
Fn fn
Definition json.hpp:317