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
97 public:
98 JsonConverter() = default;
99 explicit JsonConverter(const std::vector<std::string>& column_names)
100 : column_names_(column_names) {
101 escaped_object_keys_.reserve(column_names_.size());
102 column_positions_.reserve(column_names_.size());
103
104 for (size_t i = 0; i < column_names_.size(); ++i) {
105 escaped_object_keys_.push_back('"' + json_escape_string(column_names_[i]) + "\":");
106 column_positions_[column_names_[i]] = i;
107 }
108 }
109
110 template<typename FieldAt>
111 std::string row_to_json(size_t field_count, FieldAt field_at, const std::vector<std::string>& subset = {}) const {
112 return subset.empty()
113 ? this->row_to_json_all(field_count, field_at)
114 : this->row_to_json_subset(field_count, field_at, subset);
115 }
116
117 template<typename FieldAt>
118 std::string row_to_json_array(size_t field_count, FieldAt field_at, const std::vector<std::string>& subset = {}) const {
119 return subset.empty()
120 ? this->row_to_json_array_all(field_count, field_at)
121 : this->row_to_json_array_subset(field_count, field_at, subset);
122 }
123
124 private:
125 template<typename FieldAt>
126 std::string row_to_json_all(size_t field_count, FieldAt field_at) const {
127 const size_t count = (field_count < escaped_object_keys_.size()) ? field_count : escaped_object_keys_.size();
128 std::string out = "{";
129
130 for (size_t i = 0; i < count; ++i) {
131 out += escaped_object_keys_[i];
132 this->append_json_value(out, field_at(i));
133 if (i + 1 < count) {
134 out += ',';
135 }
136 }
137
138 out += '}';
139 return out;
140 }
141
142 template<typename FieldAt>
143 std::string row_to_json_subset(
144 size_t field_count,
145 FieldAt field_at,
146 const std::vector<std::string>& subset
147 ) const {
148 std::string out = "{";
149 bool first = true;
150
151 for (const auto& column : subset) {
152 const size_t index = this->index_of(column);
153 if (index >= field_count) {
154 continue;
155 }
156
157 if (!first) {
158 out += ',';
159 }
160
161 out += escaped_object_keys_[index];
162 this->append_json_value(out, field_at(index));
163 first = false;
164 }
165
166 out += '}';
167 return out;
168 }
169
170 template<typename FieldAt>
171 std::string row_to_json_array_all(size_t field_count, FieldAt field_at) const {
172 const size_t count = (field_count < column_names_.size()) ? field_count : column_names_.size();
173 std::string out = "[";
174
175 for (size_t i = 0; i < count; ++i) {
176 this->append_json_value(out, field_at(i));
177 if (i + 1 < count) {
178 out += ',';
179 }
180 }
181
182 out += ']';
183 return out;
184 }
185
186 template<typename FieldAt>
187 std::string row_to_json_array_subset(
188 size_t field_count,
189 FieldAt field_at,
190 const std::vector<std::string>& subset
191 ) const {
192 std::string out = "[";
193 bool first = true;
194
195 for (const auto& column : subset) {
196 const size_t index = this->index_of(column);
197 if (index >= field_count) {
198 continue;
199 }
200
201 if (!first) {
202 out += ',';
203 }
204
205 this->append_json_value(out, field_at(index));
206 first = false;
207 }
208
209 out += ']';
210 return out;
211 }
212
213 void append_json_value(std::string& out, csv::string_view value) const {
214 const DataType type = internals::data_type(value);
215 if (type >= DataType::CSV_INT8 && type <= DataType::CSV_DOUBLE) {
216 out.append(value.data(), value.size());
217 } else {
218 out += '"';
219 append_json_escaped(out, value);
220 out += '"';
221 }
222 }
223
224 size_t index_of(const std::string& column) const {
225 const auto it = column_positions_.find(column);
226 if (it == column_positions_.end()) {
227 throw_column_not_found(column);
228 }
229
230 return it->second;
231 }
232
233 std::vector<std::string> column_names_;
234 std::vector<std::string> escaped_object_keys_;
235 std::unordered_map<std::string, size_t> column_positions_;
236 };
237 }
238}
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_INT8
8-bit integer
std::string_view string_view
The string_view class used by this library.
Definition common.hpp:174