Skip to content

Commit f282977

Browse files
refactor: Add conf as default serde
1 parent 0635a4c commit f282977

22 files changed

Lines changed: 1700 additions & 773 deletions

CMakeLists.txt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,22 @@ include(Coverage)
1414
copy_compile_commands()
1515

1616
find_package(GTest CONFIG REQUIRED)
17-
find_package(nlohmann_json CONFIG REQUIRED)
1817
find_package(absl CONFIG REQUIRED)
1918

19+
# Optional serializer back-ends
20+
option(CPPFIG_ENABLE_JSON "Enable JSON serializer (requires nlohmann-json)" OFF)
21+
22+
# When building in-tree we always need JSON for the tests & benchmarks.
23+
# Detect whether nlohmann-json is available and enable automatically.
24+
find_package(nlohmann_json CONFIG QUIET)
25+
if(nlohmann_json_FOUND)
26+
set(CPPFIG_ENABLE_JSON ON CACHE BOOL "" FORCE)
27+
message(STATUS "nlohmann-json found — CPPFIG_ENABLE_JSON=ON")
28+
else()
29+
message(STATUS "nlohmann-json not found — JSON serializer disabled")
30+
endif()
31+
# Future: option(CPPFIG_ENABLE_TOML "Enable TOML serializer" OFF)
32+
2033
option(STRICT_BUILD "Enable warnings as errors" ON)
2134

2235
if(STRICT_BUILD)

README.md

Lines changed: 92 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ A modern, header-only C++20 configuration library with **compile-time type safet
1111
- **Compile-time type safety** - All type errors caught at compile time
1212
- **Zero macros** - Pure C++20 templates and concepts
1313
- **IDE-friendly** - Full autocompletion: `config.Get<settings::ServerPort>()`
14-
- **Hierarchical configuration** - Dot-notation paths create nested JSON
14+
- **Hierarchical configuration** - Dot-notation paths create nested structures
1515
- **Environment variable overrides** - Production-friendly configuration
1616
- **Validation** - Built-in validators with custom extensions
1717
- **Schema migration** - Automatically adds new settings to existing files
1818
- **Mockable** - GMock-compatible interfaces for unit testing
19-
- **Pluggable serialization** - JSON default, extensible to YAML/TOML
19+
- **Pluggable serialization** - Flat `.conf` default, JSON opt-in, extensible to YAML/TOML
2020
- **Thread-safe** - Opt-in `MultiThreadedPolicy` with reader-writer locking (zero overhead by default)
2121

