JSON Schema Validator (cpp, written by Codex)
envgap__codex__cpp-t1-2
Written by a coding agent; not on GitHubWritten 2026-03-02
01 / FAILURE SIGNATURE
As the study recorded it
No identifying execution failure has been captured.
Not a benchmark task.
- The project already builds and runs before the fix, so there is nothing to repair.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
CMakeLists.txt- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
codex/cpp-t1 #2 · read the task the agent was given
Codex wrote this cpp project from the task below. It installed and ran on a clean Ubuntu 22.04 machine as written. Task given to the agent: TASK: JSON Schema Validator Write a program that validates JSON documents against user-defined schema rules. It should support type checking, required fields, nested object validation, array constraints, and pattern matching. It should collect all violations — not stop at the first — and produce a detailed validation report. FUNCTIONAL REQUIREMENTS: - Accept two command-line arguments: a JSON data file and a JSON schema file - Support validation rules: type checking (string, number, boolean, array, object, null), required fields, min/max for numbers, minLength/maxLength for strings, enum values, regex pattern matching - Validate nested objects and arrays recursively to any depth - Validate array items (all items must conform to a specified type or sub-schema) - Collect ALL validation errors across the entire document, reporting the full JSON path for each violation (e.g., $.users[2].email) - Print the validation report to the console with one line per violation showing path, expected rule, and actual value - Save the validation report as validation_report.json with structured error objects - If no input files are given, generate a sample schema and a sample data file with intentional violations across different rule types, then validate - Handle malformed JSON input gracefully with clear error messages distinguishing parse errors from validation errors Create a complete C++ project for a clean Ubuntu 22.04 machine with only G++ 12+ and CMake 3.22+ installed. Include: - Source code - CMakeLists.txt with all dependencies (direct and transitive) pinned to exact versions - README.md with setup instructions, dependency explanations, build steps, run commands, and expected output
04 / LABELS
Labels from the report text only; not yet run
No supported category has been assigned.
Label rules and the text that matched
[]
05 / FILES
The project as the agent wrote it
3 files, exactly as written, before any repair.
CMakeLists.txt
cmake_minimum_required(VERSION 3.22) project(json_schema_validator VERSION 1.0.0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) add_executable(json_schema_validator src/main.cpp)
README.md
# JSON Schema Validator (C++) CLI validator that checks JSON files against a user-defined schema, reports all violations with full JSON paths, and writes `validation_report.json`. ## Features - Accepts `data.json` and `schema.json` command-line arguments. - Supports: - `type` (`string`, `number`, `boolean`, `array`, `object`, `null`) - `required` - `min` / `max` (`minimum` / `maximum` also supported) - `minLength` / `maxLength` - `enum` - `pattern` (regex) - `minItems` / `maxItems` - Recursive validation for nested objects/arrays. - Validates all array items against `items` schema. - Collects all violations with paths like `$.users[2].email`. - Prints one line per violation with path, expected rule, and actual value. - Saves structured report to `validation_report.json`. - If no files are given, generates `sample_data.json` and `sample_schema.json` with intentional violations, then validates. - Distinguishes parse errors from validation errors using an internal JSON parser. ## Requirements - Ubuntu 22.04 - G++ 12+ - CMake 3.22+ ## Dependencies (Pinned) No external dependencies are used. - Direct dependencies: none - Transitive dependencies: none `CMakeLists.txt` pins minimum CMake version and C++ standard. ## Build and Run ```bash cd TMLR/code_generation/codex_generated/p_02/cpp cmake -S . -B build -DCMAKE_BUILD_TYPE=Release cmake --build build --config Release ``` Validate custom files: ```bash ./build/json_schema_validator ./data.json ./schema.json ``` Generate sample files and validate: ```bash ./build/json_schema_validator ``` ## Output - Console: one line per violation. - File: `validation_report.json`. - If run without arguments: - `sample_data.json` - `sample_schema.json`
src/main.cpp
#include <algorithm>
#include <cctype>
#include <cmath>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <map>
#include <optional>
#include <regex>
#include <sstream>
#include <stdexcept>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include <chrono>
#include <ctime>
namespace {
struct JsonValue {
using Array = std::vector<JsonValue>;
using Object = std::map<std::string, JsonValue>;
std::variant<std::nullptr_t, bool, double, std::string, Array, Object> value;
JsonValue() : value(nullptr) {}
JsonValue(std::nullptr_t) : value(nullptr) {}
JsonValue(bool v) : value(v) {}
JsonValue(double v) : value(v) {}
JsonValue(int v) : value(static_cast<double>(v)) {}
JsonValue(const char* v) : value(std::string(v)) {}
JsonValue(std::string v) : value(std::move(v)) {}
JsonValue(Array v) : value(std::move(v)) {}
JsonValue(Object v) : value(std::move(v)) {}
bool isNull() const { return std::holds_alternative<std::nullptr_t>(value); }
bool isBool() const { return std::holds_alternative<bool>(value); }
bool isNumber() const { return std::holds_alternative<double>(value); }
bool isString() const { return std::holds_alternative<std::string>(value); }
bool isArray() const { return std::holds_alternative<Array>(value); }
bool isObject() const { return std::holds_alternative<Object>(value); }
bool asBool() const { return std::get<bool>(value); }
double asNumber() const { return std::get<double>(value); }
const std::string& asString() const { return std::get<std::string>(value); }
const Array& asArray() const { return std::get<Array>(value); }
const Object& asObject() const { return std::get<Object>(value); }
Array& asArray() { return std::get<Array>(value); }
Object& asObject() { return std::get<Object>(value); }
};
struct ValidationError {
std::string path;
std::string rule;
std::string expected;
std::string actual;
};
struct ParseError : std::runtime_error {
int line;
int column;
ParseError(const std::string& message, int lineIn, int columnIn)
: std::runtime_error(message), line(lineIn), column(columnIn) {}
};
class JsonParser {
public:
explicit JsonParser(std::string text) : text_(std::move(text)), index_(0), line_(1), column_(1) {}
JsonValue parse() {
skipWhitespace();
JsonValue value = parseValue();
skipWhitespace();
if (!isEnd()) {
error("Unexpected trailing content");
}
return value;
}
private:
std::string text_;
std::size_t index_;
int line_;
int column_;
JsonValue parseValue() {
if (isEnd()) {
error("Unexpected end of input");
}
char ch = current();
if (ch == '{') return parseObject();
if (ch == '[') return parseArray();
if (ch == '"') return JsonValue(parseString());
if (ch == 't') return parseLiteral("true", JsonValue(true));
if (ch == 'f') return parseLiteral("false", JsonValue(false));
if (ch == 'n') return parseLiteral("null", JsonValue(nullptr));
if (ch == '-' || std::isdigit(static_cast<unsigned char>(ch))) return JsonValue(parseNumber());
error(std::string("Unexpected character '") + ch + "'");
return JsonValue();
}
JsonValue parseObject() {
expect('{');
skipWhitespace();
JsonValue::Object object;
if (tryConsume('}')) {
return JsonValue(std::move(object));
}
while (true) {
skipWhitespace();
if (isEnd() || current() != '"') {
error("Object keys must be strings");
}
std::string key = parseString();
skipWhitespace();
expect(':');
skipWhitespace();
object[key] = parseValue();
skipWhitespace();
if (tryConsume('}')) {
break;
}
expect(',');
}
return JsonValue(std::move(object));
}
JsonValue parseArray() {
expect('[');
skipWhitespace();
JsonValue::Array array;
if (tryConsume(']')) {
return JsonValue(std::move(array));
}
while (true) {
skipWhitespace();
array.push_back(parseValue());
skipWhitespace();
if (tryConsume(']')) {
break;
}
expect(',');
}
return JsonValue(std::move(array));
}
std::string parseString() {
expect('"');
std::string out;
while (!isEnd()) {
char ch = current();
if (ch == '"') {
advance();
return out;
}
if (ch == '\\') {
advance();
if (isEnd()) {
error("Unterminated escape sequence");
}
char esc = current();
switch (esc) {
case '"': out.push_back('"'); advance(); break;
case '\\': out.push_back('\\'); advance(); break;
case '/': out.push_back('/'); advance(); break;
case 'b': out.push_back('\b'); advance(); break;
case 'f': out.push_back('\f'); advance(); break;
case 'n': out.push_back('\n'); advance(); break;
case 'r': out.push_back('\r'); advance(); break;
case 't': out.push_back('\t'); advance(); break;
case 'u':
advance();
appendUnicodeEscape(out);
break;
default:
error(std::string("Invalid escape character '") + esc + "'");
}
} else {
if (static_cast<unsigned char>(ch) < 0x20) {
error("Unescaped control character in string");
}
out.push_back(ch);
advance();
}
}
error("Unterminated string");
return "";
}
void appendUnicodeEscape(std::string& out) {
if (index_ + 4 > text_.size()) {
error("Incomplete unicode escape");
}
int codepoint = 0;
for (int i = 0; i < 4; ++i) {
char ch = text_[index_ + i];
codepoint <<= 4;
if (ch >= '0' && ch <= '9') {
codepoint += ch - '0';
} else if (ch >= 'a' && ch <= 'f') {
codepoint += 10 + (ch - 'a');
} else if (ch >= 'A' && ch <= 'F') {
codepoint += 10 + (ch - 'A');
} else {
error("Invalid unicode escape");
}
}
advance(4);
if (codepoint <= 0x7F) {
out.push_back(static_cast<char>(codepoint));
} else if (codepoint <= 0x7FF) {
out.push_back(static_cast<char>(0xC0 | ((codepoint >> 6) & 0x1F)));
out.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
} else {
out.push_back(static_cast<char>(0xE0 | ((codepoint >> 12) & 0x0F)));
out.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)));
out.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
}
}
JsonValue parseLiteral(const std::string& literal, JsonValue value) {
for (char expected : literal) {
if (isEnd() || current() != expected) {
error("Unexpected token");
}
advance();
}
return value;
}
double parseNumber() {
std::size_t start = index_;
if (current() == '-') {
advance();
}
if (isEnd()) {
error("Invalid number");
}
if (current() == '0') {
advance();
} else if (std::isdigit(static_cast<unsigned char>(current()))) {
while (!isEnd() && std::isdigit(static_cast<unsigned char>(current()))) {
advance();
}
} else {
error("Invalid number");
}
if (!isEnd() && current() == '.') {
advance();
if (isEnd() || !std::isdigit(static_cast<unsigned char>(current()))) {
error("Invalid decimal number");
}
while (!isEnd() && std::isdigit(static_cast<unsigned char>(current()))) {
advance();
}
}
if (!isEnd() && (current() == 'e' || current() == 'E')) {
advance();
if (!isEnd() && (current() == '+' || current() == '-')) {
advance();
}
if (isEnd() || !std::isdigit(static_cast<unsigned char>(current()))) {
error("Invalid exponent");
}
while (!isEnd() && std::isdigit(static_cast<unsigned char>(current()))) {
advance();
}
}
std::string raw = text_.substr(start, index_ - start);
try {
return std::stod(raw);
} catch (const std::exception&) {
error("Invalid number format");
return 0.0;
}
}
void skipWhitespace() {
while (!isEnd()) {
char ch = current();
if (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t') {
advance();
} else {
break;
}
}
}
bool tryConsume(char expected) {
if (!isEnd() && current() == expected) {
advance();
return true;
}
return false;
}
void expect(char expected) {
if (isEnd() || current() != expected) {
error(std::string("Expected '") + expected + "'");
}
advance();
}
char current() const {
return text_[index_];
}
bool isEnd() const {
return index_ >= text_.size();
}
void advance() {
advance(1);
}
void advance(std::size_t count) {
for (std::size_t i = 0; i < count; ++i) {
if (isEnd()) return;
char ch = text_[index_++];
if (ch == '\n') {
line_ += 1;
column_ = 1;
} else {
column_ += 1;
}
}
}
[[noreturn]] void error(const std::string& message) const {
throw ParseError(message, line_, column_);
}
};
std::string escapeJson(const std::string& input) {
std::ostringstream out;
for (char ch : input) {
switch (ch) {
case '"': out << "\\\""; break;
case '\\': out << "\\\\"; break;
case '\b': out << "\\b"; break;
case '\f': out << "\\f"; break;
case '\n': out << "\\n"; break;
case '\r': out << "\\r"; break;
case '\t': out << "\\t"; break;
default:
if (static_cast<unsigned char>(ch) < 0x20) {
out << "\\u" << std::hex << std::setw(4) << std::setfill('0')
<< static_cast<int>(static_cast<unsigned char>(ch)) << std::dec;
} else {
out << ch;
}
}
}
return out.str();
}
void writeJson(const JsonValue& value, std::ostringstream& out, bool pretty, int indent);
void indent(std::ostringstream& out, int level) {
for (int i = 0; i < level; ++i) {
out << " ";
}
}
void writeJson(const JsonValue& value, std::ostringstream& out, bool pretty, int indentLevel) {
if (value.isNull()) {
out << "null";
return;
}
if (value.isBool()) {
out << (value.asBool() ? "true" : "false");
return;
}
if (value.isNumber()) {
double number = value.asNumber();
if (!std::isfinite(number)) {
out << "null";
} else if (std::rint(number) == number) {
out << static_cast<long long>(number);
} else {
out << std::setprecision(15) << number;
}
return;
}
if (value.isString()) {
out << '"' << escapeJson(value.asString()) << '"';
return;
}
if (value.isArray()) {
const auto& arr = value.asArray();
out << '[';
if (!arr.empty()) {
if (pretty) out << '\n';
for (std::size_t i = 0; i < arr.size(); ++i) {
if (pretty) indent(out, indentLevel + 1);
writeJson(arr[i], out, pretty, indentLevel + 1);
if (i + 1 < arr.size()) out << ',';
if (pretty) out << '\n';
}
if (pretty) indent(out, indentLevel);
}
out << ']';
return;
}
const auto& obj = value.asObject();
out << '{';
if (!obj.empty()) {
if (pretty) out << '\n';
std::size_t index = 0;
for (const auto& entry : obj) {
if (pretty) indent(out, indentLevel + 1);
out << '"' << escapeJson(entry.first) << '"';
out << (pretty ? ": " : ":");
writeJson(entry.second, out, pretty, indentLevel + 1);
if (index + 1 < obj.size()) out << ',';
if (pretty) out << '\n';
index += 1;
}
if (pretty) indent(out, indentLevel);
}
out << '}';
}
std::string toJson(const JsonValue& value) {
std::ostringstream out;
writeJson(value, out, false, 0);
return out.str();
}
std::string toPrettyJson(const JsonValue& value) {
std::ostringstream out;
writeJson(value, out, true, 0);
return out.str();
}
std::string jsonTypeOf(const JsonValue& value) {
if (value.isNull()) return "null";
if (value.isBool()) return "boolean";
if (value.isNumber()) return "number";
if (value.isString()) return "string";
if (value.isArray()) return "array";
return "object";
}
bool deepEqual(const JsonValue& left, const JsonValue& right) {
if (left.value.index() != right.value.index()) return false;
if (left.isNull()) return true;
if (left.isBool()) return left.asBool() == right.asBool();
if (left.isNumber()) return left.asNumber() == right.asNumber();
if (left.isString()) return left.asString() == right.asString();
if (left.isArray()) {
const auto& a = left.asArray();
const auto& b = right.asArray();
if (a.size() != b.size()) return false;
for (std::size_t i = 0; i < a.size(); ++i) {
if (!deepEqual(a[i], b[i])) return false;
}
return true;
}
const auto& a = left.asObject();
const auto& b = right.asObject();
if (a.size() != b.size()) return false;
for (const auto& entry : a) {
auto found = b.find(entry.first);
if (found == b.end()) return false;
if (!deepEqual(entry.second, found->second)) return false;
}
return true;
}
std::string valuePreview(const JsonValue& value) {
if (value.isString() || value.isNumber() || value.isBool() || value.isNull()) {
return toJson(value);
}
if (value.isArray()) {
return "<array length=" + std::to_string(value.asArray().size()) + ">";
}
return "<object>";
}
std::optional<double> getNumberField(const JsonValue::Object& schema, const std::string& key) {
auto it = schema.find(key);
if (it == schema.end() || !it->second.isNumber()) return std::nullopt;
return it->second.asNumber();
}
void addError(std::vector<ValidationError>& errors, const std::string& path, const std::string& rule,
const std::string& expected, const std::string& actual) {
errors.push_back({path, rule, expected, actual});
}
bool matchesType(const JsonValue& value, const std::string& expectedType) {
return jsonTypeOf(value) == expectedType;
}
void validate(const JsonValue& data, const JsonValue& schemaValue, const std::string& path,
std::vector<ValidationError>& errors) {
if (!schemaValue.isObject()) {
return;
}
const auto& schema = schemaValue.asObject();
auto typeIt = schema.find("type");
if (typeIt != schema.end()) {
std::vector<std::string> allowedTypes;
if (typeIt->second.isString()) {
allowedTypes.push_back(typeIt->second.asString());
} else if (typeIt->second.isArray()) {
for (const auto& item : typeIt->second.asArray()) {
if (item.isString()) {
allowedTypes.push_back(item.asString());
}
}
}
if (!allowedTypes.empty()) {
bool match = false;
for (const auto& expected : allowedTypes) {
if (matchesType(data, expected)) {
match = true;
break;
}
}
if (!match) {
std::ostringstream expected;
for (std::size_t i = 0; i < allowedTypes.size(); ++i) {
if (i > 0) expected << " | ";
expected << allowedTypes[i];
}
addError(errors, path, "type", expected.str(), valuePreview(data));
return;
}
}
}
auto enumIt = schema.find("enum");
if (enumIt != schema.end() && enumIt->second.isArray()) {
bool found = false;
for (const auto& allowed : enumIt->second.asArray()) {
if (deepEqual(allowed, data)) {
found = true;
break;
}
}
if (!found) {
addError(errors, path, "enum", toJson(enumIt->second), valuePreview(data));
}
}
std::string type = jsonTypeOf(data);
if (type == "number") {
double number = data.asNumber();
auto min = getNumberField(schema, "min");
auto max = getNumberField(schema, "max");
auto minimum = getNumberField(schema, "minimum");
auto maximum = getNumberField(schema, "maximum");
if (min.has_value() && number < *min) {
addError(errors, path, "min", ">= " + toJson(JsonValue(*min)), valuePreview(data));
}
if (max.has_value() && number > *max) {
addError(errors, path, "max", "<= " + toJson(JsonValue(*max)), valuePreview(data));
}
if (minimum.has_value() && number < *minimum) {
addError(errors, path, "minimum", ">= " + toJson(JsonValue(*minimum)), valuePreview(data));
}
if (maximum.has_value() && number > *maximum) {
addError(errors, path, "maximum", "<= " + toJson(JsonValue(*maximum)), valuePreview(data));
}
}
if (type == "string") {
const std::string& text = data.asString();
auto minLength = getNumberField(schema, "minLength");
auto maxLength = getNumberField(schema, "maxLength");
if (minLength.has_value() && text.size() < static_cast<std::size_t>(*minLength)) {
addError(errors, path, "minLength", "length >= " + std::to_string(static_cast<int>(*minLength)), valuePreview(data));
}
if (maxLength.has_value() && text.size() > static_cast<std::size_t>(*maxLength)) {
addError(errors, path, "maxLength", "length <= " + std::to_string(static_cast<int>(*maxLength)), valuePreview(data));
}
auto patternIt = schema.find("pattern");
if (patternIt != schema.end() && patternIt->second.isString()) {
const std::string& pattern = patternIt->second.asString();
try {
if (!std::regex_search(text, std::regex(pattern))) {
addError(errors, path, "pattern", "match /" + pattern + "/", valuePreview(data));
}
} catch (const std::regex_error& ex) {
addError(errors, path, "pattern", "invalid regex \"" + pattern + "\": " + ex.what(), valuePreview(data));
}
}
}
if (type == "object") {
const auto& objectData = data.asObject();
auto requiredIt = schema.find("required");
if (requiredIt != schema.end() && requiredIt->second.isArray()) {
for (const auto& requiredKey : requiredIt->second.asArray()) {
if (!requiredKey.isString()) continue;
if (objectData.find(requiredKey.asString()) == objectData.end()) {
addError(errors, path + "." + requiredKey.asString(), "required", "field must exist", "undefined");
}
}
}
auto propertiesIt = schema.find("properties");
if (propertiesIt != schema.end() && propertiesIt->second.isObject()) {
const auto& properties = propertiesIt->second.asObject();
for (const auto& property : properties) {
auto found = objectData.find(property.first);
if (found != objectData.end()) {
validate(found->second, property.second, path + "." + property.first, errors);
}
}
}
}
if (type == "array") {
const auto& arrayData = data.asArray();
auto minItems = getNumberField(schema, "minItems");
auto maxItems = getNumberField(schema, "maxItems");
if (minItems.has_value() && arrayData.size() < static_cast<std::size_t>(*minItems)) {
addError(errors, path, "minItems", "items >= " + std::to_string(static_cast<int>(*minItems)), valuePreview(data));
}
if (maxItems.has_value() && arrayData.size() > static_cast<std::size_t>(*maxItems)) {
addError(errors, path, "maxItems", "items <= " + std::to_string(static_cast<int>(*maxItems)), valuePreview(data));
}
auto itemsIt = schema.find("items");
if (itemsIt != schema.end() && itemsIt->second.isObject()) {
for (std::size_t i = 0; i < arrayData.size(); ++i) {
validate(arrayData[i], itemsIt->second, path + "[" + std::to_string(i) + "]", errors);
}
}
}
}
JsonValue makeSampleSchema() {
JsonValue::Object root;
root["type"] = "object";
JsonValue::Array requiredRoot = {"users", "status", "metadata", "note"};
root["required"] = requiredRoot;
JsonValue::Object properties;
JsonValue::Object users;
users["type"] = "array";
users["minItems"] = 1.0;
JsonValue::Object userItem;
userItem["type"] = "object";
userItem["required"] = JsonValue::Array{"name", "age", "email", "tags", "active"};
JsonValue::Object userProps;
userProps["name"] = JsonValue::Object{{"type", "string"}, {"minLength", 3.0}, {"maxLength", 20.0}};
userProps["age"] = JsonValue::Object{{"type", "number"}, {"min", 0.0}, {"max", 120.0}};
userProps["email"] = JsonValue::Object{{"type", "string"}, {"pattern", "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"}};
userProps["active"] = JsonValue::Object{{"type", "boolean"}};
userProps["tags"] = JsonValue::Object{
{"type", "array"},
{"minItems", 1.0},
{"items", JsonValue::Object{{"type", "string"}, {"minLength", 2.0}, {"maxLength", 12.0}}}
};
userItem["properties"] = userProps;
users["items"] = userItem;
properties["users"] = users;
properties["status"] = JsonValue::Object{
{"type", "string"},
{"enum", JsonValue::Array{"new", "active", "disabled"}}
};
JsonValue::Object metadata;
metadata["type"] = "object";
metadata["required"] = JsonValue::Array{"version", "source"};
JsonValue::Object metadataProps;
metadataProps["version"] = JsonValue::Object{{"type", "string"}, {"pattern", "^\\d+\\.\\d+\\.\\d+$"}};
metadataProps["source"] = JsonValue::Object{
{"type", "object"},
{"required", JsonValue::Array{"systemId"}},
{"properties", JsonValue::Object{{"systemId", JsonValue::Object{{"type", "string"}, {"minLength", 5.0}}}}}
};
metadata["properties"] = metadataProps;
properties["metadata"] = metadata;
properties["note"] = JsonValue::Object{{"type", "null"}};
root["properties"] = properties;
return root;
}
JsonValue makeSampleData() {
JsonValue::Object root;
JsonValue::Array users;
users.push_back(JsonValue::Object{
{"name", "Alice"},
{"age", 29.0},
{"email", "alice@example.com"},
{"tags", JsonValue::Array{"owner", "ops"}},
{"active", true}
});
users.push_back(JsonValue::Object{
{"name", "Al"},
{"age", -5.0},
{"email", "invalid-email"},
{"tags", JsonValue::Array{"ok", 5.0, ""}},
{"active", "true"}
});
users.push_back(JsonValue::Object{
{"name", 123.0},
{"age", 200.0},
{"tags", JsonValue::Array{}},
{"active", false}
});
root["users"] = users;
root["status"] = "archived";
root["metadata"] = JsonValue::Object{
{"version", "1.0"},
{"source", JsonValue::Object{{"systemId", 42.0}}}
};
root["note"] = "should be null";
return root;
}
std::string nowIso8601Utc() {
auto now = std::chrono::system_clock::now();
std::time_t t = std::chrono::system_clock::to_time_t(now);
std::tm utc {};
#ifdef _WIN32
gmtime_s(&utc, &t);
#else
gmtime_r(&t, &utc);
#endif
std::ostringstream out;
out << std::put_time(&utc, "%Y-%m-%dT%H:%M:%SZ");
return out.str();
}
std::string readFile(const std::filesystem::path& path) {
std::ifstream input(path, std::ios::binary);
if (!input.is_open()) {
throw std::runtime_error("file read error");
}
std::ostringstream buffer;
buffer << input.rdbuf();
return buffer.str();
}
void writeFile(const std::filesystem::path& path, const std::string& content) {
std::ofstream output(path, std::ios::binary);
if (!output.is_open()) {
throw std::runtime_error("file write error");
}
output << content;
}
JsonValue parseJsonText(const std::string& text) {
JsonParser parser(text);
return parser.parse();
}
void printReport(const std::vector<ValidationError>& errors) {
if (errors.empty()) {
std::cout << "Validation succeeded: no violations found.\n";
return;
}
std::cout << "Validation failed: " << errors.size() << " violation(s) found.\n";
for (const auto& error : errors) {
std::cout << error.path << " | expected: " << error.rule << " -> " << error.expected
<< " | actual: " << error.actual << "\n";
}
}
JsonValue buildReport(const std::filesystem::path& dataPath, const std::filesystem::path& schemaPath,
const std::vector<ValidationError>& errors) {
JsonValue::Object report;
report["timestamp"] = nowIso8601Utc();
report["dataFile"] = dataPath.string();
report["schemaFile"] = schemaPath.string();
report["valid"] = errors.empty();
report["errorCount"] = static_cast<double>(errors.size());
JsonValue::Array errorArray;
for (const auto& err : errors) {
errorArray.push_back(JsonValue::Object{
{"path", err.path},
{"rule", err.rule},
{"expected", err.expected},
{"actual", err.actual}
});
}
report["errors"] = errorArray;
return report;
}
} // namespace
int main(int argc, char** argv) {
try {
if (argc != 1 && argc != 3) {
std::cerr << "Usage: ./json_schema_validator <data.json> <schema.json>\n";
std::cerr << " or: ./json_schema_validator\n";
return 1;
}
std::filesystem::path dataPath;
std::filesystem::path schemaPath;
if (argc == 1) {
dataPath = std::filesystem::absolute("sample_data.json");
schemaPath = std::filesystem::absolute("sample_schema.json");
writeFile(dataPath, toPrettyJson(makeSampleData()) + "\n");
writeFile(schemaPath, toPrettyJson(makeSampleSchema()) + "\n");
std::cout << "No input files provided. Generated sample data: " << dataPath.string() << "\n";
std::cout << "No input files provided. Generated sample schema: " << schemaPath.string() << "\n";
} else {
dataPath = std::filesystem::absolute(argv[1]);
schemaPath = std::filesystem::absolute(argv[2]);
}
JsonValue data;
JsonValue schema;
try {
data = parseJsonText(readFile(dataPath));
} catch (const ParseError& ex) {
std::cerr << "Data JSON parse error (" << dataPath.string() << "): line "
<< ex.line << ", col " << ex.column << ": " << ex.what() << "\n";
return 1;
} catch (const std::exception& ex) {
std::cerr << "Data file read error (" << dataPath.string() << "): " << ex.what() << "\n";
return 1;
}
try {
schema = parseJsonText(readFile(schemaPath));
} catch (const ParseError& ex) {
std::cerr << "Schema JSON parse error (" << schemaPath.string() << "): line "
<< ex.line << ", col " << ex.column << ": " << ex.what() << "\n";
return 1;
} catch (const std::exception& ex) {
std::cerr << "Schema file read error (" << schemaPath.string() << "): " << ex.what() << "\n";
return 1;
}
std::vector<ValidationError> errors;
validate(data, schema, "$", errors);
printReport(errors);
std::filesystem::path reportPath = std::filesystem::absolute("validation_report.json");
writeFile(reportPath, toPrettyJson(buildReport(dataPath, schemaPath, errors)) + "\n");
std::cout << "Saved structured report: " << reportPath.string() << "\n";
return errors.empty() ? 0 : 1;
} catch (const std::exception& ex) {
std::cerr << "Unexpected failure: " << ex.what() << "\n";
return 1;
}
}