Conception et Implémentation d'un Analyseur JSON Personnalisé en C++

Architecture et Objectifs du Projet

L'objectif de cette implémentation est de développer une classe C++ autonome, nommée JsonNode, capable de lire, d'analyser et de sérialiser des structures de données JSON. Cette solution ne s'appuie sur aucune bibliothèque tierce et gère nativement l'imbrication complexe des objets et des tbaleaux.

Capacités Principales

  • Prise en charge des types natifs : Chaînes de caractères, entiers, nombres à virgule flottante, booléens, valeurs nulles, objets et tableaux.
  • Manipulation dynamique : Accès et création à la volée de paires clé-valeur via la surcharge des opérateurs, permettant une syntaxe intuitive lors de l'exécution.
  • Analyseur récursif descendant : Traitement caractère par caractère avec une gestion précise des erreurs de syntaxe et de formatage.

Pistes d'Optimisation

  • Interopérabilité XML : Ajouter une méthode de sérialisation pour convertir l'arbre syntaxique JSON en une hiérarchie de nœuds XML.
  • Concurrence : Intégrer des mécanismes de verrouillage (mutex) pour sécuriser les opérations de lecture et d'écriture dans des environnements multi-threads.

Implémentation du Moteur d'Analyse

Le code suivant détaille l'en-tête et la logique d'analyse. La structure a été modernisée en utilisant des conteneurs standards (std::vector et std::map) et une énumération fortement typée pour identifier la nature de chaque nœud.

#pragma once
#include <string>
#include <vector>
#include <map>
#include <iostream>
#include <cmath>
#include <cstring>
#include <cstdlib>

enum class JsonType { Null, Boolean, Integer, Double, String, Array, Object };

const char* SkipWhitespaces(const char* str) {
    while (str && *str && static_cast<unsigned char>(*str) <= 32) str++;
    return str;
}

class JsonNode {
public:
    JsonNode() : type_(JsonType::Null), key_("") { payload.int_val = nullptr; }
    explicit JsonNode(const char* json) : JsonNode() { 
        const char* err = nullptr; 
        Parse(json, &err); 
    }
    explicit JsonNode(const std::string& json) : JsonNode(json.c_str()) {}
    ~JsonNode() { ReleaseMemory(); }

    JsonNode& operator[](const std::string& key);
    JsonNode& operator[](size_t index);
    
    JsonNode& operator=(const std::string& val);
    JsonNode& operator=(int val);
    JsonNode& operator=(double val);

    std::string Serialize(int indent = 0) const;
    void Clear() { ReleaseMemory(); }
    
    size_t GetSize() const;
    JsonType GetType() const { return type_; }

private:
    JsonType type_;
    std::string key_;
    
    union Payload {
        std::string* str_val;
        long long* int_val;
        double* double_val;
        bool* bool_val;
    } payload;

    std::vector<JsonNode*> array_elements_;
    std::map<std::string, JsonNode*> object_properties_;

    const char* Parse(const char* json, const char** err_pos);
    const char* ParseObject(const char* json, const char** err_pos);
    const char* ParseArray(const char* json, const char** err_pos);
    const char* ParseString(const char* json, const char** err_pos);
    const char* ParseNumber(const char* json, const char** err_pos);
    
    void ReleaseMemory();
    std::string GenerateIndent(int level) const;
};

void JsonNode::ReleaseMemory() {
    if (type_ == JsonType::String && payload.str_val) delete payload.str_val;
    else if (type_ == JsonType::Integer && payload.int_val) delete payload.int_val;
    else if (type_ == JsonType::Double && payload.double_val) delete payload.double_val;
    else if (type_ == JsonType::Boolean && payload.bool_val) delete payload.bool_val;
    
    for (auto& child : array_elements_) delete child;
    array_elements_.clear();
    
    for (auto& pair : object_properties_) delete pair.second;
    object_properties_.clear();
    
    type_ = JsonType::Null;
}

