diff --git a/include/rfl.hpp b/include/rfl.hpp index 74e7ef6f0..b57542b62 100644 --- a/include/rfl.hpp +++ b/include/rfl.hpp @@ -41,6 +41,7 @@ #include "rfl/Skip.hpp" #include "rfl/SnakeCaseToCamelCase.hpp" #include "rfl/SnakeCaseToPascalCase.hpp" +#include "rfl/CamelCaseToSnakeCase.hpp" #include "rfl/TaggedUnion.hpp" #include "rfl/Timestamp.hpp" #include "rfl/UnderlyingEnums.hpp" diff --git a/include/rfl/CamelCaseToSnakeCase.hpp b/include/rfl/CamelCaseToSnakeCase.hpp new file mode 100644 index 000000000..d41d96982 --- /dev/null +++ b/include/rfl/CamelCaseToSnakeCase.hpp @@ -0,0 +1,38 @@ +#ifndef RFL_CAMELCASETOSNAKECASE_HPP_ +#define RFL_CAMELCASETOSNAKECASE_HPP_ + +#include "Field.hpp" +#include "internal/is_rename.hpp" +#include "internal/transform_case.hpp" + +namespace rfl { + +struct CamelCaseToSnakeCase { + public: + /// Replaces all instances of camelCase field names with snake_case. + template + static auto process(const auto& _named_tuple) { + return _named_tuple.transform([](const FieldType& _f) { + if constexpr (FieldType::name() != "xml_content" && + !internal::is_rename_v) { + return handle_one_field(_f); + } else { + return _f; + } + }); + } + + private: + /// Applies the logic to a single field. + template + static auto handle_one_field(const FieldType& _f) { + using NewFieldType = + Field(), + typename FieldType::Type>; + return NewFieldType(_f.value()); + } +}; + +} // namespace rfl + +#endif diff --git a/include/rfl/SnakeCaseToCamelCase.hpp b/include/rfl/SnakeCaseToCamelCase.hpp index 2681e1f61..17746b291 100644 --- a/include/rfl/SnakeCaseToCamelCase.hpp +++ b/include/rfl/SnakeCaseToCamelCase.hpp @@ -3,7 +3,7 @@ #include "Field.hpp" #include "internal/is_rename.hpp" -#include "internal/transform_snake_case.hpp" +#include "internal/transform_case.hpp" namespace rfl { diff --git a/include/rfl/SnakeCaseToPascalCase.hpp b/include/rfl/SnakeCaseToPascalCase.hpp index 1eb454373..b53b714f6 100644 --- a/include/rfl/SnakeCaseToPascalCase.hpp +++ b/include/rfl/SnakeCaseToPascalCase.hpp @@ -3,7 +3,7 @@ #include "Field.hpp" #include "internal/is_rename.hpp" -#include "internal/transform_snake_case.hpp" +#include "internal/transform_case.hpp" namespace rfl { diff --git a/include/rfl/internal/transform_snake_case.hpp b/include/rfl/internal/transform_case.hpp similarity index 68% rename from include/rfl/internal/transform_snake_case.hpp rename to include/rfl/internal/transform_case.hpp index cf7360d52..fc10a4d37 100644 --- a/include/rfl/internal/transform_snake_case.hpp +++ b/include/rfl/internal/transform_case.hpp @@ -24,6 +24,11 @@ consteval char to_lower() { } } +template +consteval bool is_upper() { + return c >= 'A' && c <= 'Z'; +} + /// Transforms the field name from snake case to camel case. template @@ -50,6 +55,23 @@ consteval auto transform_snake_case() { _name.arr_[_i]>(); } } + +/// Transforms the field name from camel case to snake case +template +consteval auto transform_camel_case() { + if constexpr (_i == _name.arr_.size()) { + return StringLiteral(chars...); + + } else if constexpr (_name.arr_[_i] == '\0') { + return transform_camel_case<_name, _name.arr_.size(), chars...>(); + + } else if constexpr (is_upper<_name.arr_[_i]>()) { + return transform_camel_case<_name, _i + 1, chars..., '_', to_lower<_name.arr_[_i]>()>(); + + } else { + return transform_camel_case<_name, _i + 1, chars..., _name.arr_[_i]>(); + } +} } // namespace rfl::internal #endif diff --git a/src/rfl/internal/strings/strings.cpp b/src/rfl/internal/strings/strings.cpp index ec4526ce1..663d0cd4b 100644 --- a/src/rfl/internal/strings/strings.cpp +++ b/src/rfl/internal/strings/strings.cpp @@ -60,6 +60,7 @@ std::vector split(const std::string& _str, std::string to_camel_case(const std::string& _str) { std::string result; + result.reserve(_str.size()); bool capitalize = false; for (const char ch : _str) { if (ch == '_') { diff --git a/tests/json/test_camel_case_to_snake_case_rename.cpp b/tests/json/test_camel_case_to_snake_case_rename.cpp new file mode 100644 index 000000000..b9ff0f246 --- /dev/null +++ b/tests/json/test_camel_case_to_snake_case_rename.cpp @@ -0,0 +1,24 @@ +#include +#include +#include + +#include "write_and_read.hpp" + +namespace test_camel_case_to_snake_case_rename { + +struct Person { + std::string firstName; + std::string lastName; + rfl::Rename<"homeTown", std::string> homeTown = "Springfield"; +}; + +TEST(json, test_camel_case_to_snake_case_rename) { + + const auto homer = Person{.firstName = "Homer", .lastName = "Simpson"}; + + write_and_read( + homer, + R"({"first_name":"Homer","last_name":"Simpson","homeTown":"Springfield"})"); +} + +} // namespace test_camel_case_to_snake_case_rename