json-parser is a from-scratch JSON parser implementation in Java, built as part of the codingchallenges.fyi "Build Your Own JSON Parser" challenge. It is a Maven single-module project (org.example:json-parser:1.0-SNAPSHOT) with no external runtime dependencies — only JUnit for testing. The project is early-stage: the lexer is partially implemented, the parser is an empty stub, and the public entry point has not been built yet.
Intended pipeline (recursive-descent style JSON parsing):
source text → PushbackReader → Lexer.nextToken() → Token(TokenType, lexeme, line, column) → Parser → parsed value → JsonParser (entry point)
Lexer(src/main/java/org/example/jsonparser/Lexer.java) wraps ajava.io.PushbackReaderand exposesToken nextToken() throws IOException. It tracksline/columnfor error reporting and has private helpersadvance(),skipWhiteSpace(),readString(),readNumber(),readKeyword().Tokenis an immutablerecord Token(TokenType type, String lexeme, int line, int column).TokenTypeis a flat enum:LBRACE, RBRACE, COLON, COMMA, STRING, NUMBER, TRUE, FALSE, NULL, EOF. Note: noLBRACKET/RBRACKETyet — JSON array syntax ([,]) is not tokenized.Parser(src/main/java/org/example/jsonparser/Parser.java) is currently an empty class (public class Parser {}). No recursive-descent logic, no result type defined yet.JsonParser(src/main/java/org/example/jsonparser/JsonParser.java) is the intended public entry point; currentlymain()just prints a placeholder ("Step 0" stub). Noparse(...)API exists yet.JsonParseException(unchecked, extendsRuntimeException) carriesline/columnand is thrown fromLexer.readString()(unterminated string) andLexer.readKeyword()(unrecognized keyword).Parserdoes not yet throw/handle errors.
Known bugs in Lexer to be aware of when extending it (do not silently "fix" without checking test expectations first):
skipWhiteSpace()condition is inverted (!Character.isWhitespace(...)) — currently skips non-whitespace instead of whitespace.'{'/'}'are mapped toRBRACE/LBRACErespectively (swapped).- No initial
advance()call in the constructor, socurrentCharstarts at0before the firstnextToken()call. - The default case in
nextToken()'s dispatch returnsnew Token(null, null, 0, 0)for unrecognized characters instead of throwingJsonParseException. readNumber()doesn't handle exponents (e/E) after a decimal point.
| Path | Purpose |
|---|---|
src/main/java/org/example/jsonparser/ |
All production code (single flat package, no sub-packages): Lexer.java, Token.java, TokenType.java, Parser.java, JsonParser.java, JsonParseException.java |
src/main/resources/ |
Empty — no config, fixtures, or schemas yet |
src/test/java/org/example/jsonparser/ |
JUnit 5 tests, mirrors the main package |
.mvn/ |
Present but empty — no Maven wrapper committed |
target/ |
Maven build output (gitignored); regenerated by mvn |
No Maven wrapper is committed — mvn must be on PATH.
mvn clean test # compile + run all tests (JUnit via Surefire)
mvn compile # compile main sources only
mvn package # build target/json-parser-1.0-SNAPSHOT.jar
mvn -Dtest=SanityTest test # run a single test classThere is no linter, formatter, or CI configuration in this repo — none to run.
- Java 24, compiled with
maven.compiler.source/target/release=24, UTF-8 source encoding. Preview features and annotation processing are disabled. - Single flat package: everything lives in
org.example.jsonparser— no sub-packaging by layer. - Records for data:
Tokenis arecord; use records for new immutable data carriers rather than plain classes with boilerplate getters. - Enums for closed sets:
TokenTypeis a plain enum with no fields/methods — follow this for token/kind classification. - Switch expressions:
Lexer.readKeyword()uses arrow-formswitchexpressions (return switch (lexeme) { case "true" -> ...; }) — prefer this modern form over classicswitchstatements for new dispatch logic. - Exceptions:
JsonParseException extends RuntimeException(unchecked) and always carriesline/columnfor diagnostics; the message is built asmessage + " at line " + line + ", column " + column. Follow this pattern for any new parse-time errors instead of throwing generic exceptions. - I/O:
Lexeris built aroundPushbackReaderfor single-character lookahead/pushback rather than buffering the whole input into aString— preserve this streaming style if extending the lexer. - No javadoc/comments anywhere in the current codebase — match the terse style unless documenting genuinely non-obvious logic.
- No external libraries beyond JDK stdlib in main code; JUnit Jupiter is test-scope only.
src/main/java/org/example/jsonparser/JsonParser.java— intended CLI/API entry point (currently a stubmain)src/main/java/org/example/jsonparser/Lexer.java— tokenizer; most substantial logic currently in the projectsrc/main/java/org/example/jsonparser/Parser.java— recursive-descent parser to be implemented (currently empty)src/main/java/org/example/jsonparser/Token.java/TokenType.java— token modelsrc/main/java/org/example/jsonparser/JsonParseException.java— error type with line/column contextpom.xml— single source of build truth (Java version, dependencies, plugins)
- Build tool: Maven (system-installed
mvn; no wrapper committed, though.gitignoreun-ignores.mvn/wrapper/maven-wrapper.jarsuggesting one was once intended). - JDK: 24 (
openjdk-24per.idea/misc.xml; matches Eclipse.settings/org.eclipse.jdt.core.prefs). - IDE metadata: Both IntelliJ (
.idea/) and Eclipse (.classpath,.project,.settings/) config exist locally but are gitignored — don't rely on them being present in a fresh checkout. - No package manager beyond Maven Central — the only dependency is
org.junit.jupiter:junit-jupiter:6.0.1(test scope).
- Framework: JUnit 5 (Jupiter), run via
maven-surefire-plugin3.5.4. Uses@Testandorg.junit.jupiter.api.Assertions.assertEquals— no@ParameterizedTest, AssertJ, or Hamcrest currently in use. - Current state:
src/test/java/org/example/jsonparser/SanityTest.javacontains exactly one test,junitIsRunning(), which only asserts2 == 1 + 1to confirm the test harness is wired up. No parser functionality (Lexer, Parser, JsonParser, JsonParseException) is tested yet. - No test resources:
src/test/resources/does not exist; no JSON fixture files anywhere. - When adding tests: follow the existing
camelCase, descriptive-phrase method naming (e.g.junitIsRunning), place them undersrc/test/java/org/example/jsonparser/, and prefer covering theLexerfirst (valid tokens, whitespace, strings with escapes, numbers, keywords, unterminated-string errors) since it's the most implemented component. Given the identifiedLexerbugs above, confirm expected behavior with the user/spec before writing tests that assume current (buggy) behavior is correct.