Comment exporter les champs d'une struct C++ vers JSON en utilisant clang et boost::json
Voir aussi la version Rust de ce post : Comment exporter les champs d’une struct C++ vers JSON en utilisant Rust et clang
Dans notre post précédent Comment analyser une définition de struct C++ depuis le code source en utilisant clang, nous avons appris comment analyser du code C/C++ et extraire des informations sur les définitions de struct en utilisant la bibliothèque clang.
Dans ledit post, les informations étaient affichées dans un format libre, lisible par l’homme, cependant :
Struct: myParameters
Field: a (Type: double)
Field: b (Type: double)Dans ce post, nous allons étendre l’exemple précédent pour exporter les champs de la struct au format JSON en utilisant la bibliothèque boost::json.
Code source complet
#include <clang-c/Index.h>
#include <iostream>
#include <vector>
#include <string>
#include <cstring>
#include <fstream>
#include <filesystem>
#include <boost/json.hpp>
boost::json::value extractStructFields(CXCursor cursor) {
CXCursorKind kind = clang_getCursorKind(cursor);
if (kind == CXCursor_StructDecl) {
CXString structName = clang_getCursorSpelling(cursor);
std::string structNameStr = clang_getCString(structName);
clang_disposeString(structName);
boost::json::object structObj;
structObj["name"] = structNameStr;
boost::json::array fieldsArray;
struct FieldCollector {
boost::json::array& fields;
int currentIndex;
};
FieldCollector collector = {fieldsArray, 0};
clang_visitChildren(cursor, [](CXCursor c, CXCursor parent, CXClientData client_data) {
auto* collector = static_cast<FieldCollector*>(client_data);
CXCursorKind kind = clang_getCursorKind(c);
if (kind == CXCursor_FieldDecl) {
boost::json::object fieldObj;
CXString fieldName = clang_getCursorSpelling(c);
CXType fieldType = clang_getCursorType(c);
CXString typeName = clang_getTypeSpelling(fieldType);
fieldObj["index"] = collector->currentIndex++;
fieldObj["name"] = clang_getCString(fieldName);
fieldObj["type"] = clang_getCString(typeName);
collector->fields.push_back(fieldObj);
clang_disposeString(fieldName);
clang_disposeString(typeName);
}
return CXChildVisit_Continue;
}, &collector);
structObj["fields"] = fieldsArray;
return structObj;
}
return boost::json::value(nullptr);
}
int main(int argc, char** argv) {
CXIndex index = clang_createIndex(0, 0);
// Read code from argv[1]
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " <source file>" << std::endl;
return 1;
}
const char* codeFilename = argv[1];
if(!std::filesystem::exists(codeFilename)) {
std::cerr << "File does not exist: " << codeFilename << std::endl;
return 1;
}
std::ifstream codeFile(codeFilename);
if (!codeFile) {
std::cerr << "Error opening file: " << codeFilename << std::endl;
return 1;
}
std::string code((std::istreambuf_iterator<char>(codeFile)), std::istreambuf_iterator<char>());
codeFile.close();
if (code.empty()) {
std::cerr << "File is empty: " << codeFilename << std::endl;
return 1;
}
CXUnsavedFile unsavedFile = {"test.cpp", code.c_str(), code.size()};
CXTranslationUnit unit = clang_parseTranslationUnit(index, "test.cpp", nullptr, 0, &unsavedFile, 1, CXTranslationUnit_None);
if (unit == nullptr) {
std::cerr << "Failed to parse translation unit." << std::endl;
return 1;
}
CXCursor cursor = clang_getTranslationUnitCursor(unit);
boost::json::array structsArray;
clang_visitChildren(cursor, [](CXCursor c, CXCursor parent, CXClientData client_data) {
auto* structs = static_cast<boost::json::array*>(client_data);
boost::json::value structJson = extractStructFields(c);
if (!structJson.is_null()) {
structs->push_back(structJson);
}
return CXChildVisit_Continue;
}, &structsArray);
boost::json::object result;
result["structs"] = structsArray;
std::cout << boost::json::serialize(result) << std::endl;
clang_disposeTranslationUnit(unit);
clang_disposeIndex(index);
return 0;
}Comment compiler
Dans ce cas, nous avons utilisé cmake pour compiler le projet. Le fichier CMakeLists.txt ressemble à ceci :
cmake_minimum_required(VERSION 3.10)
project(parse_structs)
# C++17 Standard festlegen
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# LLVM/Clang-Komponenten finden
find_package(LLVM 19.1.1 REQUIRED CONFIG)
find_package(Clang REQUIRED CONFIG)
find_package(Boost REQUIRED COMPONENTS json)
# Include-Verzeichnisse hinzufügen
include_directories(${LLVM_INCLUDE_DIRS})
include_directories(${CLANG_INCLUDE_DIRS})
# Executable erstellen
add_executable(parse_structs parse_structs.cpp)
# LLVM-Definitionen hinzufügen
target_compile_definitions(parse_structs PUBLIC ${LLVM_DEFINITIONS})
# Gegen libclang linken
target_link_libraries(parse_structs PUBLIC libclang ${Boost_LIBRARIES})Comment exécuter
./parse_structs test.cpp | jq| jq est utilisé pour afficher joliment la sortie JSON. Vous n’avez pas besoin de l’utiliser si vous ne voulez pas afficher joliment la sortie.
avec test.cpp contenant, par exemple, le code suivant :
struct myParameters {
double a;
double b;
};Exemple de sortie
{
"structs": [
{
"name": "myParameters",
"fields": [
{
"index": 0,
"name": "a",
"type": "double"
},
{
"index": 1,
"name": "b",
"type": "double"
}
]
}
]
}