apple/pkl · error · ParseException

Unexpected character

Error message

Unexpected character

What it means

Pkl's internal JSON parser throws 'Unexpected character' when, after parsing the top-level JSON value and skipping whitespace, there are additional non-whitespace characters left in the input. Valid JSON must contain exactly one value; trailing garbage (another value, stray brace, commentary) is rejected. The error surfaces to users as a jsonParseError hint from `json.parse`.

Source

Thrown at pkl-core/src/main/java/org/pkl/core/util/json/JsonParser.java:126

  public void parse(Reader reader, int buffersize) throws IOException {
    if (buffersize <= 0) {
      throw new IllegalArgumentException("buffersize is zero or negative");
    }
    this.reader = reader;
    buffer = new char[buffersize];
    bufferOffset = 0;
    index = 0;
    fill = 0;
    line = 1;
    lineOffset = 0;
    current = 0;
    captureStart = -1;
    read();
    skipWhiteSpace();
    readValue();
    skipWhiteSpace();
    if (!isEndOfText()) {
      throw error("Unexpected character");
    }
  }

  private void readValue() throws IOException {
    switch (current) {
      case 'n' -> readNull();
      case 't' -> readTrue();
      case 'f' -> readFalse();
      case '"' -> readString();
      case '[' -> readArray();
      case '{' -> readObject();
      case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> readNumber();
      default -> throw expected("value");
    }
  }

  private void readArray() throws IOException {
    var array = handler.startArray();

View on GitHub (pinned to f3efcbfc9b)

Solutions

  1. Parse NDJSON/JSON Lines by splitting on newlines and calling json.parse per line
  2. Remove trailing characters after the single JSON value in the input
  3. Ensure you are not concatenating multiple JSON documents into one string
  4. Extract just the first JSON document if the source emits one value plus extra output

Example fix

// before
obj = json.parse(fileText)  // NDJSON file
// after
objs = fileText.split("\n").filter((l) -> l.trim() != "").map((l) -> json.parse(l))
Defensive patterns

Strategy: validation

Validate before calling

// NDJSON detection: more than one top-level value
lines = text.split("\n").filter((l) -> l.trim() != "")
assert(lines.length == 1, "use per-line parsing for NDJSON")

Try / catch

try {
  value = json.parse(text)
} catch (e) {
  // possibly multiple documents -> parse line by line
  values = text.split("\n").filter((l) -> l.trim() != "").map((l) -> json.parse(l))
}

Prevention

When it happens

Trigger: Calling `json.parse` on input like `{"a":1}{"b":2}`, `123 abc`, `"x" "y"`, or JSON followed by a newline-containing log entry. Raised in JsonParser.parse when !isEndOfText() after readValue().

Common situations: Parsing JSON Lines / NDJSON files (multiple values, one per line) with a single-value parser; concatenating JSON files; a log file where JSON is followed by plain text lines.

Understand the failure class

Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.

Related errors


AI-assisted analysis of apple/pkl@f3efcbfc9b (2026-09-08). Data as JSON: /api/errors/5101ad15fc85538e. Report an issue: GitHub.