simdjson 5.0.2
Ridiculously Fast JSON
Loading...
Searching...
No Matches
annotations.h
Go to the documentation of this file.
1#ifndef SIMDJSON_ANNOTATIONS_H
2#define SIMDJSON_ANNOTATIONS_H
3
38#if SIMDJSON_STATIC_REFLECTION
39
40#include <meta>
41#include <string>
42#include <string_view>
43#include <vector>
44
45namespace simdjson {
46
47// Structural compile-time string -- char array avoids the pointer-based
48// 'reflect_constant failed' that occurs with const char* / string_view members.
49template <size_t N>
50struct fixed_string {
51 char data[N];
52
53 consteval fixed_string(const char (&s)[N]) noexcept {
54 for (size_t i = 0; i < N; ++i) { data[i] = s[i]; }
55 }
56
57 consteval std::string_view view() const noexcept { return {data, N - 1}; }
58
59 consteval bool operator==(const fixed_string&) const noexcept = default;
60};
61
69enum class case_style {
70 lowercase,
71 uppercase,
72 pascal_case,
73 camel_case,
74 snake_case,
75 screaming_snake_case,
76 kebab_case,
77 screaming_kebab_case
78};
79
80namespace detail {
81 template <fixed_string Name>
82 struct rename_t {
83 static constexpr auto name = Name;
84 // Exposed as a pointer and a size: std::meta::extract requires structural types.
85 static constexpr const char *key_data = Name.data;
86 static constexpr size_t key_size = Name.view().size();
87 };
88 template <fixed_string... Names>
89 struct alias_t {
90 static_assert(sizeof...(Names) > 0, "simdjson::alias requires at least one name");
91 static constexpr std::string_view keys[] = {Names.view()...};
92 static constexpr const std::string_view *keys_data = keys;
93 static constexpr size_t keys_count = sizeof...(Names);
94 };
95 struct skip_tag {};
96 struct skip_serializing_tag {};
97 struct skip_deserializing_tag {};
98 template <auto Predicate>
99 struct skip_serializing_if_t {
100 static constexpr auto predicate = Predicate;
101 };
102 struct default_value_tag {};
103 template <auto Factory>
104 struct default_from_t {
105 static constexpr auto factory = Factory;
106 };
107 template <typename Adapter>
108 struct with_t {
109 using adapter = Adapter;
110 };
111 template <case_style Style>
112 struct rename_all_t {
113 static constexpr case_style style = Style;
114 };
115 struct deny_unknown_fields_tag {};
116 struct transparent_tag {};
117 struct flatten_tag {};
118
119 // Predicates usable with skip_serializing_if.
120 struct is_none_t {
121 template <typename T>
122 constexpr bool operator()(const T& v) const noexcept { return !v; }
123 };
124 struct is_empty_t {
125 template <typename T>
126 constexpr bool operator()(const T& v) const noexcept { return v.empty(); }
127 };
128} // namespace detail
129
130// Usage: [[= simdjson::rename<"first_name">]] std::string firstName;
131template <fixed_string Name>
132inline constexpr detail::rename_t<Name> rename{};
133
134// Usage: [[= simdjson::alias<"userName", "login">]] std::string user_name;
135// The aliases are accepted (in addition to the regular key) when deserializing.
136// Serialization always uses the regular key. If the JSON object contains more
137// than one of the names, which one is used is unspecified.
138template <fixed_string... Names>
139inline constexpr detail::alias_t<Names...> alias{};
140
141// Usage: [[= simdjson::skip]] int internalCache;
142inline constexpr detail::skip_tag skip{};
143
144// Usage: [[= simdjson::skip_serializing]] std::string password;
145inline constexpr detail::skip_serializing_tag skip_serializing{};
146
147// Usage: [[= simdjson::skip_deserializing]] int computed;
148// The member is never assigned during deserialization (it keeps its current
149// value) and a matching key in the JSON input is treated as unknown.
150inline constexpr detail::skip_deserializing_tag skip_deserializing{};
151
152// Usage: [[= simdjson::skip_serializing_if<simdjson::is_none>]] std::optional<int> x;
153// The predicate is called with the member value; when it returns true, the key
154// is omitted from the output.
155template <auto Predicate>
156inline constexpr detail::skip_serializing_if_t<Predicate> skip_serializing_if{};
157
158// Predicate: true for an empty std::optional, a null smart pointer, etc.
159inline constexpr detail::is_none_t is_none{};
160// Predicate: true for an empty string or container.
161inline constexpr detail::is_empty_t is_empty{};
162
163// Usage: [[= simdjson::default_value]] int port = 8080;
164// When the key is missing from the JSON input, the member is left untouched
165// (with get<T>(), it keeps its default member initializer) instead of reporting
166// NO_SUCH_FIELD. Applied to a structure, it applies to all of its members.
167inline constexpr detail::default_value_tag default_value{};
168
169// Usage: [[= simdjson::default_from<make_port>]] int port;
170// When the key is missing from the JSON input, the member is assigned the
171// result of calling the factory (a constexpr callable taking no argument, such
172// as a captureless lambda or a pointer to a function).
173template <auto Factory>
174inline constexpr detail::default_from_t<Factory> default_from{};
175
176// Usage: [[= simdjson::with<unix_time>]] std::chrono::system_clock::time_point t;
177// Adapter is a type that provides one or both of
178// static void serialize(simdjson::builder::string_builder &b, const T &value);
179// static simdjson::error_code deserialize(simdjson::ondemand::value &v, T &out);
180// (the parameters may also be declared auto&). When one of them is missing, the
181// default behaviour is used in that direction.
182template <typename Adapter>
183inline constexpr detail::with_t<Adapter> with{};
184
185// Usage: [[= simdjson::flatten]] pagination page;
186// The members of the nested structure are (de)serialized as if they were members
187// of the enclosing structure: {"id":1,"limit":10,"offset":0} rather than
188// {"id":1,"page":{"limit":10,"offset":0}}. The nested structure's own annotations
189// (rename_all, default_value, ...) apply to its members.
190inline constexpr detail::flatten_tag flatten{};
191
192// Usage: struct [[= simdjson::rename_all<simdjson::case_style::camel_case>]] S {...};
193// Also applies to enumerations. An explicit rename on a member takes precedence.
194template <case_style Style>
195inline constexpr detail::rename_all_t<Style> rename_all{};
196
197// Usage: struct [[= simdjson::deny_unknown_fields]] S {...};
198// Deserialization fails with UNKNOWN_FIELD when the JSON object has a key that
199// is not deserialized into a member (including the keys of skipped members).
200inline constexpr detail::deny_unknown_fields_tag deny_unknown_fields{};
201
202// Usage: struct [[= simdjson::transparent]] user_id { int64_t value; };
203// A structure with a single data member is (de)serialized as that member alone:
204// user_id{42} becomes 42 rather than {"value":42}.
205inline constexpr detail::transparent_tag transparent{};
206
207namespace detail {
208
209// True when the entity (a data member, an enumerator or a type) carries an
210// annotation of type tag.
211consteval bool has_annotation(std::meta::info entity, std::meta::info tag) {
212 return !std::meta::annotations_of_with_type(entity, tag).empty();
213}
214
215// Returns the type of the (first) annotation of entity that is a specialization
216// of the class template tmpl, or std::meta::info{} when there is none.
217consteval std::meta::info annotation_of_template(std::meta::info entity, std::meta::info tmpl) {
218 for (std::meta::info ann : std::meta::annotations_of(entity)) {
219 std::meta::info type = std::meta::type_of(ann);
220 if (std::meta::has_template_arguments(type) && std::meta::template_of(type) == tmpl) {
221 return type;
222 }
223 }
224 return std::meta::info{};
225}
226
227// Value of the static data member `name` of the class `type`.
228template <typename T>
229consteval T static_member_value(std::meta::info type, std::string_view name) {
230 for (std::meta::info m : std::meta::static_data_members_of(type, std::meta::access_context::unchecked())) {
231 if (std::meta::has_identifier(m) && std::meta::identifier_of(m) == name) {
232 return std::meta::extract<T>(m);
233 }
234 }
235 return T{};
236}
237
238consteval bool is_upper(char c) { return c >= 'A' && c <= 'Z'; }
239consteval bool is_lower(char c) { return c >= 'a' && c <= 'z'; }
240consteval bool is_digit(char c) { return c >= '0' && c <= '9'; }
241consteval char to_upper(char c) { return is_lower(c) ? char(c - 'a' + 'A') : c; }
242consteval char to_lower(char c) { return is_upper(c) ? char(c - 'A' + 'a') : c; }
243
244// Split an identifier into words: at underscores, at a lowercase letter or digit
245// followed by an uppercase letter (userId), and before the last capital of an
246// acronym followed by a lowercase letter (HTTPServer -> HTTP, Server).
247consteval std::vector<std::string> split_identifier(std::string_view id) {
248 std::vector<std::string> words;
249 std::string current;
250 for (size_t i = 0; i < id.size(); i++) {
251 char c = id[i];
252 if (c == '_') {
253 if (!current.empty()) { words.push_back(current); current.clear(); }
254 continue;
255 }
256 if (is_upper(c) && !current.empty()) {
257 char prev = current.back();
258 bool next_is_lower = (i + 1 < id.size()) && is_lower(id[i + 1]);
259 if (is_lower(prev) || is_digit(prev) || (is_upper(prev) && next_is_lower)) {
260 words.push_back(current);
261 current.clear();
262 }
263 }
264 current.push_back(c);
265 }
266 if (!current.empty()) { words.push_back(current); }
267 return words;
268}
269
270consteval std::string apply_case_style(std::string_view id, case_style style) {
271 std::string result;
272 if (style == case_style::lowercase || style == case_style::uppercase) {
273 for (char c : id) {
274 result.push_back(style == case_style::lowercase ? to_lower(c) : to_upper(c));
275 }
276 return result;
277 }
278 std::vector<std::string> words = split_identifier(id);
279 for (size_t w = 0; w < words.size(); w++) {
280 const std::string &word = words[w];
281 if (style == case_style::pascal_case || style == case_style::camel_case) {
282 for (size_t i = 0; i < word.size(); i++) {
283 bool capital = (i == 0) && (style == case_style::pascal_case || w > 0);
284 result.push_back(capital ? to_upper(word[i]) : to_lower(word[i]));
285 }
286 } else {
287 bool upper = style == case_style::screaming_snake_case || style == case_style::screaming_kebab_case;
288 bool kebab = style == case_style::kebab_case || style == case_style::screaming_kebab_case;
289 if (w > 0) { result.push_back(kebab ? '-' : '_'); }
290 for (char c : word) { result.push_back(upper ? to_upper(c) : to_lower(c)); }
291 }
292 }
293 return result;
294}
295
296// The JSON key for a data member or an enumerator: an explicit rename wins,
297// then the rename_all of the enclosing structure or enumeration, then the C++
298// identifier.
299consteval std::string_view json_key_name(std::meta::info entity) {
300 std::meta::info rename_type = annotation_of_template(entity, ^^rename_t);
301 if (rename_type != std::meta::info{}) {
302 return std::define_static_string(std::string_view{
303 static_member_value<const char *>(rename_type, "key_data"),
304 static_member_value<size_t>(rename_type, "key_size")});
305 }
306 std::meta::info rename_all_type = annotation_of_template(std::meta::parent_of(entity), ^^rename_all_t);
307 if (rename_all_type != std::meta::info{}) {
308 case_style style = static_member_value<case_style>(rename_all_type, "style");
309 return std::define_static_string(apply_case_style(std::meta::identifier_of(entity), style));
310 }
311 return std::define_static_string(std::meta::identifier_of(entity));
312}
313
314// The keys accepted when deserializing a data member or an enumerator: the JSON
315// key first, followed by the aliases (if any), in declaration order.
316consteval std::vector<std::string_view> json_key_names(std::meta::info entity) {
317 std::vector<std::string_view> names{json_key_name(entity)};
318 for (std::meta::info ann : std::meta::annotations_of(entity)) {
319 std::meta::info type = std::meta::type_of(ann);
320 if (std::meta::has_template_arguments(type) && std::meta::template_of(type) == ^^alias_t) {
321 const std::string_view *keys = static_member_value<const std::string_view *>(type, "keys_data");
322 size_t count = static_member_value<size_t>(type, "keys_count");
323 for (size_t i = 0; i < count; i++) { names.push_back(keys[i]); }
324 }
325 }
326 return names;
327}
328
329// The structure type of a member annotated with flatten.
330consteval std::meta::info flattened_type(std::meta::info mem) {
331 if (std::meta::is_reference_type(std::meta::type_of(mem))) {
332 // A reference member could refer back to the enclosing structure: the
333 // flattening would never terminate.
334 throw std::meta::exception(u8"simdjson::flatten requires a member that is not a reference", mem);
335 }
336 std::meta::info type = std::meta::remove_cvref(std::meta::type_of(mem));
337 if (!std::meta::is_class_type(type)) {
338 throw std::meta::exception(u8"simdjson::flatten requires a member of class type", mem);
339 }
340 return type;
341}
342
343// The single data member of a structure annotated with transparent: the only
344// member that is not annotated with skip.
345consteval std::meta::info transparent_member(std::meta::info type) {
346 std::vector<std::meta::info> members;
347 for (std::meta::info mem : std::meta::nonstatic_data_members_of(type, std::meta::access_context::unchecked())) {
348 if (!has_annotation(mem, ^^skip_tag)) { members.push_back(mem); }
349 }
350 if (members.size() != 1) {
351 throw std::meta::exception(u8"simdjson::transparent requires exactly one data member (not counting skipped members)", type);
352 }
353 return members[0];
354}
355
356} // namespace detail
357
358// Returns the JSON key for a reflected data member (or enumerator).
359template <auto dm>
360consteval const char* get_json_key_name() {
361 return detail::json_key_name(dm).data();
362}
363
364} // namespace simdjson
365
366#endif // SIMDJSON_STATIC_REFLECTION
367#endif // SIMDJSON_ANNOTATIONS_H
simdjson_unused simdjson_inline bool operator==(const raw_json_string &a, std::string_view c) noexcept
Comparisons between raw_json_string and std::string_view instances are potentially unsafe: the user i...
The top level simdjson namespace, containing everything the library provides.
Definition base.h:8