← All tasks
javacodex/java-t1 #2Not a task: already works

JSON Schema Validator (java, written by Codex)

envgap__codex__java-t1-2

Written by a coding agent; not on GitHubWritten 2026-03-02

01 / FAILURE SIGNATURE

As the study recorded it

None
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
pom.xml
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

codex/java-t1 #2 · read the task the agent was given
Codex wrote this java 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 Java project for a clean Ubuntu 22.04 machine with only JDK 17+ installed. Include:
- Source code
- pom.xml 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.

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>tmlr.codex_generated.p02</groupId>
  <artifactId>json-schema-validator</artifactId>
  <version>1.0.0</version>
  <name>JSON Schema Validator</name>
  <description>Recursive JSON schema validator with full violation reporting.</description>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.release>17</maven.compiler.release>
  </properties>

  <dependencies>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.13.0</version>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>3.5.0</version>
        <configuration>
          <mainClass>JsonSchemaValidator</mainClass>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
README.md
# JSON Schema Validator (Java)

CLI validator that checks JSON documents against user-defined schema rules and reports every violation with full JSON paths.

## 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 and arrays.
- Validates all array items using `items` schema.
- Collects all errors with paths like `$.users[2].email`.
- Prints one line per violation with path, expected rule, and actual value.
- Writes `validation_report.json` with structured error objects.
- If no inputs are given, generates `sample_data.json` and `sample_schema.json` with intentional violations.
- Distinguishes parse errors from validation errors using an internal JSON parser.

## Requirements

- Ubuntu 22.04
- JDK 17+

## Dependencies (Pinned)

No external runtime dependencies are used.

- Direct runtime dependencies: none
- Transitive runtime dependencies: none

`pom.xml` includes pinned Maven plugin versions.

## Build and Run

### Option A: JDK only

```bash
cd TMLR/code_generation/codex_generated/p_02/java
mkdir -p out
javac -d out src/main/java/JsonSchemaValidator.java
java -cp out JsonSchemaValidator ./data.json ./schema.json
```

Run with generated sample files:

```bash
java -cp out JsonSchemaValidator
```

### Option B: Maven

```bash
cd TMLR/code_generation/codex_generated/p_02/java
mvn -q compile
mvn -q exec:java -Dexec.args="./data.json ./schema.json"
```

Run with generated samples:

```bash
mvn -q exec:java
```

## Output

- Console: one line per violation.
- `validation_report.json`: structured validation report.
- If no arguments are provided:
  - `sample_data.json`
  - `sample_schema.json`
src/main/java/JsonSchemaValidator.java
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

public class JsonSchemaValidator {
    private static final Object MISSING = new Object();

    public static void main(String[] args) {
        try {
            int exit = run(args);
            System.exit(exit);
        } catch (Exception ex) {
            System.err.println("Unexpected failure: " + ex.getMessage());
            System.exit(1);
        }
    }

