mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
add: C++26 annotations support for rename and skip (#2730)
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
#ifndef SIMDJSON_ANNOTATIONS_H
|
||||
#define SIMDJSON_ANNOTATIONS_H
|
||||
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
#include <meta>
|
||||
#include <string_view>
|
||||
|
||||
namespace simdjson {
|
||||
|
||||
// Structural compile-time string — char array avoids the pointer-based
|
||||
// 'reflect_constant failed' that occurs with const char* / string_view members.
|
||||
template <size_t N>
|
||||
struct fixed_string {
|
||||
char data[N];
|
||||
|
||||
consteval fixed_string(const char (&s)[N]) noexcept {
|
||||
for (size_t i = 0; i < N; ++i) { data[i] = s[i]; }
|
||||
}
|
||||
|
||||
consteval std::string_view view() const noexcept { return {data, N - 1}; }
|
||||
|
||||
consteval bool operator==(const fixed_string&) const noexcept = default;
|
||||
};
|
||||
|
||||
// Usage: [[= simdjson::rename<"first_name">]] std::string firstName;
|
||||
namespace detail {
|
||||
template <fixed_string Name>
|
||||
struct rename_t {
|
||||
static constexpr auto name = Name;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
template <fixed_string Name>
|
||||
inline constexpr detail::rename_t<Name> rename{};
|
||||
|
||||
// Usage: [[= simdjson::skip]] int internalCache;
|
||||
namespace detail {
|
||||
struct skip_tag {};
|
||||
} // namespace detail
|
||||
|
||||
inline constexpr detail::skip_tag skip{};
|
||||
|
||||
// Returns the JSON key for a reflected data member.
|
||||
template <auto dm>
|
||||
consteval const char* get_json_key_name() {
|
||||
template for (constexpr auto ann :
|
||||
std::define_static_array(std::meta::annotations_of(dm))) {
|
||||
constexpr auto ann_type = std::meta::type_of(ann);
|
||||
if constexpr (std::meta::has_template_arguments(ann_type) &&
|
||||
std::meta::template_of(ann_type) == ^^detail::rename_t) {
|
||||
constexpr auto args =
|
||||
std::define_static_array(std::meta::template_arguments_of(ann_type));
|
||||
return std::define_static_string([:args[0]:].view());
|
||||
}
|
||||
}
|
||||
return std::define_static_string(std::meta::identifier_of(dm));
|
||||
}
|
||||
|
||||
} // namespace simdjson
|
||||
|
||||
#endif // SIMDJSON_STATIC_REFLECTION
|
||||
#endif // SIMDJSON_ANNOTATIONS_H
|
||||
@@ -10,5 +10,6 @@
|
||||
// Otherwise, amalgamation will fail.
|
||||
#include "simdjson/concepts.h"
|
||||
#include "simdjson/dom/fractured_json.h"
|
||||
#include "simdjson/annotations.h"
|
||||
|
||||
#endif // SIMDJSON_GENERIC_BUILDER_DEPENDENCIES_H
|
||||
@@ -4,6 +4,7 @@
|
||||
#define SIMDJSON_GENERIC_STRING_BUILDER_H
|
||||
#include "simdjson/generic/builder/json_string_builder.h"
|
||||
#include "simdjson/concepts.h"
|
||||
#include "simdjson/annotations.h"
|
||||
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
@@ -246,26 +247,29 @@ simdjson_really_inline constexpr void atom(writer &w, const T &t) {
|
||||
// through the writer's local pos. For arithmetic fields, the integer
|
||||
// write happens directly via write_uint_jeaiii on w.ptr+w.pos, so pos
|
||||
// never round-trips through memory.
|
||||
int i = 0;
|
||||
bool first = true;
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = '{';
|
||||
template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||
constexpr auto first_key = std::define_static_string(
|
||||
constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(dm)) + ":");
|
||||
constexpr auto rest_key = std::define_static_string(
|
||||
std::string(",") + constevalutil::consteval_to_quoted_escaped(std::meta::identifier_of(dm)) + ":");
|
||||
constexpr size_t first_key_len = std::char_traits<char>::length(first_key);
|
||||
constexpr size_t rest_key_len = std::char_traits<char>::length(rest_key);
|
||||
if (!w.ensure(rest_key_len)) return;
|
||||
if (i == 0) {
|
||||
std::memcpy(w.ptr + w.pos, first_key, first_key_len);
|
||||
w.pos += first_key_len;
|
||||
} else {
|
||||
std::memcpy(w.ptr + w.pos, rest_key, rest_key_len);
|
||||
w.pos += rest_key_len;
|
||||
if constexpr (std::meta::annotations_of_with_type(dm, ^^simdjson::detail::skip_tag).empty()) {
|
||||
constexpr const char* key_name = simdjson::get_json_key_name<dm>();
|
||||
constexpr auto first_key = std::define_static_string(
|
||||
constevalutil::consteval_to_quoted_escaped(key_name) + ":");
|
||||
constexpr auto rest_key = std::define_static_string(
|
||||
std::string(",") + constevalutil::consteval_to_quoted_escaped(key_name) + ":");
|
||||
constexpr size_t first_key_len = std::char_traits<char>::length(first_key);
|
||||
constexpr size_t rest_key_len = std::char_traits<char>::length(rest_key);
|
||||
if (!w.ensure(rest_key_len)) return;
|
||||
if (first) {
|
||||
std::memcpy(w.ptr + w.pos, first_key, first_key_len);
|
||||
w.pos += first_key_len;
|
||||
} else {
|
||||
std::memcpy(w.ptr + w.pos, rest_key, rest_key_len);
|
||||
w.pos += rest_key_len;
|
||||
}
|
||||
first = false;
|
||||
atom(w, t.[:dm:]);
|
||||
}
|
||||
atom(w, t.[:dm:]);
|
||||
i++;
|
||||
};
|
||||
if (!w.ensure(1)) return;
|
||||
w.ptr[w.pos++] = '}';
|
||||
|
||||
@@ -14,5 +14,6 @@
|
||||
#include "simdjson/padded_string_view.h"
|
||||
#include "simdjson/internal/dom_parser_implementation.h"
|
||||
#include "simdjson/jsonpathutil.h"
|
||||
#include "simdjson/annotations.h"
|
||||
|
||||
#endif // SIMDJSON_GENERIC_ONDEMAND_DEPENDENCIES_H
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "simdjson/generic/ondemand/object.h"
|
||||
#include "simdjson/generic/ondemand/array.h"
|
||||
#include "simdjson/generic/ondemand/base.h"
|
||||
#include "simdjson/annotations.h"
|
||||
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
||||
|
||||
#include <concepts>
|
||||
@@ -277,8 +278,9 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept {
|
||||
SIMDJSON_TRY(val.get_object().get(obj));
|
||||
}
|
||||
template for (constexpr auto mem : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) {
|
||||
if constexpr (!std::meta::is_const(mem) && std::meta::is_public(mem)) {
|
||||
constexpr std::string_view key = std::define_static_string(std::meta::identifier_of(mem));
|
||||
if constexpr (!std::meta::is_const(mem) && std::meta::is_public(mem)
|
||||
&& std::meta::annotations_of_with_type(mem, ^^simdjson::detail::skip_tag).empty()) {
|
||||
constexpr std::string_view key = simdjson::get_json_key_name<mem>();
|
||||
if constexpr (concepts::optional_type<decltype(out.[:mem:])>) {
|
||||
// for optional members, it's ok if the key is missing
|
||||
auto error = obj[key].get(out.[:mem:]);
|
||||
|
||||
@@ -8,6 +8,7 @@ if(SIMDJSON_STATIC_REFLECTION)
|
||||
add_cpp_test(static_reflection_edge_cases_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(static_reflection_enum_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(static_reflection_fractured_json_tests LABELS ondemand acceptance per_implementation)
|
||||
add_cpp_test(static_reflection_annotations_tests LABELS ondemand acceptance per_implementation)
|
||||
endif(SIMDJSON_STATIC_REFLECTION)
|
||||
# Copy the simdjson dll into the tests directory
|
||||
if(MSVC AND BUILD_SHARED_LIBS)
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
#include "simdjson.h"
|
||||
#include "test_builder.h"
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
using namespace simdjson;
|
||||
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
struct RenamedFields {
|
||||
[[= simdjson::rename<"first_name">]] std::string firstName = "";
|
||||
[[= simdjson::rename<"last_name">]] std::string lastName = "";
|
||||
int age = 0;
|
||||
};
|
||||
|
||||
struct SkippedField {
|
||||
std::string name = "";
|
||||
[[= simdjson::skip]] int internalCache = 0;
|
||||
};
|
||||
|
||||
struct MixedAnnotations {
|
||||
[[= simdjson::rename<"user_name">]] std::string userName = "";
|
||||
[[= simdjson::skip]] int sessionToken = 0;
|
||||
int age = 0;
|
||||
};
|
||||
|
||||
#endif // SIMDJSON_STATIC_REFLECTION
|
||||
|
||||
namespace annotation_tests {
|
||||
|
||||
bool rename_serialize_test() {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
RenamedFields r{"Alice", "Smith", 30};
|
||||
std::string out;
|
||||
ASSERT_SUCCESS(simdjson::to_json(r).get(out));
|
||||
ASSERT_EQUAL(out, "{\"first_name\":\"Alice\",\"last_name\":\"Smith\",\"age\":30}");
|
||||
#endif
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool rename_deserialize_test() {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
std::string json = R"({"first_name":"Bob","last_name":"Jones","age":25})";
|
||||
simdjson::ondemand::parser parser;
|
||||
simdjson::ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(simdjson::pad(json)).get(doc));
|
||||
RenamedFields r;
|
||||
ASSERT_SUCCESS(doc.get<RenamedFields>().get(r));
|
||||
ASSERT_EQUAL(r.firstName, "Bob");
|
||||
ASSERT_EQUAL(r.lastName, "Jones");
|
||||
ASSERT_EQUAL(r.age, 25);
|
||||
#endif
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool rename_roundtrip_test() {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
RenamedFields original{"Carol", "White", 40};
|
||||
std::string json;
|
||||
ASSERT_SUCCESS(simdjson::to_json(original).get(json));
|
||||
simdjson::ondemand::parser parser;
|
||||
simdjson::ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(simdjson::pad(json)).get(doc));
|
||||
RenamedFields result;
|
||||
ASSERT_SUCCESS(doc.get<RenamedFields>().get(result));
|
||||
ASSERT_EQUAL(result.firstName, original.firstName);
|
||||
ASSERT_EQUAL(result.lastName, original.lastName);
|
||||
ASSERT_EQUAL(result.age, original.age);
|
||||
#endif
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool skip_serialize_test() {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
SkippedField s{"Alice", 999};
|
||||
std::string out;
|
||||
ASSERT_SUCCESS(simdjson::to_json(s).get(out));
|
||||
ASSERT_EQUAL(out, "{\"name\":\"Alice\"}");
|
||||
#endif
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool skip_deserialize_ignores_field_test() {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
// The key "internalCache" is present in JSON but the field is annotated skip -
|
||||
// it should be ignored and the field should keep its default value.
|
||||
std::string json = R"({"name":"Carol","internalCache":999})";
|
||||
simdjson::ondemand::parser parser;
|
||||
simdjson::ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(simdjson::pad(json)).get(doc));
|
||||
SkippedField s;
|
||||
ASSERT_SUCCESS(doc.get<SkippedField>().get(s));
|
||||
ASSERT_EQUAL(s.name, "Carol");
|
||||
ASSERT_EQUAL(s.internalCache, 0);
|
||||
#endif
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool mixed_annotations_serialize_test() {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
MixedAnnotations m{"dave", 12345, 28};
|
||||
std::string out;
|
||||
ASSERT_SUCCESS(simdjson::to_json(m).get(out));
|
||||
// sessionToken must not appear; userName must appear as "user_name"
|
||||
ASSERT_EQUAL(out, "{\"user_name\":\"dave\",\"age\":28}");
|
||||
#endif
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool mixed_annotations_deserialize_test() {
|
||||
TEST_START();
|
||||
#if SIMDJSON_STATIC_REFLECTION
|
||||
std::string json = R"({"user_name":"eve","age":35})";
|
||||
simdjson::ondemand::parser parser;
|
||||
simdjson::ondemand::document doc;
|
||||
ASSERT_SUCCESS(parser.iterate(simdjson::pad(json)).get(doc));
|
||||
MixedAnnotations m;
|
||||
ASSERT_SUCCESS(doc.get<MixedAnnotations>().get(m));
|
||||
ASSERT_EQUAL(m.userName, "eve");
|
||||
ASSERT_EQUAL(m.age, 35);
|
||||
ASSERT_EQUAL(m.sessionToken, 0);
|
||||
#endif
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
bool run_all() {
|
||||
return rename_serialize_test()
|
||||
&& rename_deserialize_test()
|
||||
&& rename_roundtrip_test()
|
||||
&& skip_serialize_test()
|
||||
&& skip_deserialize_ignores_field_test()
|
||||
&& mixed_annotations_serialize_test()
|
||||
&& mixed_annotations_deserialize_test();
|
||||
}
|
||||
|
||||
} // namespace annotation_tests
|
||||
|
||||
int main() {
|
||||
return annotation_tests::run_all() ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
Reference in New Issue
Block a user