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 inline void append_json_number_without_leading_zeros(
97 std::string& out,
99 ) noexcept {
100 size_t first = 0;
101 if (first < value.size() && (value[first] == '+' || value[first] == '-')) {
102 if (value[first] == '-') {
103 out += '-';
104 }
105 ++first;
106 }
107
108 while (first + 1 < value.size()
109 && value[first] == '0'
110 && value[first + 1] >= '0'
111 && value[first + 1] <= '9') {
112 ++first;
113 }
114
115 out.append(value.data() + first, value.size() - first);
116 }
117
119 public:
120 JsonConverter() = default;
121 explicit JsonConverter(const std::vector<std::string>& column_names)
122 : column_names_(column_names) {
123 escaped_object_keys_.reserve(column_names_.size());
124 column_positions_.reserve(column_names_.size());
125
126 for (size_t i = 0; i < column_names_.size(); ++i) {
127 escaped_object_keys_.push_back('"' + json_escape_string(column_names_[i]) + "\":");
128 column_positions_[column_names_[i]] = i;
129 }
130 }
131
132 template<typename FieldAt>
133 std::string row_to_json(size_t field_count, FieldAt field_at, const std::vector<std::string>& subset = {}) const {
134 return subset.empty()
135 ? this->row_to_json_all(field_count, field_at)
136 : this->row_to_json_subset(field_count, field_at, subset);
137 }
138
139 template<typename FieldAt>
140 std::string row_to_json_array(size_t field_count, FieldAt field_at, const std::vector<std::string>& subset = {}) const {
141 return subset.empty()
142 ? this->row_to_json_array_all(field_count, field_at)
143 : this->row_to_json_array_subset(field_count, field_at, subset);
144 }
145
146 private:
147 template<typename FieldAt>
148 std::string row_to_json_all(size_t field_count, FieldAt field_at) const {
149 const size_t count = (field_count < escaped_object_keys_.size()) ? field_count : escaped_object_keys_.size();
150 std::string out = "{";
151
152 for (size_t i = 0; i < count; ++i) {
153 out += escaped_object_keys_[i];
154 this->append_json_value(out, field_at(i));
155 if (i + 1 < count) {
156 out += ',';
157 }
158 }
159
160 out += '}';
161 return out;
162 }
163
164 template<typename FieldAt>
165 std::string row_to_json_subset(
166 size_t field_count,
167 FieldAt field_at,
168 const std::vector<std::string>& subset
169 ) const {
170 std::string out = "{";
171 bool first = true;
172
173 for (const auto& column : subset) {
174 const size_t index = this->index_of(column);
175 if (index >= field_count) {
176 continue;
177 }
178
179 if (!first) {
180 out += ',';
181 }
182
183 out += escaped_object_keys_[index];
184 this->append_json_value(out, field_at(index));
185 first = false;
186 }
187
188 out += '}';
189 return out;
190 }
191
192 template<typename FieldAt>
193 std::string row_to_json_array_all(size_t field_count, FieldAt field_at) const {
194 const size_t count = (field_count < column_names_.size()) ? field_count : column_names_.size();
195 std::string out = "[";
196
197 for (size_t i = 0; i < count; ++i) {
198 this->append_json_value(out, field_at(i));
199 if (i + 1 < count) {
200 out += ',';
201 }
202 }
203
204 out += ']';
205 return out;
206 }
207
208 template<typename FieldAt>
209 std::string row_to_json_array_subset(
210 size_t field_count,
211 FieldAt field_at,
212 const std::vector<std::string>& subset
213 ) const {
214 std::string out = "[";
215 bool first = true;
216
217 for (const auto& column : subset) {
218 const size_t index = this->index_of(column);
219 if (index >= field_count) {
220 continue;
221 }
222
223 if (!first) {
224 out += ',';
225 }
226
227 this->append_json_value(out, field_at(index));
228 first = false;
229 }
230
231 out += ']';
232 return out;
233 }
234
235 void append_json_value(std::string& out, csv::string_view value) const {
236 const DataType type = internals::data_type(value);
237 if (type >= DataType::CSV_INT8 && type <= DataType::CSV_DOUBLE) {
238 append_json_number_without_leading_zeros(out, value);
239 } else if (type == DataType::CSV_NULL) {
240 out += "null";
241 } else {
242 out += '"';
243 append_json_escaped(out, value);
244 out += '"';
245 }
246 }
247
248 size_t index_of(const std::string& column) const {
249 const auto it = column_positions_.find(column);
250 if (it == column_positions_.end()) {
251 throw_column_not_found(column);
252 }
253
254 return it->second;
255 }
256
257 std::vector<std::string> column_names_;
258 std::vector<std::string> escaped_object_keys_;
259 std::unordered_map<std::string, size_t> column_positions_;
260 };
261 }
262}
A standalone header file containing shared code.
Shared exception message templates and throw helpers.
CSV scalar type classification adapter.
The all encompassing namespace.
DataType
Enumerates the different CSV field types recognized by this library.
Definition data_type.hpp:14
@ CSV_DOUBLE
Floating point value.
@ CSV_NULL
Empty string.
@ CSV_INT8
8-bit integer
std::string_view string_view
The string_view class used by this library.
Definition common.hpp:176