Skip to content

Commit 1b53f52

Browse files
committed
faster JSON string building
1 parent d482ebd commit 1b53f52

File tree

1 file changed

+168
-0
lines changed

1 file changed

+168
-0
lines changed

Source/JSON/JSONBuilder.h

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
Copyright(c) 2021-2026 jvde.github@gmail.com
3+
4+
This program is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
This program is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
18+
#pragma once
19+
20+
#include <string>
21+
#include <cstdio>
22+
#include <cstring>
23+
#include <algorithm>
24+
#include <type_traits>
25+
26+
#include "Common.h"
27+
28+
namespace JSON {
29+
30+
class JSONBuilder {
31+
std::string buf;
32+
size_t cursor;
33+
bool need_comma;
34+
35+
void ensure(size_t len) {
36+
if (cursor + len >= buf.size()) {
37+
buf.resize(MAX(buf.size() * 2, cursor + len + 64));
38+
}
39+
}
40+
41+
void writeRaw(const char* data, size_t len) {
42+
std::memcpy(&buf[cursor], data, len);
43+
cursor += len;
44+
}
45+
46+
void comma() {
47+
if (need_comma) { ensure(1); buf[cursor++] = ','; }
48+
need_comma = true;
49+
}
50+
51+
void escapeString(const char* s, size_t len) {
52+
ensure(len * 2 + 2);
53+
buf[cursor++] = '"';
54+
for (size_t i = 0; i < len; ++i) {
55+
unsigned char c = s[i];
56+
const char* repl = nullptr;
57+
if (c == '"') repl = "\\\""; else if (c == '\\') repl = "\\\\";
58+
else if (c == '\b') repl = "\\b"; else if (c == '\f') repl = "\\f";
59+
else if (c == '\n') repl = "\\n"; else if (c == '\r') repl = "\\r";
60+
else if (c == '\t') repl = "\\t"; else if (c < 0x20) continue;
61+
62+
if (repl) { writeRaw(repl, 2); }
63+
else { buf[cursor++] = c; }
64+
}
65+
buf[cursor++] = '"';
66+
}
67+
68+
// Numeric Writers
69+
template<typename T>
70+
typename std::enable_if<std::is_integral<T>::value, void>::type writeVal(T v) {
71+
ensure(24);
72+
if (std::is_signed<T>::value && v < 0) {
73+
buf[cursor++] = '-';
74+
return writeVal(static_cast<typename std::make_unsigned<T>::type>(-v));
75+
}
76+
char tmp[24], *p = tmp + 23;
77+
if (v == 0) *--p = '0';
78+
while (v > 0) { *--p = '0' + (v % 10); v /= 10; }
79+
writeRaw(p, (tmp + 23) - p);
80+
}
81+
82+
void writeVal(double v) { ensure(32); cursor += std::snprintf(&buf[cursor], 32, "%.6g", v); }
83+
void writeVal(bool v) { ensure(5); writeRaw(v ? "true" : "false", v ? 4 : 5); }
84+
void writeVal(const std::string& v) { escapeString(v.data(), v.size()); }
85+
void writeVal(const char* v) { v ? escapeString(v, std::strlen(v)) : writeRaw("null", 4); }
86+
87+
public:
88+
JSONBuilder() : cursor(0), need_comma(false) { buf.resize(1024); }
89+
90+
std::string str() const { return std::string(buf.data(), cursor); }
91+
size_t size() const { return cursor; }
92+
void clear() { cursor = 0; need_comma = false; }
93+
94+
JSONBuilder& nl() { ensure(1); buf[cursor++] = '\n'; return *this; }
95+
JSONBuilder& crlf() { ensure(2); buf[cursor++] = '\r'; buf[cursor++] = '\n'; return *this; }
96+
97+
// Structure
98+
JSONBuilder& start() { comma(); ensure(1); buf[cursor++] = '{'; need_comma = false; return *this; }
99+
JSONBuilder& end() { ensure(1); buf[cursor++] = '}'; need_comma = true; return *this; }
100+
JSONBuilder& startArray() { comma(); ensure(1); buf[cursor++] = '['; need_comma = false; return *this; }
101+
JSONBuilder& endArray() { ensure(1); buf[cursor++] = ']'; need_comma = true; return *this; }
102+
JSONBuilder& startArr() { return startArray(); }
103+
JSONBuilder& endArr() { return endArray(); }
104+
105+
// Key Management
106+
JSONBuilder& key(const char* k, size_t len) {
107+
ensure(len + 4);
108+
if (need_comma) buf[cursor++] = ',';
109+
buf[cursor++] = '"'; writeRaw(k, len); writeRaw("\":", 2);
110+
need_comma = false;
111+
return *this;
112+
}
113+
JSONBuilder& key(const char* k) { return key(k, std::strlen(k)); }
114+
JSONBuilder& key(const std::string& k) { return key(k.data(), k.size()); }
115+
116+
// Unified Add (Literal Keys - compile-time length)
117+
template<size_t N, typename T>
118+
JSONBuilder& add(const char (&k)[N], T v) { key(k, N-1); writeVal(v); need_comma = true; return *this; }
119+
120+
// Unified Add (Runtime key with strlen)
121+
template<typename T>
122+
JSONBuilder& add(const std::string& k, T v) { key(k); writeVal(v); need_comma = true; return *this; }
123+
124+
// Specialized Adders
125+
template<size_t N>
126+
JSONBuilder& addRaw(const char (&k)[N], const std::string& v) { key(k, N-1); ensure(v.size()); writeRaw(v.data(), v.size()); need_comma = true; return *this; }
127+
128+
JSONBuilder& addRaw(const std::string& k, const std::string& v) { key(k); ensure(v.size()); writeRaw(v.data(), v.size()); need_comma = true; return *this; }
129+
130+
template<size_t N>
131+
JSONBuilder& addSafe(const char (&k)[N], const std::string& v) {
132+
key(k, N-1); ensure(v.size() + 2); buf[cursor++] = '"'; writeRaw(v.data(), v.size()); buf[cursor++] = '"';
133+
need_comma = true; return *this;
134+
}
135+
136+
template<typename T>
137+
JSONBuilder& addIf(bool cond, const char* k, T v) { return cond ? add(k, v) : *this; }
138+
139+
// Array Values
140+
template<typename T>
141+
JSONBuilder& value(T v) { comma(); writeVal(v); return *this; }
142+
143+
JSONBuilder& valueRaw(const std::string& v) { comma(); ensure(v.size()); writeRaw(v.data(), v.size()); return *this; }
144+
145+
JSONBuilder& valueNull() { comma(); ensure(4); writeRaw("null", 4); return *this; }
146+
147+
template<typename T>
148+
JSONBuilder& valueOrNull(T v, T undef) { return (v == undef) ? valueNull() : value(v); }
149+
150+
// Raw JSON fragment appending (no key, for manual control)
151+
JSONBuilder& write(const char* str) { size_t len = std::strlen(str); ensure(len); writeRaw(str, len); return *this; }
152+
JSONBuilder& write(const std::string& str) { ensure(str.size()); writeRaw(str.data(), str.size()); return *this; }
153+
154+
// Nullable logic
155+
template<size_t N, typename T>
156+
JSONBuilder& addOrNull(const char (&k)[N], T v, T undef) { return (v == undef) ? add(k, nullptr) : add(k, v); }
157+
158+
template<size_t N>
159+
JSONBuilder& addStringOrNull(const char (&k)[N], const std::string& v) { return v.empty() ? add(k, nullptr) : add(k, v); }
160+
161+
template<size_t N>
162+
JSONBuilder& addNull(const char (&k)[N]) { key(k, N-1); ensure(4); writeRaw("null", 4); need_comma = true; return *this; }
163+
164+
template<typename T>
165+
JSONBuilder& addIf(bool cond, const std::string& k, T v) { return cond ? add(k, v) : *this; }
166+
};
167+
168+
} // namespace JSON

0 commit comments

Comments
 (0)