1 #include "jsonutils.h"
2
3 #include <QDebug>
4 #include <simdjson.h>
5
6 // Based on https://github.com/nlohmann/json/blob/ec7a1d834773f9fee90d8ae908a0c9933c5646fc/src/json.hpp#L4604-L4697
7 // Copyright © 2013-2015 Niels Lohmann.
8 // The code is licensed under the MIT License
extra_space(const std::string_view & s)9 std::size_t extra_space(const std::string_view& s) noexcept
10 {
11 std::size_t result = 0;
12
13 for (const auto& c : s)
14 {
15 switch (c)
16 {
17 case '"':
18 case '\\':
19 case '\b':
20 case '\f':
21 case '\n':
22 case '\r':
23 case '\t':
24 {
25 // from c (1 byte) to \x (2 bytes)
26 result += 1;
27 break;
28 }
29
30 default:
31 {
32 if (c >= 0x00 and c <= 0x1f)
33 {
34 // from c (1 byte) to \uxxxx (6 bytes)
35 result += 5;
36 }
37 break;
38 }
39 }
40 }
41
42 return result;
43 }
44
escape_string(const std::string_view & s)45 std::string escape_string(const std::string_view& s) noexcept
46 {
47 const auto space = extra_space(s);
48 if (space == 0)
49 {
50 return std::string(s);
51 }
52
53 // create a result string of necessary size
54 std::string result(s.size() + space, '\\');
55 std::size_t pos = 0;
56
57 for (const auto& c : s)
58 {
59 switch (c)
60 {
61 // quotation mark (0x22)
62 case '"':
63 {
64 result[pos + 1] = '"';
65 pos += 2;
66 break;
67 }
68
69 // reverse solidus (0x5c)
70 case '\\':
71 {
72 // nothing to change
73 pos += 2;
74 break;
75 }
76
77 // backspace (0x08)
78 case '\b':
79 {
80 result[pos + 1] = 'b';
81 pos += 2;
82 break;
83 }
84
85 // formfeed (0x0c)
86 case '\f':
87 {
88 result[pos + 1] = 'f';
89 pos += 2;
90 break;
91 }
92
93 // newline (0x0a)
94 case '\n':
95 {
96 result[pos + 1] = 'n';
97 pos += 2;
98 break;
99 }
100
101 // carriage return (0x0d)
102 case '\r':
103 {
104 result[pos + 1] = 'r';
105 pos += 2;
106 break;
107 }
108
109 // horizontal tab (0x09)
110 case '\t':
111 {
112 result[pos + 1] = 't';
113 pos += 2;
114 break;
115 }
116
117 default:
118 {
119 if (c >= 0x00 and c <= 0x1f)
120 {
121 // print character c as \uxxxx
122 sprintf(&result[pos + 1], "u%04x", int(c));
123 pos += 6;
124 // overwrite trailing null character
125 result[pos] = '\\';
126 }
127 else
128 {
129 // all other characters are added as-is
130 result[pos++] = c;
131 }
132 break;
133 }
134 }
135 }
136
137 return result;
138 }
139
escapeJsonKey(std::string_view key)140 QByteArray escapeJsonKey(std::string_view key) {
141 return QByteArray::fromStdString(escape_string(key));
142 }
143
print_json(QByteArray & result,simdjson::ondemand::value element,long level,bool objectValue=false)144 void print_json(QByteArray &result,
145 simdjson::ondemand::value element,
146 long level, bool objectValue = false) {
147 using namespace simdjson::ondemand;
148
149 QByteArray whitespace = QByteArray().fill(' ', level * 2);
150 bool add_comma;
151
152 if (!objectValue) result.append(whitespace);
153
154 switch (element.type()) {
155 case json_type::array:
156 result.append("[\n");
157 add_comma = false;
158 for (auto child : element.get_array()) {
159 if (add_comma) {
160 result.append(",\n");
161 }
162 print_json(result, child.value(), level + 1);
163 add_comma = true;
164 }
165
166 result.append('\n');
167 result.append(whitespace);
168 result.append("]");
169 break;
170 case json_type::object:
171 result.append("{\n");
172 add_comma = false;
173 for (auto field : element.get_object()) {
174 if (add_comma) {
175 result.append(",\n");
176 }
177
178 result.append(whitespace);
179 result.append(" ");
180 result.append(QString("\"%1\": ")
181 .arg(QString::fromUtf8(escapeJsonKey(field.unescaped_key())))
182 .toUtf8());
183 print_json(result, field.value(), level + 1, true);
184 add_comma = true;
185 }
186 result.append('\n');
187 result.append(whitespace);
188 result.append("}");
189 break;
190 case json_type::number:
191 result.append(QByteArray::fromStdString(
192 std::string(std::string_view(element.raw_json_token())))
193 .trimmed());
194 break;
195 case json_type::string:
196 result.append(QByteArray::fromStdString(
197 std::string(std::string_view(element.raw_json_token())))
198 .trimmed());
199 break;
200 case json_type::boolean:
201 result.append(bool(element) ? "true" : "false");
202 break;
203 case json_type::null:
204 result.append("null");
205 break;
206 }
207 }
208
prettyPrintJSON(QByteArray val)209 QByteArray JSONUtils::prettyPrintJSON(QByteArray val)
210 {
211 QByteArray result;
212 result.reserve(val.size() * 32);
213
214 val.resize(val.size() + simdjson::SIMDJSON_PADDING);
215
216 simdjson::ondemand::parser p;
217
218 try {
219 auto doc = p.iterate(val.data(), val.size());
220 print_json(result, simdjson::ondemand::value(doc), 0);
221 } catch (...) {
222 qDebug() << "Cannot parse JSON";
223 return QByteArray();
224 }
225
226 return result;
227 }
228
minifyJSON(const QByteArray & val)229 QByteArray JSONUtils::minifyJSON(const QByteArray &val)
230 {
231 QByteArray minified;
232 minified.resize(val.size());
233
234 size_t new_length{};
235 auto error = simdjson::minify(val.data(), val.size(), minified.data(), new_length);
236
237 if (error != 0) {
238 qDebug() << "Failed to minify JSON with simdjson:" << error;
239 return QByteArray();
240 }
241
242 minified.resize(new_length);
243
244 return minified;
245 }
246
isJSON(QByteArray val)247 bool JSONUtils::isJSON(QByteArray val)
248 {
249 int originalSize = val.size();
250 val.resize(val.size() + simdjson::SIMDJSON_PADDING);
251
252 simdjson::dom::parser parser;
253 simdjson::dom::element data;
254 auto error = parser.parse(val.data(), originalSize, false).get(data);
255
256 if (error != simdjson::SUCCESS && error != simdjson::NUMBER_ERROR) {
257 qDebug() << "JSON is not valid:" << simdjson::error_message(error);
258 return false;
259 }
260
261 return true;
262 }
263