4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C/C++環境でJSONファイル読み込み用のパーサーが欲しい

4
Last updated at Posted at 2026-07-22

はじめに

C/C++でJSONファイルの読み込みをしたいとき、下記の強力なライブラリを導入することで簡単に実現可能だが、ダウンロードして、インストールして、OSS使うには利用のお伺い立てて...などと煩わしいと思ってしまうことがある。
単純なJSONファイルを、少し読み込みたいだけなのに...

JSONパーサー(読み込み機能のみ)

SinpleJson.hpp
#pragma once
#include <string>
#include <unordered_map>
#include <variant>
#include <vector>
#include <sstream>
#include <fstream>
#include <stdexcept>
#include <cctype>

namespace SimpleJson {

enum class Type { Null, Bool, Number, String, Array, Object };

struct Value;
using Object = std::unordered_map<std::string, Value>;
using Array = std::vector<Value>;

struct Value {
    Type type = Type::Null;
    std::variant<nullptr_t, bool, double, std::string, Array, Object> data;

    // ヘルパー用取得関数
    bool as_bool() const { return std::get<bool>(data); }
    double as_number() const { return std::get<double>(data); }
    const std::string& as_string() const { return std::get<std::string>(data); }
    const Array& as_array() const { return std::get<Array>(data); }
    const Object& as_object() const { return std::get<Object>(data); }

    const Value& operator[](const std::string& key) const {
        return as_object().at(key);
    }
    const Value& operator[](size_t index) const {
        return as_array().at(index);
    }
};

class Parser {
public:
    static Value parse(const std::string& json_str) {
        size_t index = 0;
        skip_whitespace(json_str, index);
        Value result = parse_value(json_str, index);
        return result;
    }

    static Value parse_file(const std::string& filepath) {
        std::ifstream file(filepath);
        if (!file.is_open()) {
            throw std::runtime_error("ファイルを開けませんでした: " + filepath);
        }
        std::stringstream buffer;
        buffer << file.rdbuf();
        return parse(buffer.str());
    }

private:
    static void skip_whitespace(const std::string& str, size_t& i) {
        while (i < str.size() && (str[i] == ' ' || str[i] == '\t' || str[i] == '\n' || str[i] == '\r')) {
            i++;
        }
    }

    static Value parse_value(const std::string& str, size_t& i) {
        skip_whitespace(str, i);
        if (i >= str.size()) throw std::runtime_error("予期せぬ構文終了です");

        char c = str[i];
        if (c == '{') return parse_object(str, i);
        if (c == '[') return parse_array(str, i);
        if (c == '"') return parse_string(str, i);
        if (c == 't' || c == 'f') return parse_bool(str, i);
        if (c == 'n') return parse_null(str, i);
        if (c == '-' || std::isdigit(c)) return parse_number(str, i);

        throw std::runtime_error(std::string("無効な文字が見つかりました: ") + c);
    }

    static Value parse_string(const std::string& str, size_t& i) {
        i++; // 最初のかっこ " をスキップ
        std::string res;
        while (i < str.size() && str[i] != '"') {
            // エスケープ処理(簡易実装)
            if (str[i] == '\\' && i + 1 < str.size()) {
                i++;
                if (str[i] == 'n') res += '\n';
                else if (str[i] == 't') res += '\t';
                else res += str[i];
            } else {
                res += str[i];
            }
            i++;
        }
        i++; // 終わりのかっこ " をスキップ
        return Value{Type::String, res};
    }

    static Value parse_number(const std::string& str, size_t& i) {
        size_t start = i;
        if (str[i] == '-') i++;
        while (i < str.size() && (std::isdigit(str[i]) || str[i] == '.')) {
            i++;
        }
        double val = std::stod(str.substr(start, i - start));
        return Value{Type::Number, val};
    }

