metadata 0.2.0
Modern C++23 header-only metadata container (JSON-like)
Loading...
Searching...
No Matches
ostream.hpp
1#pragma once
2
3#include <md/object.hpp>
4#include <md/value.hpp>
5
6#include <array>
7#include <charconv>
8#include <cstdint>
9#include <ostream>
10#include <string>
11#include <string_view>
12#include <system_error>
13#include <variant>
14
15namespace md {
16
17namespace detail {
18
19inline void write_json_string(std::ostream& os, const std::string_view s) {
20 os.put('"');
21 for (const char c : s) {
22 switch (c) {
23 case '"':
24 os << "\\\"";
25 break;
26 case '\\':
27 os << "\\\\";
28 break;
29 case '\b':
30 os << "\\b";
31 break;
32 case '\f':
33 os << "\\f";
34 break;
35 case '\n':
36 os << "\\n";
37 break;
38 case '\r':
39 os << "\\r";
40 break;
41 case '\t':
42 os << "\\t";
43 break;
44 default:
45 if (static_cast<unsigned char>(c) < 0x20) {
46 static constexpr std::array<char, 16> hex = {
47 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
48 os << "\\u00";
49 os.put(hex[(static_cast<unsigned char>(c) >> 4U) & 0xFU]);
50 os.put(hex[static_cast<unsigned char>(c) & 0xFU]);
51 } else {
52 os.put(c);
53 }
54 }
55 }
56 os.put('"');
57}
58
59// Shortest round-trip decimal via std::to_chars: produces the fewest digits
60// that, when parsed back to the same type, recover the exact same value.
61// So `3.14` (a double) prints as "3.14", not "3.1400000000000001".
62template <class T>
63inline void write_json_floating(std::ostream& os, const T v) {
64 static_assert(std::is_floating_point_v<T>);
65 std::array<char, 64> buf{};
66 auto [end, ec] = std::to_chars(buf.data(), buf.data() + buf.size(), v);
67 (void)ec; // 64 bytes is enough for any IEEE 754 binary32/binary64 shortest form
68 os.write(buf.data(), end - buf.data());
69}
70
71void write_json(std::ostream& os, const Value& v);
72
73inline void write_json(std::ostream& os, const Array& a) {
74 os.put('[');
75 bool first = true;
76 for (const auto& el : a) {
77 if (!first) {
78 os.put(',');
79 }
80 first = false;
81 write_json(os, el);
82 }
83 os.put(']');
84}
85
86inline void write_json(std::ostream& os, const Object& o) {
87 os.put('{');
88 bool first = true;
89 for (const auto& [k, val] : o) {
90 if (!first) {
91 os.put(',');
92 }
93 first = false;
94 write_json_string(os, k);
95 os.put(':');
96 write_json(os, val);
97 }
98 os.put('}');
99}
100
101inline void write_json(std::ostream& os, const Value& v) {
102 std::visit(
103 [&](const auto& x) {
104 using T = std::decay_t<decltype(x)>;
105 if constexpr (std::is_same_v<T, std::nullptr_t>) {
106 os << "null";
107 } else if constexpr (std::is_same_v<T, bool>) {
108 os << (x ? "true" : "false");
109 } else if constexpr (std::is_same_v<T, std::int64_t> ||
110 std::is_same_v<T, std::uint64_t>) {
111 os << x;
112 } else if constexpr (std::is_same_v<T, float> || std::is_same_v<T, double>) {
113 write_json_floating(os, x);
114 } else if constexpr (std::is_same_v<T, std::string>) {
115 write_json_string(os, x);
116 } else if constexpr (std::is_same_v<T, Array>) {
117 write_json(os, x);
118 } else if constexpr (std::is_same_v<T, std::unique_ptr<Object>>) {
119 write_json(os, *x);
120 }
121 },
122 v.raw());
123}
124
125} // namespace detail
126
128inline std::ostream& operator<<(std::ostream& os, const Value& v) {
129 detail::write_json(os, v);
130 return os;
131}
133inline std::ostream& operator<<(std::ostream& os, const Object& o) {
134 detail::write_json(os, o);
135 return os;
136}
138inline std::ostream& operator<<(std::ostream& os, const Array& a) {
139 detail::write_json(os, a);
140 return os;
141}
142
143} // namespace md