← All tasks
cppcodex/cpp-t1 #2Not a task: already works

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;
    }
}