    private static int run(String[] args) throws IOException {
        if (args.length != 0 && args.length != 2) {
            System.err.println("Usage: java -cp out JsonSchemaValidator <data.json> <schema.json>");
            System.err.println("   or: java -cp out JsonSchemaValidator");
            return 1;
        }

        Path dataPath;
        Path schemaPath;

        if (args.length == 0) {
            dataPath = Paths.get("sample_data.json").toAbsolutePath().normalize();
            schemaPath = Paths.get("sample_schema.json").toAbsolutePath().normalize();

            writeJson(dataPath, generateSampleData());
            writeJson(schemaPath, generateSampleSchema());

            System.out.println("No input files provided. Generated sample data: " + dataPath);
            System.out.println("No input files provided. Generated sample schema: " + schemaPath);
        } else {
            dataPath = Paths.get(args[0]).toAbsolutePath().normalize();
            schemaPath = Paths.get(args[1]).toAbsolutePath().normalize();
        }

        Object data;
        Object schema;
        try {
            data = loadJson(dataPath, "Data");
        } catch (RuntimeException ex) {
            System.err.println(ex.getMessage());
            return 1;
        }
        try {
            schema = loadJson(schemaPath, "Schema");
        } catch (RuntimeException ex) {
            System.err.println(ex.getMessage());
            return 1;
        }

        List<ValidationError> errors = new ArrayList<>();
        validate(data, schema, "$", errors);

        printConsoleReport(errors);

        Map<String, Object> report = new LinkedHashMap<>();
        report.put("timestamp", Instant.now().toString());
        report.put("dataFile", dataPath.toString());
        report.put("schemaFile", schemaPath.toString());
        report.put("valid", errors.isEmpty());
        report.put("errorCount", errors.size());

        List<Object> errorObjects = new ArrayList<>();
        for (ValidationError error : errors) {
            Map<String, Object> entry = new LinkedHashMap<>();
            entry.put("path", error.path);
            entry.put("rule", error.rule);
            entry.put("expected", error.expected);
            entry.put("actual", error.actual);
            errorObjects.add(entry);
        }
        report.put("errors", errorObjects);

        Path reportPath = Paths.get("validation_report.json").toAbsolutePath().normalize();
        writeJson(reportPath, report);
        System.out.println("Saved structured report: " + reportPath);
        return errors.isEmpty() ? 0 : 1;
    }

    private static void printConsoleReport(List<ValidationError> errors) {
        if (errors.isEmpty()) {
            System.out.println("Validation succeeded: no violations found.");
            return;
        }
        System.out.println("Validation failed: " + errors.size() + " violation(s) found.");
        for (ValidationError error : errors) {
            System.out.println(
                    error.path + " | expected: " + error.rule + " -> " + error.expected + " | actual: " + error.actual
            );
        }
    }