const char* JsonNode::Parse(const char* json, const char** err_pos) {
    if (!json) return nullptr;
    json = SkipWhitespaces(json);
    
    if (strncmp(json, "true", 4) == 0) { type_ = JsonType::Boolean; payload.bool_val = new bool(true); return json + 4; }
    if (strncmp(json, "false", 5) == 0) { type_ = JsonType::Boolean; payload.bool_val = new bool(false); return json + 5; }
    if (strncmp(json, "null", 4) == 0) { type_ = JsonType::Null; return json + 4; }
    
    if (*json == '{') return ParseObject(json, err_pos);
    if (*json == '[') return ParseArray(json, err_pos);
    if (*json == '\"') return ParseString(json, err_pos);
    if (*json == '-' || (*json >= '0' && *json <= '9')) return ParseNumber(json, err_pos);
    
    *err_pos = json;
    return nullptr;
}

const char* JsonNode::ParseObject(const char* json, const char** err_pos) {
    type_ = JsonType::Object;
    json = SkipWhitespaces(json + 1);
    if (*json == '}') return json + 1;

    while (true) {
        JsonNode* key_node = new JsonNode();
        json = key_node->ParseString(SkipWhitespaces(json), err_pos);
        if (!json) { delete key_node; return nullptr; }
        
        std::string key = *key_node->payload.str_val;
        delete key_node;

        json = SkipWhitespaces(json);
        if (*json != ':') { *err_pos = json; return nullptr; }
        
        JsonNode* val_node = new JsonNode();
        json = val_node->Parse(SkipWhitespaces(json + 1), err_pos);
        if (!json) { delete val_node; return nullptr; }
        
        val_node->key_ = key;
        object_properties_[key] = val_node;

        json = SkipWhitespaces(json);
        if (*json == '}') return json + 1;
        if (*json != ',') { *err_pos = json; return nullptr; }
        json++;
    }
}

const char* JsonNode::ParseArray(const char* json, const char** err_pos) {
    type_ = JsonType::Array;
    json = SkipWhitespaces(json + 1);
    if (*json == ']') return json + 1;

    while (true) {
        JsonNode* item = new JsonNode();
        json = item->Parse(SkipWhitespaces(json), err_pos);
        if (!json) { delete item; return nullptr; }
        array_elements_.push_back(item);

        json = SkipWhitespaces(json);
        if (*json == ']') return json + 1;
        if (*json != ',') { *err_pos = json; return nullptr; }
        json++;
    }
}

const char* JsonNode::ParseString(const char* json, const char** err_pos) {
    if (*json != '\"') { *err_pos = json; return nullptr; }
    json++;
    std::string res;
    while (*json && *json != '\"') {
        if (*json == '\\') {
            json++;
            switch (*json) {
                case 'b': res += '\b'; break;
                case 'f': res += '\f'; break;
                case 'n': res += '\n'; break;
                case 'r': res += '\r'; break;
                case 't': res += '\t'; break;
                case '\"': res += '\"'; break;
                case '\\': res += '\\'; break;
                default: res += *json; break;
            }
        } else {
            res += *json;
        }
        json++;
    }
    if (*json == '\"') {
        type_ = JsonType::String;
        payload.str_val = new std::string(res);
        return json + 1;
    }
    *err_pos = json;
    return nullptr;
}

const char* JsonNode::ParseNumber(const char* json, const char** err_pos) {
    const char* start = json;
    bool is_float = false;
    if (*json == '-') json++;
    while (*json >= '0' && *json <= '9') json++;
    if (*json == '.') {
        is_float = true;
        json++;
        while (*json >= '0' && *json <= '9') json++;
    }
    if (*json == 'e' || *json == 'E') {
        is_float = true;
        json++;
        if (*json == '+' || *json == '-') json++;
        while (*json >= '0' && *json <= '9') json++;
    }
    
    std::string num_str(start, json);
    if (is_float) {
        type_ = JsonType::Double;
        payload.double_val = new double(std::stod(num_str));
    } else {
        type_ = JsonType::Integer;
        payload.int_val = new long long(std::stoll(num_str));
    }
    return json;
}

JsonNode& JsonNode::operator[](const std::string& key) {
    if (type_ != JsonType::Object) type_ = JsonType::Object;
    if (object_properties_.find(key) == object_properties_.end()) {
        JsonNode* node = new JsonNode();
        node->key_ = key;
        object_properties_[key] = node;
    }
    return *object_properties_[key];
}

JsonNode& JsonNode::operator[](size_t index) {
    if (type_ != JsonType::Array) type_ = JsonType::Array;
    if (index >= array_elements_.size()) array_elements_.resize(index + 1, nullptr);
    if (!array_elements_[index]) array_elements_[index] = new JsonNode();
    return *array_elements_[index];
}

