Skip to content

Commit 4cadea1

Browse files
committed
Initial Commit
1 parent fc5b3f4 commit 4cadea1

File tree

7 files changed

+137
-45
lines changed

7 files changed

+137
-45
lines changed

AutoDescribe.hpp

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
#define BOOST_AUTO_DESCRIBE_HPP
55

66
// AutoDescribe.hpp
7-
// "필드 시퀀스"를 한 번만 정의하면 구조체와 BOOST_DESCRIBE_STRUCT를 자동 생성하는 유틸
7+
// A utility that generates both the struct definition and BOOST_DESCRIBE_STRUCT automatically
8+
// by defining the "field sequence" only once.
89

910
#include <boost/preprocessor.hpp>
1011
#include <boost/describe.hpp>
@@ -15,19 +16,19 @@ BOOST_PP_TUPLE_ELEM(3, 1, elem) BOOST_PP_TUPLE_ELEM(3, 0, elem){ \
1516
BOOST_PP_TUPLE_ELEM(3, 2, elem) \
1617
};
1718

18-
// describe에 넣을 멤버 이름 목록 추출
19+
// Extract the list of member names for use in describe
1920
#define ADH_EXTRACT_NAME(r, data, i, elem) \
2021
BOOST_PP_COMMA_IF(i) BOOST_PP_TUPLE_ELEM(3, 0, elem)
2122

22-
// 구조체와 describe를 한 번에 생성
23+
// Generate both the struct and its describe at once
2324
#define ADH_MAKE_STRUCT_AND_DESCRIBE(name, fields_seq) \
2425
struct name { \
2526
BOOST_PP_SEQ_FOR_EACH(ADH_DECLARE_MEMBER, _, fields_seq) \
2627
}; \
2728
BOOST_DESCRIBE_STRUCT( \
28-
name, \
29-
(), \
30-
(BOOST_PP_SEQ_FOR_EACH_I(ADH_EXTRACT_NAME, _, fields_seq)) \
31-
)
29+
name, \
30+
(), \
31+
(BOOST_PP_SEQ_FOR_EACH_I(ADH_EXTRACT_NAME, _, fields_seq)) \
32+
)
3233

3334
#endif // AUTODESCRIBE_HPP

CMakeLists.txt

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ if(POLICY CMP0167)
99
cmake_policy(SET CMP0167 NEW)
1010
endif()
1111

12-
# ---- vcpkg (요청하신 MinGW 블록 그대로 유지) ----
12+
# ---- vcpkg (Keep the requested MinGW block as is) ----
1313
if (WIN32 AND MSVC)
14-
# MSVC 전용 설정 필요시 여기에
14+
# Add MSVC-specific settings here if needed
1515
endif()
1616

1717
if (WIN32 AND MINGW)
@@ -21,7 +21,7 @@ if (WIN32 AND MINGW)
2121
set(CMAKE_TOOLCHAIN_FILE "${VCPKG_ROOT}/vcpkg/scripts/buildsystems/vcpkg.cmake")
2222
endif()
2323

24-
# (선택) Linux 감지
24+
# (Optional) Detect Linux
2525
if(UNIX AND NOT WIN32)
2626
message(STATUS "Detected Linux")
2727
if (EXISTS "/etc/os-release")
@@ -50,23 +50,23 @@ if (WIN32 AND NOT DEFINED LIBCLANG_DLL)
5050
endif()
5151

5252
if (WIN32 AND NOT LIBCLANG_DLL)
53-
message(FATAL_ERROR "LIBCLANG_DLL 경로를 지정해 주세요. 예) -DLIBCLANG_DLL=C:/Users/<you>/scoop/apps/llvm/current/bin/libclang.dll")
53+
message(FATAL_ERROR "Please specify the LIBCLANG_DLL path. Example: -DLIBCLANG_DLL=C:/Users/<you>/scoop/apps/llvm/current/bin/libclang.dll")
5454
endif()
5555

5656
# ---- Boost(JSON) ----
5757
find_package(Boost 1.75 CONFIG REQUIRED COMPONENTS json)
5858