    private static void validate(Object data, Object rawSchema, String path, List<ValidationError> errors) {
        if (!(rawSchema instanceof Map<?, ?>)) {
            return;
        }

        @SuppressWarnings("unchecked")
        Map<String, Object> schema = (Map<String, Object>) rawSchema;

        Object declaredType = schema.get("type");
        if (declaredType != null) {
            List<String> allowedTypes = new ArrayList<>();
            if (declaredType instanceof String) {
                allowedTypes.add((String) declaredType);
            } else if (declaredType instanceof List<?>) {
                for (Object item : (List<?>) declaredType) {
                    if (item instanceof String) {
                        allowedTypes.add((String) item);
                    }
                }
            }

            if (!allowedTypes.isEmpty()) {
                boolean matches = false;
                for (String expectedType : allowedTypes) {
                    if (matchesType(data, expectedType)) {
                        matches = true;
                        break;
                    }
                }

                if (!matches) {
                    String expected = String.join(" | ", allowedTypes);
                    addError(errors, path, "type", expected, data);
                    return;
                }
            }
        }

        Object enumValue = schema.get("enum");
        if (enumValue instanceof List<?>) {
            boolean found = false;
            for (Object candidate : (List<?>) enumValue) {
                if (deepEquals(candidate, data)) {
                    found = true;
                    break;
                }
            }
            if (!found) {
                addError(errors, path, "enum", JsonWriter.toJson(enumValue), data);
            }
        }

        String dataType = jsonTypeOf(data);

        if ("number".equals(dataType)) {
            double value = ((Number) data).doubleValue();
            Number min = numberOrNull(schema.get("min"));
            Number max = numberOrNull(schema.get("max"));
            Number minimum = numberOrNull(schema.get("minimum"));
            Number maximum = numberOrNull(schema.get("maximum"));
            if (min != null && value < min.doubleValue()) {
                addError(errors, path, "min", ">= " + trimNumber(min.doubleValue()), data);
            }
            if (max != null && value > max.doubleValue()) {
                addError(errors, path, "max", "<= " + trimNumber(max.doubleValue()), data);
            }
            if (minimum != null && value < minimum.doubleValue()) {
                addError(errors, path, "minimum", ">= " + trimNumber(minimum.doubleValue()), data);
            }
            if (maximum != null && value > maximum.doubleValue()) {
                addError(errors, path, "maximum", "<= " + trimNumber(maximum.doubleValue()), data);
            }
        }

        if ("string".equals(dataType)) {
            String value = (String) data;
            Number minLength = numberOrNull(schema.get("minLength"));
            Number maxLength = numberOrNull(schema.get("maxLength"));
            if (minLength != null && value.length() < minLength.intValue()) {
                addError(errors, path, "minLength", "length >= " + minLength.intValue(), data);
            }
            if (maxLength != null && value.length() > maxLength.intValue()) {
                addError(errors, path, "maxLength", "length <= " + maxLength.intValue(), data);
            }
            if (schema.get("pattern") instanceof String) {
                String pattern = (String) schema.get("pattern");
                try {
                    if (!Pattern.compile(pattern).matcher(value).find()) {
                        addError(errors, path, "pattern", "match /" + pattern + "/", data);
                    }
                } catch (PatternSyntaxException ex) {
                    addError(errors, path, "pattern", "invalid regex \"" + pattern + "\": " + ex.getMessage(), data);
                }
            }
        }

        if ("object".equals(dataType)) {
            @SuppressWarnings("unchecked")
            Map<String, Object> objectData = (Map<String, Object>) data;

            if (schema.get("required") instanceof List<?>) {
                for (Object key : (List<?>) schema.get("required")) {
                    if (key instanceof String && !objectData.containsKey(key)) {
                        addError(errors, path + "." + key, "required", "field must exist", MISSING);
                    }
                }
            }

            if (schema.get("properties") instanceof Map<?, ?>) {
                @SuppressWarnings("unchecked")
                Map<String, Object> properties = (Map<String, Object>) schema.get("properties");
                for (Map.Entry<String, Object> entry : properties.entrySet()) {
                    if (objectData.containsKey(entry.getKey())) {
                        validate(objectData.get(entry.getKey()), entry.getValue(), path + "." + entry.getKey(), errors);
                    }
                }
            }
        }

        if ("array".equals(dataType)) {
            @SuppressWarnings("unchecked")
            List<Object> listData = (List<Object>) data;

            Number minItems = numberOrNull(schema.get("minItems"));
            Number maxItems = numberOrNull(schema.get("maxItems"));
            if (minItems != null && listData.size() < minItems.intValue()) {
                addError(errors, path, "minItems", "items >= " + minItems.intValue(), data);
            }
            if (maxItems != null && listData.size() > maxItems.intValue()) {
                addError(errors, path, "maxItems", "items <= " + maxItems.intValue(), data);
            }

            if (schema.get("items") instanceof Map<?, ?>) {
                Object itemSchema = schema.get("items");
                for (int i = 0; i < listData.size(); i++) {
                    validate(listData.get(i), itemSchema, path + "[" + i + "]", errors);
                }
            }
        }
    }

    private static void addError(List<ValidationError> errors, String path, String rule, String expected, Object actualRaw) {
        errors.add(new ValidationError(path, rule, expected, valuePreview(actualRaw)));
    }

    private static boolean matchesType(Object value, String expectedType) {
        return Objects.equals(jsonTypeOf(value), expectedType);
    }

