Vince's CSV Parser
Loading...
Searching...
No Matches
json_converter.hpp
Go to the documentation of this file.
1
5#pragma once
6
7#include <cstring>
8#include <stdexcept>
9#include <string>
10#include <unordered_map>
11#include <vector>
12
13#include "common.hpp"
14#include "csv_exceptions.hpp"
15#include "data_type.hpp"
16
17namespace csv {
18 namespace internals {
20 static const char* const json_control_escape_sequences[32] = {
21 "\\u0000", "\\u0001", "\\u0002", "\\u0003",
22 "\\u0004", "\\u0005", "\\u0006", "\\u0007",
23 "\\b", "\\t", "\\n", "\\u000b",
24 "\\f", "\\r", "\\u000e", "\\u000f",
25 "\\u0010", "\\u0011", "\\u0012", "\\u0013",
26 "\\u0014", "\\u0015", "\\u0016", "\\u0017",
27 "\\u0018", "\\u0019", "\\u001a", "\\u001b",
28 "\\u001c", "\\u001d", "\\u001e", "\\u001f"
29 };
30
31 static const char* const json_quote_escape_sequence = "\\\"";
32 static const char* const json_backslash_escape_sequence = "\\\\";
33
34 CSV_CONST inline const char* json_escape_sequence(unsigned char c) noexcept {
35 if (c < 0x20) {
36 return json_control_escape_sequences[c];
37 }
38
39 if (c == '"') {
40 return json_quote_escape_sequence;
41 }
42
43 if (c == '\\') {
44 return json_backslash_escape_sequence;
45 }
46
47 return nullptr;
48 }
49
50 inline std::size_t json_extra_space(csv::string_view s) noexcept {
51 std::size_t result = 0;
52
53 for (csv::string_view::size_type i = 0; i < s.size(); ++i) {
54 const unsigned char c = static_cast<unsigned char>(s[i]);
55 const char* escape_sequence = json_escape_sequence(c);
56 if (escape_sequence) {
57 result += std::strlen(escape_sequence) - 1;
58 }
59 }
60
61 return result;
62 }
63
64 inline void append_json_escaped(std::string& out, csv::string_view s) noexcept {
65 const std::size_t extra = json_extra_space(s);
66 if (extra == 0) {
67 out.append(s.data(), s.size());
68 return;
69 }
70
71 const std::size_t original_size = out.size();
72 out.resize(original_size + s.size() + extra);
73 char* dest = &out[original_size];
74 std::size_t pos = 0;
75
76 for (csv::string_view::size_type i = 0; i < s.size(); ++i) {
77 const unsigned char c = static_cast<unsigned char>(s[i]);
78 const char* escape_sequence = json_escape_sequence(c);
79 if (escape_sequence) {
80 const std::size_t escape_length = std::strlen(escape_sequence);
81 std::memcpy(dest + pos, escape_sequence, escape_length);
82 pos += escape_length;
83 } else {
84 dest[pos++] = static_cast<char>(c);
85 }
86 }
87 }
88
89 inline std::string json_escape_string(csv::string_view s) noexcept {
90 std::string out;
91 out.reserve(s.size() + json_extra_space(s));
92 append_json_escaped(out, s);
93 return out;
94 }
95
96 CSV_CONST inline bool is_ascii_space(char c) noexcept {
97 return c == ' ' || (c >= '\t' && c <= '\r');
98 }
99
105 inline void append_json_number(std::string& out, csv::string_view value) noexcept {
106 size_t first = 0;
107 size_t last = value.size();
108 while (first < last && is_ascii_space(value[first])) {
109 ++first;
110 }
111
112 while (last > first && is_ascii_space(value[last - 1])) {
113 --last;
114 }
115
116 if (first < last && (value[first] == '+' || value[first] == '-')) {
117 if (value[first] == '-') {
118 out += '-';
119 }
120 ++first;
121 }
122
123 while (first + 1 < last
124 && value[first] == '0'
125 && value[first + 1] >= '0'
126 && value[first + 1] <= '9') {
127 ++first;
128 }
129
130 if (first < last && value[first] == '.') {
131 out += '0';
132 }
133
134 for (size_t i = first; i < last; ++i) {
135 out += value[i];
136 if (value[i] == '.' && (i + 1 == last || value[i + 1] < '0' || value[i + 1] > '9')) {
137 out += '0';
138 }
139 }
140 }
141
143 inline bool append_json_hex_integer(std::string& out, csv::string_view value) {
144 if (value.find_first_of("xX") == csv::string_view::npos) {
145 return false;
146 }
147
148 long long parsed = 0;
149 if (!classify_scalar::parse_hex(value.data(), value.data() + value.size(), parsed)) {
150 return false;
151 }
152
153 out += std::to_string(parsed);
154 return true;
155 }
156
158 public:
159 JsonConverter() = default;
160 explicit JsonConverter(const std::vector<std::string>& column_names)
161 : column_names_(column_names) {
162 escaped_object_keys_.reserve(column_names_.size());
163 column_positions_.reserve(column_names_.size());
164
165 for (size_t i = 0; i < column_names_.size(); ++i) {
166 escaped_object_keys_.push_back('"' + json_escape_string(column_names_[i]) + "\":");
167 column_positions_[column_names_[i]] = i;
168 }
169 }
170
171 template<typename FieldAt>
172 std::string row_to_json(size_t field_count, FieldAt field_at) const {
173 return row_to_json_all(field_count, field_at);
174 }
175
176 template<typename FieldAt>
177 std::string row_to_json_array(size_t field_count, FieldAt field_at) const {
178 return row_to_json_array_all(field_count, field_at);
179 }
180
181 template<typename FieldAt>
182 std::string try_row_to_json(size_t field_count, FieldAt field_at,
183 const std::vector<std::string>& subset, CSVError& error) const {
184 error.clear();
185 return subset.empty() ? row_to_json_all(field_count, field_at)
186 : row_to_json_subset(field_count, field_at, subset, error);
187 }
188
189 template<typename FieldAt>
190 std::string try_row_to_json_array(size_t field_count, FieldAt field_at,
191 const std::vector<std::string>& subset, CSVError& error) const {
192 error.clear();
193 return subset.empty() ? row_to_json_array_all(field_count, field_at)
194 : row_to_json_array_subset(field_count, field_at, subset, error);
195 }
196
197#if CSV_EXCEPTIONS_ENABLED
198 template<typename FieldAt>
199 std::string row_to_json(size_t field_count, FieldAt field_at,
200 const std::vector<std::string>& subset) const {
201 CSVError error;
202 auto out = try_row_to_json(field_count, field_at, subset, error);
203 throw_error(error);
204 return out;
205 }
206
207 template<typename FieldAt>
208 std::string row_to_json_array(size_t field_count, FieldAt field_at,
209 const std::vector<std::string>& subset) const {
210 CSVError error;
211 auto out = try_row_to_json_array(field_count, field_at, subset, error);
212 throw_error(error);
213 return out;
214 }
215#endif
216
217 private:
218 template<typename FieldAt>
219 std::string row_to_json_all(size_t field_count, FieldAt field_at) const {
220 const size_t count = (field_count < escaped_object_keys_.size()) ? field_count : escaped_object_keys_.size();
221 std::string out = "{";
222
223 for (size_t i = 0; i < count; ++i) {
224 out += escaped_object_keys_[i];
225 this->append_json_value(out, field_at(i));
226 if (i + 1 < count) {
227 out += ',';
228 }
229 }
230
231 out += '}';
232 return out;
233 }
234
235 template<typename FieldAt>
236 std::string row_to_json_subset(
237 size_t field_count,
238 FieldAt field_at,
239 const std::vector<std::string>& subset, CSVError& error
240 ) const {
241 std::string out = "{";
242 bool first = true;
243
244 for (const auto& column : subset) {
245 const size_t index = this->index_of(column, error);
246 if (error) return {};
247 if (index >= field_count) {
248 continue;
249 }
250
251 if (!first) {
252 out += ',';
253 }
254
255 out += escaped_object_keys_[index];
256 this->append_json_value(out, field_at(index));
257 first = false;
258 }
259
260 out += '}';
261 return out;
262 }
263
264 template<typename FieldAt>
265 std::string row_to_json_array_all(size_t field_count, FieldAt field_at) const {
266 const size_t count = (field_count < column_names_.size()) ? field_count : column_names_.size();
267 std::string out = "[";
268
269 for (size_t i = 0; i < count; ++i) {
270 this->append_json_value(out, field_at(i));
271 if (i + 1 < count) {
272 out += ',';
273 }
274 }
275
276 out += ']';
277 return out;
278 }
279
280 template<typename FieldAt>
281 std::string row_to_json_array_subset(
282 size_t field_count,
283 FieldAt field_at,
284 const std::vector<std::string>& subset, CSVError& error
285 ) const {
286 std::string out = "[";
287 bool first = true;
288
289 for (const auto& column : subset) {
290 const size_t index = this->index_of(column, error);
291 if (error) return {};
292 if (index >= field_count) {
293 continue;
294 }
295
296 if (!first) {
297 out += ',';
298 }
299
300 this->append_json_value(out, field_at(index));
301 first = false;
302 }
303
304 out += ']';
305 return out;
306 }
307
308 void append_json_value(std::string& out, csv::string_view value) const {
309 const DataType type = internals::data_type(value);
310 if (type >= DataType::CSV_INT8 && type <= DataType::CSV_DOUBLE) {
311 if (type > DataType::CSV_INT64 || !append_json_hex_integer(out, value)) {
312 append_json_number(out, value);
313 }
314 } else if (type == DataType::CSV_NULL) {
315 out += "null";
316 } else {
317 out += '"';
318 append_json_escaped(out, value);
319 out += '"';
320 }
321 }
322
323 size_t index_of(const std::string& column, CSVError& error) const {
324 const auto it = column_positions_.find(column);
325 if (it == column_positions_.end()) {
327 make_prefixed_message(ERROR_COLUMN_NOT_FOUND, column));
328 return 0;
329 }
330
331 return it->second;
332 }
333
334 std::vector<std::string> column_names_;
335 std::vector<std::string> escaped_object_keys_;
336 std::unordered_map<std::string, size_t> column_positions_;
337 };
338 }
339}
A standalone header file containing shared code.
Shared exception message templates and throw helpers.
void throw_error(const CSVError &error)
Translate explicit library failures while preserving legacy exception categories.
CSV scalar type classification adapter.
bool append_json_hex_integer(std::string &out, csv::string_view value)
JSON has no hexadecimal literal, so write 0x-prefixed integers in decimal.
void append_json_number(std::string &out, csv::string_view value) noexcept
Append a numeric field as a JSON number.
The all encompassing namespace.
DataType
Enumerates the different CSV field types recognized by this library.
Definition data_type.hpp:14
@ CSV_INT64
64-bit integer
@ CSV_DOUBLE
Floating point value.
@ CSV_NULL
Empty string.
@ CSV_INT8
8-bit integer
@ ColumnNotFound
A requested name is absent from the visible column schema.
std::string_view string_view
The string_view class used by this library.
Definition common.hpp:182
A diagnostic whose text remains valid for the lifetime of the error.
Definition csv_error.hpp:94