はじめに
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;
}