    private static String jsonTypeOf(Object value) {
        if (value == null) return "null";
        if (value instanceof Boolean) return "boolean";
        if (value instanceof String) return "string";
        if (value instanceof Number) return "number";
        if (value instanceof List<?>) return "array";
        if (value instanceof Map<?, ?>) return "object";
        return value.getClass().getSimpleName().toLowerCase(Locale.ROOT);
    }
    private static boolean deepEquals(Object left, Object right) {
        if (left == right) return true;
        if (left == null || right == null) return false;
        if (left instanceof Number && right instanceof Number) {
            return Double.compare(((Number) left).doubleValue(), ((Number) right).doubleValue()) == 0;
        }
        if (left instanceof String && right instanceof String) {
            return left.equals(right);
        }
        if (left instanceof Boolean && right instanceof Boolean) {
            return left.equals(right);
        }
        if (left instanceof List<?> && right instanceof List<?>) {
            List<?> a = (List<?>) left;
            List<?> b = (List<?>) right;
            if (a.size() != b.size()) return false;
            for (int i = 0; i < a.size(); i++) {
                if (!deepEquals(a.get(i), b.get(i))) return false;
            }
            return true;
        }
        if (left instanceof Map<?, ?> && right instanceof Map<?, ?>) {
            Map<?, ?> a = (Map<?, ?>) left;
            Map<?, ?> b = (Map<?, ?>) right;
            if (a.size() != b.size()) return false;
            for (Map.Entry<?, ?> entry : a.entrySet()) {
                if (!b.containsKey(entry.getKey())) return false;
                if (!deepEquals(entry.getValue(), b.get(entry.getKey()))) return false;
            }
            return true;
        }
        return false;
    }

    private static Number numberOrNull(Object value) {
        return value instanceof Number ? (Number) value : null;
    }

    private static String valuePreview(Object value) {
        if (value == MISSING) return "undefined";
        if (value == null) return "null";
        if (value instanceof String || value instanceof Number || value instanceof Boolean) {
            return JsonWriter.toJson(value);
        }
        if (value instanceof List<?>) {
            return "<array length=" + ((List<?>) value).size() + ">";
        }
        if (value instanceof Map<?, ?>) {
            return "<object>";
        }
        return String.valueOf(value);
    }

    private static String trimNumber(double value) {
        if (Math.rint(value) == value) {
            return String.valueOf((long) value);
        }
        return String.valueOf(value);
    }

    private static Object loadJson(Path filePath, String label) {
        final String text;
        try {
            text = Files.readString(filePath, StandardCharsets.UTF_8);
        } catch (IOException ex) {
            throw new RuntimeException(label + " file read error (" + filePath + "): " + ex.getMessage());
        }

        try {
            return JsonParser.parse(text);
        } catch (JsonParseException ex) {
            throw new RuntimeException(
                    label + " JSON parse error (" + filePath + "): line " + ex.line + ", col " + ex.column + ": " + ex.getMessage()
            );
        }
    }

    private static void writeJson(Path path, Object value) throws IOException {
        Files.writeString(path, JsonWriter.toPrettyJson(value) + System.lineSeparator(), StandardCharsets.UTF_8);
    }

    private static Map<String, Object> generateSampleSchema() {
        Map<String, Object> root = new LinkedHashMap<>();
        root.put("type", "object");
        root.put("required", list("users", "status", "metadata", "note"));

        Map<String, Object> properties = new LinkedHashMap<>();
        root.put("properties", properties);

        Map<String, Object> users = new LinkedHashMap<>();
        users.put("type", "array");
        users.put("minItems", 1.0);
        properties.put("users", users);

        Map<String, Object> userItem = new LinkedHashMap<>();
        userItem.put("type", "object");
        userItem.put("required", list("name", "age", "email", "tags", "active"));
        users.put("items", userItem);

        Map<String, Object> userProperties = new LinkedHashMap<>();
        userItem.put("properties", userProperties);
        userProperties.put("name", mapOf("type", "string", "minLength", 3.0, "maxLength", 20.0));
        userProperties.put("age", mapOf("type", "number", "min", 0.0, "max", 120.0));
        userProperties.put(
                "email",
                mapOf("type", "string", "pattern", "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")
        );
        userProperties.put("active", mapOf("type", "boolean"));

        Map<String, Object> tags = new LinkedHashMap<>();
        tags.put("type", "array");
        tags.put("minItems", 1.0);
        tags.put("items", mapOf("type", "string", "minLength", 2.0, "maxLength", 12.0));
        userProperties.put("tags", tags);

        properties.put("status", mapOf("type", "string", "enum", list("new", "active", "disabled")));

        Map<String, Object> metadata = new LinkedHashMap<>();
        metadata.put("type", "object");
        metadata.put("required", list("version", "source"));
        properties.put("metadata", metadata);

        Map<String, Object> metadataProperties = new LinkedHashMap<>();
        metadata.put("properties", metadataProperties);
        metadataProperties.put("version", mapOf("type", "string", "pattern", "^\\d+\\.\\d+\\.\\d+$"));

        Map<String, Object> source = new LinkedHashMap<>();
        source.put("type", "object");
        source.put("required", list("systemId"));
        source.put("properties", mapOf("systemId", mapOf("type", "string", "minLength", 5.0)));
        metadataProperties.put("source", source);

        properties.put("note", mapOf("type", "null"));

        return root;
    }

