Como exportar campos de struct C++ para JSON usando clang e boost::json
Veja também a versão em Rust deste post: Como exportar campos de struct C++ para JSON usando Rust & clang
Em nosso post anterior Como analisar a definição de struct C++ a partir do código-fonte usando clang, aprendemos como analisar código C/C++ e extrair informações sobre definições de struct usando a biblioteca clang.
Naquele post, as informações foram exibidas em um formato livre e legível por humanos, no entanto:
Struct: myParameters
Field: a (Type: double)
Field: b (Type: double)Neste post, estenderemos o exemplo anterior para exportar os campos da struct para o formato JSON usando a biblioteca boost::json.
Código-fonte completo
#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);
// Ler o código de 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;
}Como compilar
Neste caso, usamos o cmake para compilar o projeto. O arquivo CMakeLists.txt tem a seguinte aparência:
cmake_minimum_required(VERSION 3.10)
project(parse_structs)
# Definir o padrão C++17
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Encontrar componentes do LLVM/Clang
find_package(LLVM 19.1.1 REQUIRED CONFIG)
find_package(Clang REQUIRED CONFIG)
find_package(Boost REQUIRED COMPONENTS json)
# Adicionar diretórios de include
include_directories(${LLVM_INCLUDE_DIRS})
include_directories(${CLANG_INCLUDE_DIRS})
# Criar o executável
add_executable(parse_structs parse_structs.cpp)
# Adicionar definições do LLVM
target_compile_definitions(parse_structs PUBLIC ${LLVM_DEFINITIONS})
# Linkar contra libclang
target_link_libraries(parse_structs PUBLIC libclang ${Boost_LIBRARIES})Como executar
./parse_structs test.cpp | jqO | jq é usado para formatar a saída JSON de forma legível. Você não precisa usá-lo se não quiser formatar a saída.
com test.cpp contendo, por exemplo, o seguinte código:
struct myParameters {
double a;
double b;
};Saída de exemplo
{
"structs": [
{
"name": "myParameters",
"fields": [
{
"index": 0,
"name": "a",
"type": "double"
},
{
"index": 1,
"name": "b",
"type": "double"
}
]
}
]
}