{"id":"201ebc3875fe2436","repo":"google/gson","slug":"unexpected-peeked-when-reading-a-jsonelement","errorCode":null,"errorMessage":"Unexpected {peeked} when reading a JsonElement.","messagePattern":"Unexpected (.+?) when reading a JsonElement\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java","lineNumber":317,"sourceCode":"    try {\n      result = primitive.getAsInt();\n    } catch (NumberFormatException e) {\n      throw numberFormatException(\"Expected an int but was \" + primitive.getAsString(), e);\n    }\n    popStack();\n    if (stackSize > 0) {\n      pathIndices[stackSize - 1]++;\n    }\n    return result;\n  }\n\n  JsonElement nextJsonElement() throws IOException {\n    JsonToken peeked = peek();\n    if (peeked == JsonToken.NAME\n        || peeked == JsonToken.END_ARRAY\n        || peeked == JsonToken.END_OBJECT\n        || peeked == JsonToken.END_DOCUMENT) {\n      throw new IllegalStateException(\"Unexpected \" + peeked + \" when reading a JsonElement.\");\n    }\n    JsonElement element = (JsonElement) peekStack();\n    skipValue();\n    return element;\n  }\n\n  @Override\n  public void close() throws IOException {\n    stack = new Object[] {SENTINEL_CLOSED};\n    stackSize = 1;\n  }\n\n  @Override\n  public void skipValue() throws IOException {\n    JsonToken peeked = peek();\n    switch (peeked) {\n      case NAME:\n        @SuppressWarnings(\"unused\")","sourceCodeStart":299,"sourceCodeEnd":335,"githubUrl":"https://github.com/google/gson/blob/8b8628c65699bc4421696183c62ae0c1b9b281dc/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java#L299-L335","documentation":"Thrown by JsonTreeReader.nextJsonElement() (an internal bridge called when a TypeAdapter/TypeAdapterFactory requests the current element as a JsonElement tree) when the reader is parked on a structural token that is not a value. Only NAME, END_ARRAY, END_OBJECT, and END_DOCUMENT are rejected; BEGIN_ARRAY, BEGIN_OBJECT, and all scalars are allowed. It signals that the caller asked for a complete element while the reader is mid-traversal of a container or at end of input.","triggerScenarios":"Calling a TypeAdapter that internally invokes JsonReader-based nextJsonElement() while positioned on a NAME token (i.e. expecting a value but the cursor advanced only to the key), or after an array/object has been fully consumed (END_ARRAY/END_OBJECT), or at EOF (END_DOCUMENT). Most commonly triggered by custom adapters calling gson.fromJson(..., JsonElement.class) on a sub-tree, or by adapters that call peek()/skipValue() in the wrong order before requesting the element.","commonSituations":"Writing a custom TypeAdapterFactory whose read() peeks then conditionally delegates, but the peek advanced the cursor to a NAME; mixing hasNext()/nextName() with code that tries to grab the whole entry as a JsonElement; deserializing into Object or JsonElement from an already partially-consumed reader; bugs in streaming adapters that forget to call nextName() before reading the value.","solutions":["Call nextName() (or beginObject()/beginArray()) to advance the reader off the NAME token before requesting the element as JsonElement.","Guard with a peek() check: only call the JsonElement-reading path when peek() is BEGIN_OBJECT, BEGIN_ARRAY, STRING, NUMBER, BOOLEAN, or NULL.","Ensure the reader has not already been exhausted (stackSize==0 -> END_DOCUMENT) before calling read; check hasNext() in your loop.","If you wrote a custom adapter, restructure so you consume NAME tokens with nextName() and then read the value, rather than grabbing the whole element mid-object."],"exampleFix":"// before: reader is on NAME, calling element read throws\nif (reader.peek() == JsonToken.NAME) {\n  reader.nextName(); // consume the name first\n}\nJsonElement el = gson.fromJson(reader, JsonElement.class);\n\n// after: only read element when positioned on a value token\nJsonToken t = reader.peek();\nif (t == JsonToken.NAME || t == JsonToken.END_ARRAY\n    || t == JsonToken.END_OBJECT || t == JsonToken.END_DOCUMENT) {\n  throw new IllegalStateException(\"not at a value: \" + t);\n}\nJsonElement el = gson.fromJson(reader, JsonElement.class);","handlingStrategy":"validation","validationCode":"// Validate reader position before requesting a JsonElement\nJsonToken t = reader.peek();\nif (t == JsonToken.NAME || t == JsonToken.END_ARRAY\n    || t == JsonToken.END_OBJECT || t == JsonToken.END_DOCUMENT) {\n  throw new IllegalStateException(\"Reader not at a value token: \" + t);\n}\nJsonElement el = gson.fromJson(reader, JsonElement.class);","typeGuard":"// Guard: only true when safe to read a value element\nstatic boolean atValueToken(JsonReader r) throws IOException {\n  JsonToken t = r.peek();\n  return t == JsonToken.BEGIN_OBJECT || t == JsonToken.BEGIN_ARRAY\n      || t == JsonToken.STRING || t == JsonToken.NUMBER\n      || t == JsonToken.BOOLEAN || t == JsonToken.NULL;\n}","tryCatchPattern":"try {\n  JsonElement el = gson.fromJson(reader, JsonElement.class);\n} catch (IllegalStateException e) {\n  if (e.getMessage() != null && e.getMessage().startsWith(\"Unexpected \")) {\n    // reader was mid-container or at EOF; advance and recover\n    reader.skipValue();\n  } else throw e;\n}","preventionTips":["Always pair nextName() with reading the value before doing element-level reads.","Use peek() to confirm a value/container token before calling JsonElement-reading APIs.","Write adapter tests that cover mid-object cursor positions to catch misuse early."],"tags":["json","streaming","gson","reader-state","type-adapter"],"analyzedSha":"8b8628c65699bc4421696183c62ae0c1b9b281dc","analyzedAt":"2026-08-04T19:12:22.202Z","schemaVersion":2}