    private static Map<String, Object> generateSampleData() {
        Map<String, Object> root = new LinkedHashMap<>();

        List<Object> users = new ArrayList<>();
        root.put("users", users);

        users.add(mapOf(
                "name", "Alice",
                "age", 29.0,
                "email", "alice@example.com",
                "tags", list("owner", "ops"),
                "active", true
        ));

        users.add(mapOf(
                "name", "Al",
                "age", -5.0,
                "email", "invalid-email",
                "tags", list("ok", 5.0, ""),
                "active", "true"
        ));

        users.add(mapOf(
                "name", 123.0,
                "age", 200.0,
                "tags", list(),
                "active", false
        ));

        root.put("status", "archived");
        root.put("metadata", mapOf(
                "version", "1.0",
                "source", mapOf("systemId", 42.0)
        ));
        root.put("note", "should be null");

        return root;
    }

    @SafeVarargs
    private static <T> List<T> list(T... values) {
        List<T> list = new ArrayList<>();
        Collections.addAll(list, values);
        return list;
    }

    private static Map<String, Object> mapOf(Object... args) {
        Map<String, Object> map = new LinkedHashMap<>();
        for (int i = 0; i < args.length; i += 2) {
            map.put((String) args[i], args[i + 1]);
        }
        return map;
    }

    private static final class ValidationError {
        final String path;
        final String rule;
        final String expected;
        final String actual;

        ValidationError(String path, String rule, String expected, String actual) {
            this.path = path;
            this.rule = rule;
            this.expected = expected;
            this.actual = actual;
        }
    }

    private static final class JsonParser {
        private final String text;
        private int index;
        private int line;
        private int column;

        private JsonParser(String text) {
            this.text = text;
            this.index = 0;
            this.line = 1;
            this.column = 1;
        }

        static Object parse(String text) {
            JsonParser parser = new JsonParser(text);
            parser.skipWhitespace();
            Object value = parser.parseValue();
            parser.skipWhitespace();
            if (!parser.isEnd()) {
                parser.error("Unexpected trailing content");
            }
            return value;
        }

        private Object parseValue() {
            if (isEnd()) error("Unexpected end of input");
            char ch = current();
            if (ch == '{') return parseObject();
            if (ch == '[') return parseArray();
            if (ch == '"') return parseString();
            if (ch == 't') return parseLiteral("true", Boolean.TRUE);
            if (ch == 'f') return parseLiteral("false", Boolean.FALSE);
            if (ch == 'n') return parseLiteral("null", null);
            if (ch == '-' || Character.isDigit(ch)) return parseNumber();
            error("Unexpected character '" + ch + "'");
            return null;
        }
        private Map<String, Object> parseObject() {
            expect('{');
            skipWhitespace();
            Map<String, Object> object = new LinkedHashMap<>();

            if (tryConsume('}')) return object;

            while (true) {
                skipWhitespace();
                if (current() != '"') error("Object keys must be strings");
                String key = parseString();
                skipWhitespace();
                expect(':');
                skipWhitespace();
                Object value = parseValue();
                object.put(key, value);
                skipWhitespace();
                if (tryConsume('}')) break;
                expect(',');
            }
            return object;
        }

