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 &copy; 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