59-
# ---- 경로/파일 ----
59+
# ---- Paths / Files ----
6060
set(SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
6161
set(TOOL_DIR "${SRC_DIR}/tools")
6262
set(MODEL_HEADER "${SRC_DIR}/model.hpp")
63-
# 요청대로 소스 트리에 생성
63+
# Create in the source tree as requested
6464
set(GEN_HEADER "${SRC_DIR}/describe_all.gen.hpp")
6565

66-
# 생성물 표시
66+
# Mark generated files
6767
set_source_files_properties("${GEN_HEADER}" PROPERTIES GENERATED TRUE)
6868

69-
# ---- 생성 규칙: OUTPUT 한 번만 정의 ----
69+
# ---- Generation rule: define OUTPUT only once ----
7070
add_custom_command(
7171
OUTPUT "${GEN_HEADER}"
7272
COMMAND ${CMAKE_COMMAND} -E env PYTHONUTF8=1
@@ -82,10 +82,10 @@ add_custom_command(
8282
VERBATIM
8383
)
8484

85-
# 이 타깃은 파일 생성 결과에만 의존
85+
# This target depends only on the generated file result
8686
add_custom_target(gen_describe DEPENDS "${GEN_HEADER}")
8787

88-
# ---- 실행 파일 ----
88+
# ---- Executable ----
8989
add_executable(demo
9090
"${SRC_DIR}/main.cpp"
9191
"${SRC_DIR}/AutoDescribe.hpp"
@@ -103,7 +103,7 @@ else()
103103
target_compile_options(demo PRIVATE -Wall -Wextra -Wpedantic -Wno-variadic-macros)
104104
endif()
105105

106-
# ---- IDE 전용 파일 노출 ----
106+
# ---- Expose IDE-only files ----
107107
set(IDE_ONLY_FILES
108108
"${TOOL_DIR}/gen_all_structs.py"
109109
"${TOOL_DIR}/gen_fields.py"

JsonCodec.hpp

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// JsonCodec.hpp
2-
// Boost.JSON + Boost.Describe 래퍼 (헤더 전용, Boost 1.88 호환)
2+
// Boost.JSON + Boost.Describe wrapper (header-only, compatible with Boost 1.88)
33
#pragma once
44

55
#ifndef BOOST_JSON_CODEC_HPP
@@ -19,11 +19,11 @@ namespace json = boost::json;
1919
namespace bd = boost::describe;
2020
namespace mp11 = boost::mp11;
2121

22-
// Boost 1.88: has_describe_members<T> (접근자 인자 없음)
22+
// Boost 1.88: has_describe_members<T> (no accessor arguments)
2323
template<class T>
2424
using EnableIfDescribed = std::enable_if_t<bd::has_describe_members<T>::value>;
2525

26-
// describe된 타입 공통 직렬화 (struct -> JSON)
26+
// Common serialization for described types (struct -> JSON)
2727
template<class T, class = EnableIfDescribed<T>>
2828
void tag_invoke(json::value_from_tag, json::value& jv, T const& t) {
2929
json::object obj;
@@ -38,7 +38,7 @@ class JsonCodec {
3838
public:
3939
enum class MissingPolicy { Strict, Lenient };
4040

41-
// 간단 프리터(문자열 기반 pretty): 작은 유틸
41+
// Simple pretty printer (string-based pretty print): small utility
4242
static std::string simple_pretty(std::string s, int indent = 2) {
4343
std::string out; out.reserve(s.size() + s.size()/4);
4444
int level = 0; bool in_str = false; bool esc = false;
@@ -88,7 +88,7 @@ class JsonCodec {
8888

8989
template<class T, class = EnableIfDescribed<T>>
9090
static T fromValue(const json::value& jv, MissingPolicy policy = MissingPolicy::Strict) {
91-
if (!jv.is_object()) throw std::runtime_error("JSON 루트가 객체가 아닙니다.");
91+
if (!jv.is_object()) throw std::runtime_error("JSON root is not an object.");
9292
const json::object& obj = jv.as_object();
9393

9494
T out{};
@@ -98,13 +98,13 @@ class JsonCodec {
9898
auto it = obj.find(D.name);
9999
if (it == obj.end()) {
100100
if (policy == MissingPolicy::Strict)
101-
throw std::runtime_error(std::string("필수 키 누락: ") + D.name);
102-
return; // Lenient: 기본값 유지
101+
throw std::runtime_error(std::string("Missing required key: ") + D.name);
102+
return; // Lenient: keep default value
103103
}
104104
try {
105105
(out.*(D.pointer)) = json::value_to<MemberType>(it->value());
106106
} catch (const std::exception& e) {
107-
throw std::runtime_error(std::string("필드 변환 실패: ") + D.name + " - " + e.what());
107+
throw std::runtime_error(std::string("Field conversion failed: ") + D.name + " - " + e.what());
108108
}
109109
});
110110
return out;
@@ -120,7 +120,7 @@ class JsonCodec {
120120
static T fromString(const std::string& s, MissingPolicy policy = MissingPolicy::Strict) {
121121
boost::system::error_code ec;
122122
json::value v = json::parse(s, ec);
123-
if (ec) throw std::runtime_error("JSON 파싱 실패: " + ec.message());
123+
if (ec) throw std::runtime_error("JSON parsing failed: " + ec.message());
124124
return fromValue<T>(v, policy);
125125
}
126126

@@ -142,24 +142,24 @@ class JsonCodec {
142142
static json::value parse(const std::string& s) {
143143
boost::system::error_code ec;
144144
json::value v = json::parse(s, ec);
145-
if (ec) throw std::runtime_error("JSON 파싱 실패: " + ec.message());
145+
if (ec) throw std::runtime_error("JSON parsing failed: " + ec.message());
146146
return v;
147147
}
148148

149149
static void saveValue(const std::string& path, const json::value& v, bool pretty = true) {
150150
std::ofstream ofs(path, std::ios::binary);
151-
if (!ofs) throw std::runtime_error("파일을 쓸 수 없습니다: " + path);
151+
if (!ofs) throw std::runtime_error("Cannot write to file: " + path);
152152
std::string s = dump(v, pretty);
153153
ofs.write(s.data(), static_cast<std::streamsize>(s.size()));
154154
}
155155

156156
static json::value loadValue(const std::string& path) {
157157
std::ifstream ifs(path, std::ios::binary);
158-
if (!ifs) throw std::runtime_error("파일을 열 수 없습니다: " + path);
158+
if (!ifs) throw std::runtime_error("Cannot open file: " + path);
159159
std::string s((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
160160
boost::system::error_code ec;
161161
json::value v = json::parse(s, ec);
162-
if (ec) throw std::runtime_error("JSON 파싱 실패: " + ec.message());
162+
if (ec) throw std::runtime_error("JSON parsing failed: " + ec.message());
163163
return v;
164164
}
165165
};

README.ko.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Boost.JSON AutoDescribe 데모
2+
3+
[View in English](README.md)
4+
5+
## 개요
6+
이 프로젝트는 **Boost.JSON****Boost.Describe**를 이용하여 C++ 구조체와 JSON 간의 자동 변환을 구현한 예제입니다.
7+
"필드 시퀀스"를 한 번만 정의하면 구조체 정의와 `BOOST_DESCRIBE_STRUCT` 선언을 자동으로 생성하여 코드 중복을 줄이고 유지보수성을 향상시킵니다.
8+
9+
## 주요 기능
10+
- **자동 구조체 기술**
11+
`AutoDescribe.hpp`를 사용해 구조체 정의와 `BOOST_DESCRIBE_STRUCT`를 동시에 생성합니다.
12+
13+
- **JSON 직렬화 / 역직렬화**
14+
`JsonCodec.hpp`를 통해 기술된 구조체를 Boost.JSON을 사용하여 손쉽게 JSON으로 변환하거나, JSON에서 구조체로 변환할 수 있습니다.
15+
16+
- **Pretty 출력**
17+
가독성을 높이기 위한 내장 프리티 프린터 제공.
18+
19+
- **파일 입출력**
20+
JSON 데이터를 파일에 저장하고 파일에서 불러오는 기능 제공.
21+
22+
- **Clang 기반 코드 생성**
23+
Python 도구(`gen_fields.py`, `gen_all_structs.py`)가 libclang을 사용하여 헤더를 스캔하고 `BOOST_DESCRIBE_STRUCT`를 자동 생성합니다.
24+
25+
## 의존성
26+
- **Boost** >= 1.75 (Boost 1.88에서 테스트됨)
27+
- **Python 3** (`clang` 바인딩 포함)
28+
- **libclang** (LLVM)
29+
30+
## 빌드 방법
31+
```bash
32+
cmake -B build -S . -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_DLL=/path/to/libclang.dll
33+
cmake --build build
34+
```
35+
36+
## 예제
37+
```cpp
38+
hello::world::User user{"John Doe", 30, true, {"[email protected]", 95.5}, {}};
39+
std::string json_str = JsonCodec::toString(user, true);
40+
std::cout << json_str << std::endl;
41+
42+
hello::world::User loaded = JsonCodec::fromString<hello::world::User>(json_str);
43+
```
44+
45+
## 라이선스
46+
이 프로젝트는 MIT 라이선스를 따릅니다. 자세한 내용은 [LICENSE](LICENSE) 파일을 참고하세요.

README.md

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,46 @@
1-
# boost.json.codec
2-
Using Boost JSON
1+
# Boost.JSON AutoDescribe Demo
2+
3+
[한국어 설명 보기](README.ko.md)
4+
5+
## Overview
6+
This project demonstrates an automatic conversion system between C++ structures and JSON using **Boost.JSON** and **Boost.Describe**.
7+
By defining the "field sequence" only once, it can automatically generate both the struct definition and `BOOST_DESCRIBE_STRUCT` declarations, reducing redundancy and improving maintainability.
8+
9+
## Features
10+
- **Automatic Struct Description**
11+
Uses `AutoDescribe.hpp` to generate struct definitions and `BOOST_DESCRIBE_STRUCT` at the same time.
12+
13+
- **JSON Serialization / Deserialization**
14+
`JsonCodec.hpp` provides easy-to-use functions to convert described structs to and from JSON using Boost.JSON.
15+
16+
- **Pretty Printing**
17+
Built-in pretty printer for better readability.
18+
19+
- **File I/O**
20+
Save and load JSON data directly to and from files.
21+
22+
- **Clang-based Code Generation**
23+
Python tools (`gen_fields.py`, `gen_all_structs.py`) use libclang to scan headers and generate `BOOST_DESCRIBE_STRUCT` automatically.
24+
25+
## Dependencies
26+
- **Boost** >= 1.75 (Tested with Boost 1.88)
27+
- **Python 3** with `clang` bindings
28+
- **libclang** (LLVM)
29+
30+
## Building
31+
```bash
32+
cmake -B build -S . -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_DLL=/path/to/libclang.dll
33+
cmake --build build
34+
```
35+
36+
## Example
37+
```cpp
38+
hello::world::User user{"John Doe", 30, true, {"[email protected]", 95.5}, {}};
39+
std::string json_str = JsonCodec::toString(user, true);
40+
std::cout << json_str << std::endl;
41+
42+
hello::world::User loaded = JsonCodec::fromString<hello::world::User>(json_str);
43+
```
44+
45+
## License
46+
This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.

main.cpp

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
#include "model.hpp"
44
#include "AutoDescribe.hpp"
5-
#include "describe_all.gen.hpp" // describe 먼저
6-
#include "JsonCodec.hpp" // 그다음 JsonCodec
5+
#include "describe_all.gen.hpp" // describe first
6+
#include "JsonCodec.hpp" // then JsonCodec
77

88
#include <iostream>
99
#include <string>
@@ -12,7 +12,7 @@
1212
int main() {
1313
try {
1414
hello::world::User u1{
15-
"홍길동",
15+
"Hong Gil-dong",
1616
29,
1717
true,
1818
hello::world::Profile{"[email protected]", 98.5},
@@ -26,7 +26,7 @@ int main() {
2626
std::cout << "[User -> JSON]\n" << s1 << "\n\n";
2727

2828
const char* js = R"({
29-
"name": "이몽룡",
29+
"name": "Lee Mong-ryong",
3030
"age": 21,
3131
"admin": false,
3232
"profile": { "email": "[email protected]", "score": 91.2 },
@@ -45,23 +45,23 @@ int main() {
4545
<< ", admin="<< (u2.admin ? "true":"false")
4646
<< ", email="<< u2.profile.email
4747
<< ", score="<< u2.profile.score
48-
<< ", projects=" << u2.projects.size() << "\n";
48+
<< ", projects=" << u2.projects.size() << " items\n";
4949
if (!u2.projects.empty()) {
50-
std::cout << " 첫 프로젝트: " << u2.projects[0].title << "\n";
50+
std::cout << " First project: " << u2.projects[0].title << "\n";
5151
}
5252

5353
JsonCodec::saveFile("user.json", u1, true);
5454

5555
hello::world::User u3 =
5656
JsonCodec::loadFile<hello::world::User>("user.json", JsonCodec::MissingPolicy::Lenient);
5757

58-
std::cout << "[파일 로드] name=" << u3.name
59-
<< ", projects=" << u3.projects.size() << "\n";
58+
std::cout << "[File Load] name=" << u3.name
59+
<< ", projects=" << u3.projects.size() << " items\n";
6060

6161
NonNamespaceProfile nnp1;
6262

6363
} catch (const std::exception& e) {
64-
std::cerr << "[오류] " << e.what() << "\n";
64+
std::cerr << "[Error] " << e.what() << "\n";
6565
return 1;
6666
}
6767
return 0;

tools/gen_fields.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ def setup_libclang(path_from_cli: str | None):
1919
def parse_args():
2020
ap = argparse.ArgumentParser()
2121
ap.add_argument("--libclang", default=None)
22-
ap.add_argument("--all", action="store_true", help="헤더 전체를 스캔하여 해당 파일에 정의된 모든 struct/class를 기술")
22+
ap.add_argument("--all", action="store_true",
23+
help="Scan the entire header and describe all struct/class definitions in that file")
2324
ap.add_argument("header")
24-
ap.add_argument("struct", nargs="?", help="--all 미사용 시 단일 구조체 이름")
25+
ap.add_argument("struct", nargs="?", help="Struct name when --all is not used")
2526
return ap.parse_args()
2627

2728
def norm(p: str) -> str:
@@ -56,7 +57,7 @@ def collect_fields(node):
5657
return names
5758

5859
def should_skip_name(name: str) -> bool:
59-
# 시스템/런타임 내부 심볼 제외
60+
# Exclude system/runtime internal symbols
6061
return name.startswith("_")
6162

6263
def main():

0 commit comments

Comments
 (0)