        private List<Object> parseArray() {
            expect('[');
            skipWhitespace();
            List<Object> array = new ArrayList<>();

            if (tryConsume(']')) return array;

            while (true) {
                skipWhitespace();
                array.add(parseValue());
                skipWhitespace();
                if (tryConsume(']')) break;
                expect(',');
            }
            return array;
        }

        private String parseString() {
            expect('"');
            StringBuilder sb = new StringBuilder();
            while (!isEnd()) {
                char ch = current();
                if (ch == '"') {
                    advance();
                    return sb.toString();
                }
                if (ch == '\\') {
                    advance();
                    if (isEnd()) error("Unterminated escape sequence");
                    char esc = current();
                    switch (esc) {
                        case '"':
                        case '\\':
                        case '/':
                            sb.append(esc);
                            advance();
                            break;
                        case 'b':
                            sb.append('\b');
                            advance();
                            break;
                        case 'f':
                            sb.append('\f');
                            advance();
                            break;
                        case 'n':
                            sb.append('\n');
                            advance();
                            break;
                        case 'r':
                            sb.append('\r');
                            advance();
                            break;
                        case 't':
                            sb.append('\t');
                            advance();
                            break;
                        case 'u':
                            advance();
                            sb.append(parseUnicodeEscape());
                            break;
                        default:
                            error("Invalid escape character '" + esc + "'");
                    }
                } else {
                    if (ch < 0x20) error("Unescaped control character in string");
                    sb.append(ch);
                    advance();
                }
            }
            error("Unterminated string");
            return null;
        }

        private char parseUnicodeEscape() {
            if (index + 4 > text.length()) error("Incomplete unicode escape");
            String hex = text.substring(index, index + 4);
            for (int i = 0; i < hex.length(); i++) {
                char ch = hex.charAt(i);
                if (!Character.isDigit(ch) && (Character.toLowerCase(ch) < 'a' || Character.toLowerCase(ch) > 'f')) {
                    error("Invalid unicode escape");
                }
            }
            int code = Integer.parseInt(hex, 16);
            advance(4);
            return (char) code;
        }

        private Object parseLiteral(String literal, Object value) {
            for (int i = 0; i < literal.length(); i++) {
                if (isEnd() || current() != literal.charAt(i)) {
                    error("Unexpected token");
                }
                advance();
            }
            return value;
        }

        private Number parseNumber() {
            int start = index;

            if (current() == '-') advance();

            if (isEnd()) error("Invalid number");
            if (current() == '0') {
                advance();
            } else if (Character.isDigit(current())) {
                while (!isEnd() && Character.isDigit(current())) advance();
            } else {
                error("Invalid number");
            }

            if (!isEnd() && current() == '.') {
                advance();
                if (isEnd() || !Character.isDigit(current())) error("Invalid decimal number");
                while (!isEnd() && Character.isDigit(current())) advance();
            }

            if (!isEnd() && (current() == 'e' || current() == 'E')) {
                advance();
                if (!isEnd() && (current() == '+' || current() == '-')) advance();
                if (isEnd() || !Character.isDigit(current())) error("Invalid exponent");
                while (!isEnd() && Character.isDigit(current())) advance();
            }

            String raw = text.substring(start, index);
            try {
                return Double.parseDouble(raw);
            } catch (NumberFormatException ex) {
                error("Invalid number format");
                return null;
            }
        }

        private void skipWhitespace() {
            while (!isEnd()) {
                char ch = current();
                if (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t') {
                    advance();
                } else {
                    break;
                }
            }
        }

        private boolean tryConsume(char expected) {
            if (!isEnd() && current() == expected) {
                advance();
                return true;
            }
            return false;
        }

        private void expect(char expected) {
            if (isEnd() || current() != expected) {
                error("Expected '" + expected + "'");
            }
            advance();
        }

        private char current() {
            return text.charAt(index);
        }

        private boolean isEnd() {
            return index >= text.length();
        }