    static Value parse_object(const std::string& str, size_t& i) {
        i++; // '{' をスキップ
        Object obj;
        skip_whitespace(str, i);

        if (str[i] == '}') { i++; return Value{Type::Object, obj}; }

        while (i < str.size()) {
            skip_whitespace(str, i);
            std::string key = parse_string(str, i).as_string();
            skip_whitespace(str, i);
            
            if (str[i] != ':') throw std::runtime_error("':' が見つかりません");
            i++; // ':' をスキップ

            Value val = parse_value(str, i);
            obj[key] = val;

            skip_whitespace(str, i);
            if (str[i] == '}') { i++; break; }
            if (str[i] == ',') { i++; continue; }
            throw std::runtime_error("オブジェクトの区切りが無効です");
        }
        return Value{Type::Object, obj};
    }

    static Value parse_array(const std::string& str, size_t& i) {
        i++; // '[' をスキップ
        Array arr;
        skip_whitespace(str, i);

        if (str[i] == ']') { i++; return Value{Type::Array, arr}; }

        while (i < str.size()) {
            arr.push_back(parse_value(str, i));
            skip_whitespace(str, i);

            if (str[i] == ']') { i++; break; }
            if (str[i] == ',') { i++; continue; }
            throw std::runtime_error("配列の区切りが無効です");
        }
        return Value{Type::Array, arr};
    }

    static Value parse_bool(const std::string& str, size_t& i) {
        if (str.compare(i, 4, "true") == 0) { i += 4; return Value{Type::Bool, true}; }
        if (str.compare(i, 5, "false") == 0) { i += 5; return Value{Type::Bool, false}; }
        throw std::runtime_error("ブール値のパースエラー");
    }

    static Value parse_null(const std::string& str, size_t& i) {
        if (str.compare(i, 4, "null") == 0) { i += 4; return Value{Type::Null, nullptr}; }
        throw std::runtime_error("nullのパースエラー");
    }
};

}

使用方法

main.c
#include <iostream>
#include <string>
#include "simple_json.hpp"

int main() {
    // 1. サンプルJSON文字列
    std::string json_data = R"({
        "name": "ComponentA",
        "version": 1.2,
        "enabled": true,
        "tags": ["c++", "json", "parser"],
        "settings": {
            "timeout": 3000
        }
    })";

    try {
        // --- パース処理 ---
        SimpleJson::Value root = SimpleJson::Parser::parse(json_data);

        std::cout << "=== 1. 正常なアクセス ===\n";
        std::cout << "Name: " << root["name"].as_string() << "\n";
        std::cout << "Version: " << root["version"].as_number() << "\n";
        std::cout << "Tag[0]: " << root["tags"][0].as_string() << "\n";
        std::cout << "Timeout: " << root["settings"]["timeout"].as_number() << " ms\n\n";

        // --- contains() による安全なキーチェック ---
        std::cout << "=== 2. キーの事前チェック ===\n";
        if (root.contains("enabled")) {
            std::cout << "'enabled' キーは存在します: " 
                      << (root["enabled"].as_bool() ? "true" : "false") << "\n";
        }
        if (!root.contains("unknown_key")) {
            std::cout << "'unknown_key' は存在しません(クラッシュせずに判定可能)\n\n";
        }

        // --- 3. 存在しないキーへのアクセス ---
        std::cout << "=== 3. 存在しないキーアクセスのテスト ===\n";
        // 意図的にタイポ("settings" 内の "time_out")
        std::cout << root["settings"]["time_out"].as_number() << "\n";

    } 
    catch (const std::out_of_range& e) {
        // キーが存在しない場合のエラー捕捉
        std::cerr << "[アクセス例外] " << e.what() << "\n\n";
    } 
    catch (const std::exception& e) {
        std::cerr << "[汎用例外] " << e.what() << "\n\n";
    }

    // --- 4. 構文エラーのテスト(行・列の特定) ---
    std::cout << "=== 4. JSON構文エラーのテスト ===\n";
    // 3行目のカンマ忘れ
    std::string bad_json = R"({
        "id": 100
        "name": "test"
    })";

    try {
        SimpleJson::Value bad_root = SimpleJson::Parser::parse(bad_json);
    } 
    catch (const std::exception& e) {
        std::cerr << "[キャッチした構文エラー]\n" << e.what() << "\n";
    }

    return 0;
}
4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?