From fa88114b6e4f0b9f4316efa3ac25f6b95ed95238 Mon Sep 17 00:00:00 2001 From: Charles Cabergs Date: Tue, 27 Jan 2026 20:07:28 +0100 Subject: [PATCH 1/3] Add camel case to snake case conversion --- include/rfl.hpp | 1 + include/rfl/CamelCaseToSnakeCase.hpp | 38 +++++++++++++++++++ include/rfl/SnakeCaseToCamelCase.hpp | 2 +- include/rfl/SnakeCaseToPascalCase.hpp | 2 +- ...form_snake_case.hpp => transform_case.hpp} | 22 +++++++++++ src/rfl/internal/strings/strings.cpp | 1 + .../test_camel_case_to_snake_case_rename.cpp | 24 ++++++++++++ 7 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 include/rfl/CamelCaseToSnakeCase.hpp rename include/rfl/internal/{transform_snake_case.hpp => transform_case.hpp} (68%) create mode 100644 tests/json/test_camel_case_to_snake_case_rename.cpp 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..f23f656c8 --- /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 snake_case field names with camelCase. + 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..8353508b3 --- /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_pascal_case_to_camel_case_rename From 9fd01e2249e8d56817aedb37958cf853ac6fc694 Mon Sep 17 00:00:00 2001 From: Charles Cabergs Date: Tue, 27 Jan 2026 21:44:33 +0100 Subject: [PATCH 2/3] Update include/rfl/CamelCaseToSnakeCase.hpp Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- include/rfl/CamelCaseToSnakeCase.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rfl/CamelCaseToSnakeCase.hpp b/include/rfl/CamelCaseToSnakeCase.hpp index f23f656c8..d41d96982 100644 --- a/include/rfl/CamelCaseToSnakeCase.hpp +++ b/include/rfl/CamelCaseToSnakeCase.hpp @@ -9,7 +9,7 @@ namespace rfl { struct CamelCaseToSnakeCase { public: - /// Replaces all instances of snake_case field names with camelCase. + /// 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) { From 70898d092919d60dc92e9c1648f25d21db0fdfb4 Mon Sep 17 00:00:00 2001 From: Charles Cabergs Date: Tue, 27 Jan 2026 21:45:16 +0100 Subject: [PATCH 3/3] Update tests/json/test_camel_case_to_snake_case_rename.cpp Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- tests/json/test_camel_case_to_snake_case_rename.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/json/test_camel_case_to_snake_case_rename.cpp b/tests/json/test_camel_case_to_snake_case_rename.cpp index 8353508b3..b9ff0f246 100644 --- a/tests/json/test_camel_case_to_snake_case_rename.cpp +++ b/tests/json/test_camel_case_to_snake_case_rename.cpp @@ -21,4 +21,4 @@ TEST(json, test_camel_case_to_snake_case_rename) { R"({"first_name":"Homer","last_name":"Simpson","homeTown":"Springfield"})"); } -} // namespace test_pascal_case_to_camel_case_rename +} // namespace test_camel_case_to_snake_case_rename