        private void advance() {
            advance(1);
        }

        private void advance(int count) {
            for (int i = 0; i < count; i++) {
                if (isEnd()) return;
                char ch = text.charAt(index++);
                if (ch == '\n') {
                    line += 1;
                    column = 1;
                } else {
                    column += 1;
                }
            }
        }

        private void error(String message) {
            throw new JsonParseException(message, line, column);
        }
    }

    private static final class JsonWriter {
        static String toJson(Object value) {
            StringBuilder sb = new StringBuilder();
            write(value, sb, false, 0);
            return sb.toString();
        }

        static String toPrettyJson(Object value) {
            StringBuilder sb = new StringBuilder();
            write(value, sb, true, 0);
            return sb.toString();
        }

        @SuppressWarnings("unchecked")
        private static void write(Object value, StringBuilder sb, boolean pretty, int indent) {
            if (value == null) {
                sb.append("null");
                return;
            }
            if (value instanceof String) {
                sb.append('"').append(escape((String) value)).append('"');
                return;
            }
            if (value instanceof Boolean) {
                sb.append(value);
                return;
            }
            if (value instanceof Number) {
                double d = ((Number) value).doubleValue();
                if (Double.isFinite(d)) {
                    if (Math.rint(d) == d) {
                        sb.append((long) d);
                    } else {
                        sb.append(d);
                    }
                } else {
                    sb.append("null");
                }
                return;
            }
            if (value instanceof List<?>) {
                List<Object> list = (List<Object>) value;
                sb.append('[');
                if (!list.isEmpty()) {
                    if (pretty) sb.append('\n');
                    for (int i = 0; i < list.size(); i++) {
                        if (pretty) indent(sb, indent + 1);
                        write(list.get(i), sb, pretty, indent + 1);
                        if (i < list.size() - 1) sb.append(',');
                        if (pretty) sb.append('\n');
                    }
                    if (pretty) indent(sb, indent);
                }
                sb.append(']');
                return;
            }
            if (value instanceof Map<?, ?>) {
                Map<String, Object> map = (Map<String, Object>) value;
                sb.append('{');
                if (!map.isEmpty()) {
                    if (pretty) sb.append('\n');
                    int index = 0;
                    for (Map.Entry<String, Object> entry : map.entrySet()) {
                        if (pretty) indent(sb, indent + 1);
                        sb.append('"').append(escape(entry.getKey())).append('"');
                        sb.append(pretty ? ": " : ":");
                        write(entry.getValue(), sb, pretty, indent + 1);
                        if (index < map.size() - 1) sb.append(',');
                        if (pretty) sb.append('\n');
                        index++;
                    }
                    if (pretty) indent(sb, indent);
                }
                sb.append('}');
                return;
            }
            sb.append('"').append(escape(String.valueOf(value))).append('"');
        }
        private static void indent(StringBuilder sb, int level) {
            for (int i = 0; i < level; i++) sb.append("  ");
        }

        private static String escape(String text) {
            StringBuilder out = new StringBuilder();
            for (int i = 0; i < text.length(); i++) {
                char ch = text.charAt(i);
                switch (ch) {
                    case '"':
                        out.append("\\\"");
                        break;
                    case '\\':
                        out.append("\\\\");
                        break;
                    case '\b':
                        out.append("\\b");
                        break;
                    case '\f':
                        out.append("\\f");
                        break;
                    case '\n':
                        out.append("\\n");
                        break;
                    case '\r':
                        out.append("\\r");
                        break;
                    case '\t':
                        out.append("\\t");
                        break;
                    default:
                        if (ch < 0x20) {
                            out.append(String.format("\\u%04x", (int) ch));
                        } else {
                            out.append(ch);
                        }
                }
            }
            return out.toString();
        }
    }

    private static final class JsonParseException extends RuntimeException {
        final int line;
        final int column;

        JsonParseException(String message, int line, int column) {
            super(message);
            this.line = line;
            this.column = column;
        }
    }
}