JsonNode& JsonNode::operator=(const std::string& val) {
    ReleaseMemory();
    type_ = JsonType::String;
    payload.str_val = new std::string(val);
    return *this;
}

JsonNode& JsonNode::operator=(int val) {
    ReleaseMemory();
    type_ = JsonType::Integer;
    payload.int_val = new long long(val);
    return *this;
}

JsonNode& JsonNode::operator=(double val) {
    ReleaseMemory();
    type_ = JsonType::Double;
    payload.double_val = new double(val);
    return *this;
}

std::string JsonNode::GenerateIndent(int level) const { return std::string(level * 4, ' '); }

std::string JsonNode::Serialize(int indent) const {
    std::string res;
    std::string current_indent = GenerateIndent(indent);
    std::string next_indent = GenerateIndent(indent + 1);

    auto append_key = [&]() { if (!key_.empty()) res += "\"" + key_ + "\": "; };

    switch (type_) {
        case JsonType::Null: append_key(); res += "null"; break;
        case JsonType::Boolean: append_key(); res += (*payload.bool_val ? "true" : "false"); break;
        case JsonType::Integer: append_key(); res += std::to_string(*payload.int_val); break;
        case JsonType::Double: append_key(); res += std::to_string(*payload.double_val); break;
        case JsonType::String: append_key(); res += "\"" + *payload.str_val + "\""; break;
        case JsonType::Array: {
            append_key(); res += "[\n";
            for (size_t i = 0; i < array_elements_.size(); ++i) {
                res += next_indent + array_elements_[i]->Serialize(indent + 1);
                if (i < array_elements_.size() - 1) res += ",";
                res += "\n";
            }
            res += current_indent + "]"; break;
        }
        case JsonType::Object: {
            append_key(); res += "{\n";
            size_t count = 0;
            for (auto it = object_properties_.begin(); it != object_properties_.end(); ++it) {
                res += next_indent + it->second->Serialize(indent + 1);
                if (++count < object_properties_.size()) res += ",";
                res += "\n";
            }
            res += current_indent + "}"; break;
        }
    }
    return res;
}

size_t JsonNode::GetSize() const {
    if (type_ == JsonType::Array) return array_elements_.size();
    if (type_ == JsonType::Object) return object_properties_.size();
    return 0;
}

Programme d'Intégration

Ce script principal démontre la lecture d'un fichier, l'injection de nouvelles données à l'aide des opérateurs surchargés, puis l'exportation du flux JSON modifié.

#include "JsonNode.hpp"
#include <fstream>
#include <sstream>

std::string ReadFile(const std::string& path) {
    std::ifstream file(path);
    if (!file.is_open()) return "";
    std::stringstream buffer;
    buffer << file.rdbuf();
    return buffer.str();
}

void WriteFile(const std::string& path, const std::string& content) {
    std::ofstream file(path);
    if (file.is_open()) file << content;
}

int main() {
    std::string file_path = "meteo_data.json";
    std::string json_content = ReadFile(file_path);
    
    if (json_content.empty()) {
        std::cerr << "Erreur: Impossible de charger le fichier source." << std::endl;
        return 1;
    }

    JsonNode root(json_content.c_str());
    
    // Ajout dynamique de propriétés
    root["nouvelle_propriete"] = "valeur_generee";
    root["metrics"]["capteur_actif"] = 1;
    
    std::cout << "Arborescence mise a jour:\n" << root.Serialize() << std::endl;
    
    WriteFile("output.json", root.Serialize());
    return 0;
}

Exemple de Données Structurées

Le fichier suivant contient une charge utile JSON typique, incluant des types hétérogènes et des niveaux d'imbrication multiples, idéale pour valider le parcours de l'aanlyseur syntaxique.

{
    "timestamp": "2023-10-25T14:30:00Z",
    "location": {
        "city": "Lyon",
        "coordinates": {
            "lat": 45.7640,
            "lon": 4.8357
        }
    },
    "metrics": {
        "temperature": 18.5,
        "humidity": 62,
        "conditions": [
            "Partiellement nuageux", 
            "Vent leger"
        ]
    },
    "alerts": null,
    "isActive": true
}

Étiquettes: C++ JSON Parser Data Serialization Recursive Descent

Publié le 17 juillet à 12h11