nlohmann/json (バリデーション)
ライブラリ
nlohmann/json (バリデーション)
概要
nlohmann/jsonは、Modern C++のJSONライブラリとして最も人気の高いライブラリです。標準ライブラリのような直感的なAPIを提供し、JSONの読み書きを簡単に行えます。バリデーション機能は、サードパーティライブラリ(json-schema-validatorやvalijson)と組み合わせることで、強力なJSONスキーマ検証を実現できます。C++11以降に対応し、ヘッダーオンリーライブラリとして簡単に導入でき、STLコンテナのような操作感でJSONデータを扱うことができます。
詳細
nlohmann/jsonライブラリは、JSONの解析・生成・操作を行うためのモダンなC++ライブラリです。STLコンテナのような直感的なAPIを提供し、JSONオブジェクトをC++の値として自然に扱えます。バリデーション機能として、基本的な型チェック、構造検証、カスタムバリデーションロジックの実装をサポートしています。外部ライブラリとの連携により、JSON Schema Draft 4/7に準拠したスキーマバリデーションも可能です。パフォーマンスと使いやすさのバランスに優れ、企業レベルのプロダクションでも広く採用されています。
主な特徴
- 直感的API: STLコンテナのような自然な操作感
- 型安全性: 厳密な型チェックとエラーハンドリング
- スキーマ検証: 外部ライブラリとの組み合わせによる高度なバリデーション
- 高性能: 最適化されたJSON解析・生成エンジン
- ヘッダーオンリー: 簡単な導入と配布
- 豊富な機能: カスタムシリアライザー、イテレータ、パッチ操作など
メリット・デメリット
メリット
- C++標準ライブラリのような直感的で使いやすいAPI
- 優れたパフォーマンスと効率的なメモリ使用
- 豊富なドキュメントと活発なコミュニティサポート
- 多くの企業での採用実績と信頼性
- 柔軟なカスタマイズ機能と拡張性
- 包括的なエラーハンドリングとデバッグ支援
デメリット
- コンパイル時間の増加(大きなヘッダーファイル)
- 内蔵のスキーマバリデーション機能は限定的
- 複雑なバリデーションには外部ライブラリが必要
- 巨大なJSONファイルの処理にはメモリ使用量が多い
- 学習コストが存在(特に高度な機能)
参考ページ
書き方の例
インストールと基本セットアップ
# vcpkgを使用してインストール
vcpkg install nlohmann-json
# Conanを使用してインストール
conan install nlohmann_json/3.11.2@
# CMakeでのfind_package使用例
find_package(nlohmann_json REQUIRED)
target_link_libraries(your_target nlohmann_json::nlohmann_json)
# パッケージマネージャーを使用しない場合
wget https://github.com/nlohmann/json/releases/download/v3.11.2/json.hpp
# json.hppを適切なディレクトリに配置
基本的なJSON操作とバリデーション
#include <nlohmann/json.hpp>
#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <stdexcept>
using json = nlohmann::json;
// 基本的なバリデーション機能
class JSONValidator {
public:
// JSON型の確認
static bool isString(const json& j) {
return j.is_string();
}
static bool isNumber(const json& j) {
return j.is_number();
}
static bool isBoolean(const json& j) {
return j.is_boolean();
}
static bool isArray(const json& j) {
return j.is_array();
}
static bool isObject(const json& j) {
return j.is_object();
}
// 必須フィールドの確認
static bool hasRequiredFields(const json& j, const std::vector<std::string>& fields) {
if (!j.is_object()) {
return false;
}
for (const auto& field : fields) {
if (!j.contains(field)) {
return false;
}
}
return true;
}
// 値の範囲チェック
static bool isInRange(const json& j, double min_val, double max_val) {
if (!j.is_number()) {
return false;
}
double value = j.get<double>();
return value >= min_val && value <= max_val;
}
// 文字列長の確認
static bool isStringLengthValid(const json& j, size_t min_len, size_t max_len) {
if (!j.is_string()) {
return false;
}
std::string str = j.get<std::string>();
return str.length() >= min_len && str.length() <= max_len;
}
// 配列サイズの確認
static bool isArraySizeValid(const json& j, size_t min_size, size_t max_size) {
if (!j.is_array()) {
return false;
}
return j.size() >= min_size && j.size() <= max_size;
}
};
int main() {
try {
// JSONの基本的な作成とバリデーション
{
json person = {
{"name", "田中太郎"},
{"age", 30},
{"email", "[email protected]"},
{"skills", {"C++", "Python", "JavaScript"}},
{"active", true}
};
std::cout << "Person JSON:\n" << person.dump(4) << std::endl;
// 基本的なバリデーション
if (JSONValidator::hasRequiredFields(person, {"name", "age", "email"})) {
std::cout << "✓ 必須フィールドが揃っています" << std::endl;
}
if (JSONValidator::isStringLengthValid(person["name"], 2, 50)) {
std::cout << "✓ 名前の長さが有効です" << std::endl;
}
if (JSONValidator::isInRange(person["age"], 0, 120)) {
std::cout << "✓ 年齢が有効な範囲です" << std::endl;
}
if (JSONValidator::isArraySizeValid(person["skills"], 1, 10)) {
std::cout << "✓ スキル配列のサイズが有効です" << std::endl;
}
}
// JSONファイルの読み込みとバリデーション
{
// JSONファイルの読み込み
std::ifstream config_file("config.json");
if (config_file.is_open()) {
json config;
config_file >> config;
// 設定ファイルのバリデーション
std::vector<std::string> required_config = {"app_name", "port", "debug_mode"};
if (JSONValidator::hasRequiredFields(config, required_config)) {
std::cout << "✓ 設定ファイルが有効です" << std::endl;
} else {
std::cout << "✗ 設定ファイルに必須項目がありません" << std::endl;
}
config_file.close();
}
}
// エラーハンドリングの例
{
json invalid_json = {
{"name", 123}, // 文字列を期待するが数値
{"age", "thirty"}, // 数値を期待するが文字列
{"email", ""} // 空の文字列
};
// 型チェック
if (!JSONValidator::isString(invalid_json["name"])) {
std::cout << "✗ 名前は文字列である必要があります" << std::endl;
}
if (!JSONValidator::isNumber(invalid_json["age"])) {
std::cout << "✗ 年齢は数値である必要があります" << std::endl;
}
if (!JSONValidator::isStringLengthValid(invalid_json["email"], 5, 100)) {
std::cout << "✗ メールアドレスの長さが無効です" << std::endl;
}
}
} catch (const json::exception& e) {
std::cerr << "JSONエラー: " << e.what() << std::endl;
} catch (const std::exception& e) {
std::cerr << "エラー: " << e.what() << std::endl;
}
return 0;
}
高度なバリデーション機能とカスタム検証
#include <nlohmann/json.hpp>
#include <iostream>
#include <string>
#include <vector>
#include <regex>
#include <functional>
#include <map>
#include <optional>
using json = nlohmann::json;
// バリデーション結果
struct ValidationResult {
bool is_valid;
std::vector<std::string> errors;
ValidationResult() : is_valid(true) {}
void addError(const std::string& error) {
errors.push_back(error);
is_valid = false;
}
void merge(const ValidationResult& other) {
if (!other.is_valid) {
is_valid = false;
errors.insert(errors.end(), other.errors.begin(), other.errors.end());
}
}
};
// 高度なバリデーター
class AdvancedJSONValidator {
private:
// メールアドレスの検証
static bool isValidEmail(const std::string& email) {
static const std::regex email_regex(
R"(^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$)"
);
return std::regex_match(email, email_regex);
}
// URLの検証
static bool isValidURL(const std::string& url) {
static const std::regex url_regex(
R"(^https?://[^\s/$.?#].[^\s]*$)"
);
return std::regex_match(url, url_regex);
}
// 日付の検証 (ISO 8601形式)
static bool isValidDate(const std::string& date) {
static const std::regex date_regex(
R"(^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$)"
);
return std::regex_match(date, date_regex);
}
public:
// 文字列フィールドの高度な検証
static ValidationResult validateStringField(const json& j, const std::string& field_name,
bool required = true,
size_t min_length = 0,
size_t max_length = std::numeric_limits<size_t>::max(),
const std::string& pattern = "",
const std::string& format = "") {
ValidationResult result;
// フィールドの存在確認
if (!j.contains(field_name)) {
if (required) {
result.addError("必須フィールド '" + field_name + "' が見つかりません");
}
return result;
}
// 型チェック
if (!j[field_name].is_string()) {
result.addError("フィールド '" + field_name + "' は文字列である必要があります");
return result;
}
std::string value = j[field_name].get<std::string>();
// 長さチェック
if (value.length() < min_length || value.length() > max_length) {
result.addError("フィールド '" + field_name + "' の長さは " +
std::to_string(min_length) + " から " +
std::to_string(max_length) + " 文字である必要があります");
}
// パターンチェック
if (!pattern.empty()) {
std::regex regex_pattern(pattern);
if (!std::regex_match(value, regex_pattern)) {
result.addError("フィールド '" + field_name + "' がパターンに一致しません");
}
}
// フォーマットチェック
if (!format.empty()) {
if (format == "email" && !isValidEmail(value)) {
result.addError("フィールド '" + field_name + "' は有効なメールアドレスではありません");
} else if (format == "url" && !isValidURL(value)) {
result.addError("フィールド '" + field_name + "' は有効なURLではありません");
} else if (format == "date" && !isValidDate(value)) {
result.addError("フィールド '" + field_name + "' は有効な日付形式ではありません");
}
}
return result;
}
// 数値フィールドの検証
static ValidationResult validateNumberField(const json& j, const std::string& field_name,
bool required = true,
double min_value = std::numeric_limits<double>::lowest(),
double max_value = std::numeric_limits<double>::max(),
bool integer_only = false) {
ValidationResult result;
// フィールドの存在確認
if (!j.contains(field_name)) {
if (required) {
result.addError("必須フィールド '" + field_name + "' が見つかりません");
}
return result;
}
// 型チェック
if (!j[field_name].is_number()) {
result.addError("フィールド '" + field_name + "' は数値である必要があります");
return result;
}
double value = j[field_name].get<double>();
// 範囲チェック
if (value < min_value || value > max_value) {
result.addError("フィールド '" + field_name + "' の値は " +
std::to_string(min_value) + " から " +
std::to_string(max_value) + " の範囲である必要があります");
}
// 整数チェック
if (integer_only && std::floor(value) != value) {
result.addError("フィールド '" + field_name + "' は整数である必要があります");
}
return result;
}
// 配列フィールドの検証
static ValidationResult validateArrayField(const json& j, const std::string& field_name,
bool required = true,
size_t min_items = 0,
size_t max_items = std::numeric_limits<size_t>::max(),
std::function<ValidationResult(const json&, size_t)> item_validator = nullptr) {
ValidationResult result;
// フィールドの存在確認
if (!j.contains(field_name)) {
if (required) {
result.addError("必須フィールド '" + field_name + "' が見つかりません");
}
return result;
}
// 型チェック
if (!j[field_name].is_array()) {
result.addError("フィールド '" + field_name + "' は配列である必要があります");
return result;
}
const json& array = j[field_name];
// サイズチェック
if (array.size() < min_items || array.size() > max_items) {
result.addError("フィールド '" + field_name + "' のアイテム数は " +
std::to_string(min_items) + " から " +
std::to_string(max_items) + " である必要があります");
}
// 各アイテムの検証
if (item_validator) {
for (size_t i = 0; i < array.size(); ++i) {
ValidationResult item_result = item_validator(array[i], i);
if (!item_result.is_valid) {
for (const auto& error : item_result.errors) {
result.addError("配列 '" + field_name + "' の要素[" +
std::to_string(i) + "]: " + error);
}
}
}
}
return result;
}
// オブジェクトフィールドの検証
static ValidationResult validateObjectField(const json& j, const std::string& field_name,
bool required = true,
std::function<ValidationResult(const json&)> object_validator = nullptr) {
ValidationResult result;
// フィールドの存在確認
if (!j.contains(field_name)) {
if (required) {
result.addError("必須フィールド '" + field_name + "' が見つかりません");
}
return result;
}
// 型チェック
if (!j[field_name].is_object()) {
result.addError("フィールド '" + field_name + "' はオブジェクトである必要があります");
return result;
}
// オブジェクトの検証
if (object_validator) {
ValidationResult obj_result = object_validator(j[field_name]);
if (!obj_result.is_valid) {
for (const auto& error : obj_result.errors) {
result.addError("オブジェクト '" + field_name + "': " + error);
}
}
}
return result;
}
};
// 具体的なスキーマバリデーション例
class UserProfileValidator {
public:
static ValidationResult validateUserProfile(const json& user_profile) {
ValidationResult result;
// 基本情報の検証
auto name_result = AdvancedJSONValidator::validateStringField(
user_profile, "name", true, 2, 50
);
result.merge(name_result);
auto email_result = AdvancedJSONValidator::validateStringField(
user_profile, "email", true, 5, 100, "", "email"
);
result.merge(email_result);
auto age_result = AdvancedJSONValidator::validateNumberField(
user_profile, "age", true, 0, 120, true
);
result.merge(age_result);
// スキルの検証
auto skills_result = AdvancedJSONValidator::validateArrayField(
user_profile, "skills", false, 0, 20,
[](const json& item, size_t index) -> ValidationResult {
ValidationResult item_result;
if (!item.is_string()) {
item_result.addError("スキルは文字列である必要があります");
} else {
std::string skill = item.get<std::string>();
if (skill.empty() || skill.length() > 30) {
item_result.addError("スキルは1文字以上30文字以下である必要があります");
}
}
return item_result;
}
);
result.merge(skills_result);
// 連絡先情報の検証
auto contact_result = AdvancedJSONValidator::validateObjectField(
user_profile, "contact", false,
[](const json& contact) -> ValidationResult {
ValidationResult contact_result;
// 電話番号の検証
if (contact.contains("phone")) {
auto phone_result = AdvancedJSONValidator::validateStringField(
contact, "phone", false, 10, 15, R"(^\+?[1-9]\d{1,14}$)"
);
contact_result.merge(phone_result);
}
// 住所の検証
if (contact.contains("address")) {
auto address_result = AdvancedJSONValidator::validateStringField(
contact, "address", false, 10, 200
);
contact_result.merge(address_result);
}
// ウェブサイトの検証
if (contact.contains("website")) {
auto website_result = AdvancedJSONValidator::validateStringField(
contact, "website", false, 0, 200, "", "url"
);
contact_result.merge(website_result);
}
return contact_result;
}
);
result.merge(contact_result);
return result;
}
};
int main() {
try {
// 有効なユーザープロファイルのテスト
{
json valid_profile = {
{"name", "田中太郎"},
{"email", "[email protected]"},
{"age", 30},
{"skills", {"C++", "Python", "JavaScript", "SQL"}},
{"contact", {
{"phone", "+819012345678"},
{"address", "東京都新宿区新宿1-1-1"},
{"website", "https://example.com"}
}}
};
std::cout << "有効なユーザープロファイル:\n" << valid_profile.dump(4) << std::endl;
ValidationResult result = UserProfileValidator::validateUserProfile(valid_profile);
if (result.is_valid) {
std::cout << "✓ ユーザープロファイルは有効です" << std::endl;
} else {
std::cout << "✗ バリデーションエラー:" << std::endl;
for (const auto& error : result.errors) {
std::cout << " - " << error << std::endl;
}
}
}
std::cout << "\n" << std::string(50, '-') << "\n\n";
// 無効なユーザープロファイルのテスト
{
json invalid_profile = {
{"name", "A"}, // 短すぎる名前
{"email", "invalid-email"}, // 無効なメール
{"age", 150}, // 範囲外の年齢
{"skills", {"", "very long skill name that exceeds the maximum length limit"}}, // 無効なスキル
{"contact", {
{"phone", "123"}, // 無効な電話番号
{"address", "短い住所"}, // 短すぎる住所
{"website", "not-a-url"} // 無効なURL
}}
};
std::cout << "無効なユーザープロファイル:\n" << invalid_profile.dump(4) << std::endl;
ValidationResult result = UserProfileValidator::validateUserProfile(invalid_profile);
if (result.is_valid) {
std::cout << "✓ ユーザープロファイルは有効です" << std::endl;
} else {
std::cout << "✗ バリデーションエラー:" << std::endl;
for (const auto& error : result.errors) {
std::cout << " - " << error << std::endl;
}
}
}
} catch (const json::exception& e) {
std::cerr << "JSONエラー: " << e.what() << std::endl;
} catch (const std::exception& e) {
std::cerr << "エラー: " << e.what() << std::endl;
}
return 0;
}
JSON Schema統合とスキーマ駆動バリデーション
// 注意: このコードはjson-schema-validatorライブラリとの統合例です
// 実際の使用には追加のライブラリが必要です
#include <nlohmann/json.hpp>
#include <iostream>
#include <string>
#include <fstream>
#include <map>
#include <vector>
using json = nlohmann::json;
// スキーマ定義クラス
class JSONSchemaManager {
private:
std::map<std::string, json> schemas;
public:
// スキーマの定義と登録
void defineSchema(const std::string& schema_name, const json& schema) {
schemas[schema_name] = schema;
}
// ファイルからスキーマを読み込み
bool loadSchemaFromFile(const std::string& schema_name, const std::string& file_path) {
try {
std::ifstream file(file_path);
if (!file.is_open()) {
return false;
}
json schema;
file >> schema;
schemas[schema_name] = schema;
return true;
} catch (const std::exception&) {
return false;
}
}
// スキーマの取得
std::optional<json> getSchema(const std::string& schema_name) {
auto it = schemas.find(schema_name);
if (it != schemas.end()) {
return it->second;
}
return std::nullopt;
}
// 基本的なスキーマ検証(簡易版)
ValidationResult validateAgainstSchema(const json& data, const std::string& schema_name) {
ValidationResult result;
auto schema_opt = getSchema(schema_name);
if (!schema_opt) {
result.addError("スキーマ '" + schema_name + "' が見つかりません");
return result;
}
json schema = *schema_opt;
// 型チェック
if (schema.contains("type")) {
std::string expected_type = schema["type"];
if (!checkType(data, expected_type)) {
result.addError("型が一致しません。期待される型: " + expected_type);
}
}
// プロパティチェック
if (schema.contains("properties") && data.is_object()) {
auto properties = schema["properties"];
for (auto it = properties.begin(); it != properties.end(); ++it) {
std::string prop_name = it.key();
json prop_schema = it.value();
if (data.contains(prop_name)) {
auto prop_result = validateProperty(data[prop_name], prop_schema, prop_name);
result.merge(prop_result);
}
}
}
// 必須プロパティチェック
if (schema.contains("required") && data.is_object()) {
auto required = schema["required"];
for (const auto& req_prop : required) {
std::string prop_name = req_prop;
if (!data.contains(prop_name)) {
result.addError("必須プロパティ '" + prop_name + "' が見つかりません");
}
}
}
return result;
}
private:
bool checkType(const json& data, const std::string& expected_type) {
if (expected_type == "object") return data.is_object();
if (expected_type == "array") return data.is_array();
if (expected_type == "string") return data.is_string();
if (expected_type == "number") return data.is_number();
if (expected_type == "integer") return data.is_number_integer();
if (expected_type == "boolean") return data.is_boolean();
if (expected_type == "null") return data.is_null();
return false;
}
ValidationResult validateProperty(const json& value, const json& prop_schema, const std::string& prop_name) {
ValidationResult result;
// 型チェック
if (prop_schema.contains("type")) {
std::string expected_type = prop_schema["type"];
if (!checkType(value, expected_type)) {
result.addError("プロパティ '" + prop_name + "' の型が一致しません");
}
}
// 文字列の長さチェック
if (prop_schema.contains("minLength") && value.is_string()) {
int min_length = prop_schema["minLength"];
if (value.get<std::string>().length() < static_cast<size_t>(min_length)) {
result.addError("プロパティ '" + prop_name + "' の長さが最小値を下回っています");
}
}
if (prop_schema.contains("maxLength") && value.is_string()) {
int max_length = prop_schema["maxLength"];
if (value.get<std::string>().length() > static_cast<size_t>(max_length)) {
result.addError("プロパティ '" + prop_name + "' の長さが最大値を上回っています");
}
}
// 数値の範囲チェック
if (prop_schema.contains("minimum") && value.is_number()) {
double minimum = prop_schema["minimum"];
if (value.get<double>() < minimum) {
result.addError("プロパティ '" + prop_name + "' の値が最小値を下回っています");
}
}
if (prop_schema.contains("maximum") && value.is_number()) {
double maximum = prop_schema["maximum"];
if (value.get<double>() > maximum) {
result.addError("プロパティ '" + prop_name + "' の値が最大値を上回っています");
}
}
// 配列の長さチェック
if (prop_schema.contains("minItems") && value.is_array()) {
int min_items = prop_schema["minItems"];
if (value.size() < static_cast<size_t>(min_items)) {
result.addError("プロパティ '" + prop_name + "' の配列長が最小値を下回っています");
}
}
if (prop_schema.contains("maxItems") && value.is_array()) {
int max_items = prop_schema["maxItems"];
if (value.size() > static_cast<size_t>(max_items)) {
result.addError("プロパティ '" + prop_name + "' の配列長が最大値を上回っています");
}
}
return result;
}
};
// 使用例
int main() {
try {
JSONSchemaManager schema_manager;
// ユーザープロファイルのスキーマ定義
json user_schema = {
{"type", "object"},
{"properties", {
{"name", {
{"type", "string"},
{"minLength", 2},
{"maxLength", 50}
}},
{"email", {
{"type", "string"},
{"format", "email"}
}},
{"age", {
{"type", "integer"},
{"minimum", 0},
{"maximum", 120}
}},
{"skills", {
{"type", "array"},
{"items", {
{"type", "string"},
{"minLength", 1},
{"maxLength", 30}
}},
{"minItems", 0},
{"maxItems", 20}
}},
{"contact", {
{"type", "object"},
{"properties", {
{"phone", {
{"type", "string"},
{"pattern", "^\\+?[1-9]\\d{1,14}$"}
}},
{"address", {
{"type", "string"},
{"minLength", 10},
{"maxLength", 200}
}},
{"website", {
{"type", "string"},
{"format", "uri"}
}}
}}
}}
}},
{"required", {"name", "email", "age"}}
};
schema_manager.defineSchema("user_profile", user_schema);
// 有効なデータの検証
{
json valid_data = {
{"name", "田中太郎"},
{"email", "[email protected]"},
{"age", 30},
{"skills", {"C++", "Python", "JavaScript"}},
{"contact", {
{"phone", "+819012345678"},
{"address", "東京都新宿区新宿1-1-1"},
{"website", "https://example.com"}
}}
};
std::cout << "有効なデータの検証:\n";
ValidationResult result = schema_manager.validateAgainstSchema(valid_data, "user_profile");
if (result.is_valid) {
std::cout << "✓ データは有効です" << std::endl;
} else {
std::cout << "✗ バリデーションエラー:" << std::endl;
for (const auto& error : result.errors) {
std::cout << " - " << error << std::endl;
}
}
}
std::cout << "\n" << std::string(50, '-') << "\n\n";
// 無効なデータの検証
{
json invalid_data = {
{"name", "A"}, // 短すぎる
{"email", "invalid-email"}, // 無効なメール
{"age", 150}, // 範囲外
{"skills", {"", "very long skill name that exceeds maximum length"}}, // 無効なスキル
{"contact", {
{"phone", "123"}, // 無効な電話番号
{"address", "短い"}, // 短すぎる
{"website", "not-a-url"} // 無効なURL
}}
};
std::cout << "無効なデータの検証:\n";
ValidationResult result = schema_manager.validateAgainstSchema(invalid_data, "user_profile");
if (result.is_valid) {
std::cout << "✓ データは有効です" << std::endl;
} else {
std::cout << "✗ バリデーションエラー:" << std::endl;
for (const auto& error : result.errors) {
std::cout << " - " << error << std::endl;
}
}
}
// スキーマファイルの保存
{
std::ofstream schema_file("user_profile_schema.json");
schema_file << user_schema.dump(4);
schema_file.close();
std::cout << "\nスキーマファイルを user_profile_schema.json に保存しました" << std::endl;
}
} catch (const json::exception& e) {
std::cerr << "JSONエラー: " << e.what() << std::endl;
} catch (const std::exception& e) {
std::cerr << "エラー: " << e.what() << std::endl;
}
return 0;
}