2222
## Quick Start
@@ -67,8 +67,8 @@ using MySchema = cppfig::ConfigSchema<
6767
>;
6868
6969
int main() {
70-
// Create configuration manager
71-
cppfig::Configuration<MySchema> config("config.json");
70+
// Create configuration manager (.conf format by default)
71+
cppfig::Configuration<MySchema> config("config.conf");
7272
7373
// Load (creates file with defaults if missing)
7474
auto status = config.Load();
@@ -94,28 +94,42 @@ int main() {
9494
}
9595
```
9696

97-
### Generated JSON
97+
### Generated .conf
9898

99-
```json
100-
{
101-
"server": {
102-
"port": 8080,
103-
"host": "localhost"
104-
},
105-
"logging": {
106-
"level": "info"
107-
}
108-
}
99+
```conf
100+
server.port = 8080
101+
server.host = localhost
102+
logging.level = info
103+
```
104+
105+
<details>
106+
<summary>Using JSON instead?</summary>
107+
108+
```cpp
109+
#include <cppfig/cppfig.h>
110+
#include <cppfig/json.h> // opt-in
111+
112+
cppfig::Configuration<MySchema, cppfig::JsonSerializer> config("config.json");
113+
```
114+
115+
Enable with `-DCPPFIG_ENABLE_JSON=ON` or vcpkg feature `"json"`.
116+
See [Serializers](docs/serializers.md) for details.
117+
</details>
109118
```
110119

111120
## Installation
112121

113122
### Requirements
114123

115124
- C++20 compiler (GCC 11+, Clang 14+)
116-
- [nlohmann/json](https://github.com/nlohmann/json)
117125
- [Abseil](https://github.com/abseil/abseil-cpp) (for `absl::Status`)
118126

127+
### Optional Dependencies
128+
129+
| Dependency | Feature | CMake Option |
130+
|------------|---------|-------------|
131+
| [nlohmann/json](https://github.com/nlohmann/json) | JSON serializer | `CPPFIG_ENABLE_JSON` |
132+
119133
### Header-Only Integration
120134

121135
```bash
@@ -127,13 +141,30 @@ cp -r cppfig/src/cppfig your_project/include/
127141
```cmake
128142
add_subdirectory(cppfig)
129143
target_link_libraries(your_target PRIVATE cppfig)
144+
145+
# Optional: enable JSON support
146+
set(CPPFIG_ENABLE_JSON ON)
130147
```
131148

132149
### vcpkg
133150

151+
Core (`.conf` only, no extra dependencies):
152+
134153
```bash
135-
# Dependencies
136-
vcpkg install nlohmann-json abseil
154+
vcpkg install abseil
155+
```
156+
157+
With JSON support:
158+
159+
```json
160+
{
161+
"dependencies": [
162+
{
163+
"name": "cppfig",
164+
"features": ["json"]
165+
}
166+
]
167+
}
137168
```
138169

139170
## Documentation
@@ -156,8 +187,8 @@ By default, `Configuration` uses `SingleThreadedPolicy` (zero overhead). For con
156187

157188
```cpp
158189
// Thread-safe configuration:
159-
cppfig::Configuration<MySchema, cppfig::JsonSerializer, cppfig::MultiThreadedPolicy>
160-
config("config.json");
190+
cppfig::Configuration<MySchema, cppfig::ConfSerializer, cppfig::MultiThreadedPolicy>
191+
config("config.conf");
161192
```
162193
163194
| Policy | Overhead | Use case |
@@ -221,20 +252,33 @@ cppfig::Min(1).And(cppfig::Max(100))
221252
```cpp
222253
struct Point {
223254
int x, y;
255+
};
224256
225-
friend void to_json(nlohmann::json& j, const Point& p) {
226-
j = {{"x", p.x}, {"y", p.y}};
257+
// Register with cppfig — works with any serializer
258+
template <>
259+
struct cppfig::ConfigTraits<Point> {
260+
static auto Serialize(const Point& p) -> cppfig::Value {
261+
auto obj = cppfig::Value::Object();
262+
obj["x"] = cppfig::Value(p.x);
263+
obj["y"] = cppfig::Value(p.y);
264+
return obj;
227265
}
228-
friend void from_json(const nlohmann::json& j, Point& p) {
229-
j.at("x").get_to(p.x);
230-
j.at("y").get_to(p.y);
266+
static auto Deserialize(const cppfig::Value& v) -> std::optional<Point> {
267+
try {
268+
return Point{static_cast<int>(v["x"].Get<int64_t>()),
269+
static_cast<int>(v["y"].Get<int64_t>())};
270+
} catch (...) {
271+
return std::nullopt;
272+
}
273+
}
274+
static auto ToString(const Point& p) -> std::string {
275+
return "(" + std::to_string(p.x) + "," + std::to_string(p.y) + ")";
276+
}
277+
static auto FromString(std::string_view) -> std::optional<Point> {
278+
return std::nullopt;
231279
}
232280
};
233281
234-
// Register with cppfig
235-
template <>
236-
struct cppfig::ConfigTraits<Point> : cppfig::ConfigTraitsFromJsonAdl<Point> {};
237-
238282
// Use in settings
239283
struct Origin {
240284
static constexpr std::string_view path = "origin";
@@ -243,6 +287,15 @@ struct Origin {
243287
};
244288
```
245289

290+
If you have the JSON feature enabled and your type already has nlohmann ADL functions, you can use the shortcut:
291+
292+
```cpp
293+
#include <cppfig/json.h>
294+
295+
template <>
296+
struct cppfig::ConfigTraits<Point> : cppfig::ConfigTraitsFromJsonAdl<Point> {};
297+
```
298+
246299
### Testing
247300
248301
```cpp
@@ -265,7 +318,7 @@ TEST(MyTest, UsesConfiguration) {
265318

266319
```cpp
267320
cppfig::Configuration<Schema,
268-
Serializer = JsonSerializer,
321+
Serializer = ConfSerializer,
269322
ThreadPolicy = SingleThreadedPolicy>
270323

271324
// Methods
@@ -280,6 +333,10 @@ auto GetFilePath() const -> std::string_view;
280333
// Thread policies
281334
cppfig::SingleThreadedPolicy // Zero-overhead (default)
282335
cppfig::MultiThreadedPolicy // std::shared_mutex reader-writer locking
336+
337+
// Serializers
338+
cppfig::ConfSerializer // Built-in flat .conf (default)
339+
cppfig::JsonSerializer // Requires CPPFIG_ENABLE_JSON
283340
```
284341

285342
### ConfigSchema
@@ -349,9 +406,12 @@ cppfig/
349406
│ ├── configuration.h # Configuration class
350407
│ ├── schema.h # ConfigSchema template
351408
│ ├── setting.h # Setting concepts
352-
│ ├── traits.h # Type traits
409+
│ ├── traits.h # Type traits (Serialize/Deserialize)
410+
│ ├── value.h # Core Value type
353411
│ ├── validator.h # Validators
354-
│ ├── serializer.h # Serialization
412+
│ ├── serializer.h # Serializer concept
413+
│ ├── conf.h # Built-in .conf serializer
414+
│ ├── json.h # Optional JSON serializer
355415
│ ├── interface.h # Mockable interfaces
356416
│ ├── diff.h # Configuration diff
357417
│ ├── logging.h # Logging utilities

benchmark/benchmark_main.cpp

Lines changed: 51 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include <benchmark/benchmark.h>
22
#include <cppfig/cppfig.h>
3+
#include <cppfig/json.h>
34

45
#include <cstdlib>
56
#include <filesystem>
@@ -114,7 +115,7 @@ class BenchmarkFixture : public ::benchmark::Fixture {
114115
{
115116
std::filesystem::path temp_path = std::filesystem::temp_directory_path() / "cppfig_benchmark";
116117
temp_path += std::to_string(reinterpret_cast<uintptr_t>(this));
117-
temp_path += ".json";
118+
temp_path += ".conf";
118119
return temp_path.string();
119120
}
120121

@@ -457,82 +458,100 @@ BENCHMARK_REGISTER_F(BenchmarkFixture, JsonSerializerParse);
457458

458459
BENCHMARK_DEFINE_F(BenchmarkFixture, JsonSerializerStringify)(benchmark::State& state)
459460
{
460-
nlohmann::json json = { { "benchmark",
461-
{ { "string", "test" }, { "int", 42 }, { "double", 3.14159 }, { "bool", true }, { "validated", 50 } } } };
461+
auto value = Value::Object();
462+
auto benchmark_obj = Value::Object();
463+
benchmark_obj["string"] = Value("test");
464+
benchmark_obj["int"] = Value(42);
465+
benchmark_obj["double"] = Value(3.14159);
466+
benchmark_obj["bool"] = Value(true);
467+
benchmark_obj["validated"] = Value(50);
468+
value["benchmark"] = benchmark_obj;
462469

463470
for (auto _ : state) {
464-
auto result = JsonSerializer::Stringify(json);
471+
auto result = JsonSerializer::Stringify(value);
465472
benchmark::DoNotOptimize(result);
466473
}
467474
}
468475
BENCHMARK_REGISTER_F(BenchmarkFixture, JsonSerializerStringify);
469476

470-
BENCHMARK_DEFINE_F(BenchmarkFixture, JsonSerializerGetAtPath)(benchmark::State& state)
477+
BENCHMARK_DEFINE_F(BenchmarkFixture, ValueGetAtPath)(benchmark::State& state)
471478
{
472-
nlohmann::json json = { { "database", { { "connection", { { "host", "localhost" }, { "port", 5432 } } } } } };
479+
auto value = Value::Object();
480+
auto database = Value::Object();
481+
auto connection = Value::Object();
482+
connection["host"] = Value("localhost");
483+
connection["port"] = Value(5432);
484+
database["connection"] = connection;
485+
value["database"] = database;
473486
std::string path = "database.connection.host";
474487

475488
for (auto _ : state) {
476-
auto result = JsonSerializer::GetAtPath(json, path);
489+
auto result = value.GetAtPath(path);
477490
benchmark::DoNotOptimize(result);
478491
}
479492
}
480-
BENCHMARK_REGISTER_F(BenchmarkFixture, JsonSerializerGetAtPath);
493+
BENCHMARK_REGISTER_F(BenchmarkFixture, ValueGetAtPath);
481494

482-
BENCHMARK_DEFINE_F(BenchmarkFixture, JsonSerializerSetAtPath)(benchmark::State& state)
495+
BENCHMARK_DEFINE_F(BenchmarkFixture, ValueSetAtPath)(benchmark::State& state)
483496
{
484-
nlohmann::json json = { { "database", { { "connection", { { "host", "localhost" }, { "port", 5432 } } } } } };
497+
auto value = Value::Object();
498+
auto database = Value::Object();
499+
auto connection = Value::Object();
500+
connection["host"] = Value("localhost");
501+
connection["port"] = Value(5432);
502+
database["connection"] = connection;
503+
value["database"] = database;
485504
std::string path = "database.connection.host";
486505

487506
for (auto _ : state) {
488-
nlohmann::json json_copy = json;
489-
JsonSerializer::SetAtPath(json_copy, path, "example.com");
490-
benchmark::DoNotOptimize(json_copy);
507+
Value value_copy = value;
508+
value_copy.SetAtPath(path, Value("example.com"));
509+
benchmark::DoNotOptimize(value_copy);
491510
}
492511
}
493-
BENCHMARK_REGISTER_F(BenchmarkFixture, JsonSerializerSetAtPath);
512+
BENCHMARK_REGISTER_F(BenchmarkFixture, ValueSetAtPath);
494513

495-
BENCHMARK_DEFINE_F(BenchmarkFixture, TraitsToJsonInt)(benchmark::State& state)
514+
BENCHMARK_DEFINE_F(BenchmarkFixture, TraitsSerializeInt)(benchmark::State& state)
496515
{
497516
for (auto _ : state) {
498-
auto json = ConfigTraits<int>::ToJson(42);
499-
benchmark::DoNotOptimize(json);
517+
auto val = ConfigTraits<int>::Serialize(42);
518+
benchmark::DoNotOptimize(val);
500519
}
501520
}
502-
BENCHMARK_REGISTER_F(BenchmarkFixture, TraitsToJsonInt);
521+
BENCHMARK_REGISTER_F(BenchmarkFixture, TraitsSerializeInt);
503522

504-
BENCHMARK_DEFINE_F(BenchmarkFixture, TraitsFromJsonInt)(benchmark::State& state)
523+
BENCHMARK_DEFINE_F(BenchmarkFixture, TraitsDeserializeInt)(benchmark::State& state)
505524
{
506-
nlohmann::json json = 42;
525+
Value val(42);
507526

508527
for (auto _ : state) {
509-
auto value = ConfigTraits<int>::FromJson(json);
510-
benchmark::DoNotOptimize(value);
528+
auto result = ConfigTraits<int>::Deserialize(val);
529+
benchmark::DoNotOptimize(result);
511530
}
512531
}
513-
BENCHMARK_REGISTER_F(BenchmarkFixture, TraitsFromJsonInt);
532+
BENCHMARK_REGISTER_F(BenchmarkFixture, TraitsDeserializeInt);
514533

515-
BENCHMARK_DEFINE_F(BenchmarkFixture, TraitsToJsonString)(benchmark::State& state)
534+
BENCHMARK_DEFINE_F(BenchmarkFixture, TraitsSerializeString)(benchmark::State& state)
516535
{
517536
std::string value = "benchmark_test_string";
518537

519538
for (auto _ : state) {
520-
auto json = ConfigTraits<std::string>::ToJson(value);
521-
benchmark::DoNotOptimize(json);
539+
auto val = ConfigTraits<std::string>::Serialize(value);
540+
benchmark::DoNotOptimize(val);
522541
}
523542
}
524-
BENCHMARK_REGISTER_F(BenchmarkFixture, TraitsToJsonString);
543+
BENCHMARK_REGISTER_F(BenchmarkFixture, TraitsSerializeString);
525544

526-
BENCHMARK_DEFINE_F(BenchmarkFixture, TraitsFromJsonString)(benchmark::State& state)
545+
BENCHMARK_DEFINE_F(BenchmarkFixture, TraitsDeserializeString)(benchmark::State& state)
527546
{
528-
nlohmann::json json = "benchmark_test_string";
547+
Value val("benchmark_test_string");
529548

530549
for (auto _ : state) {
531-
auto value = ConfigTraits<std::string>::FromJson(json);
532-
benchmark::DoNotOptimize(value);
550+
auto result = ConfigTraits<std::string>::Deserialize(val);
551+
benchmark::DoNotOptimize(result);
533552
}
534553
}
535-
BENCHMARK_REGISTER_F(BenchmarkFixture, TraitsFromJsonString);
554+
BENCHMARK_REGISTER_F(BenchmarkFixture, TraitsDeserializeString);
536555

537556
} // namespace cppfig::bench
538557

0 commit comments

Comments
 (0)