Grasscutters/Grasscutter · error · IOException

Invalid Position definition -

Error message

Invalid Position definition - 

What it means

The Position TypeAdapter dispatches on JsonReader.peek(): an array is the canonical form and an object is the key-based form; any other token (STRING, NUMBER, BOOLEAN, NULL, NAME, END_*) is rejected. This occurs when a Position value in JSON is not an array or object, e.g. a bare number, string, or null.

Source

Thrown at src/main/java/emu/grasscutter/utils/JsonAdapters.java:142

                }
                case BEGIN_OBJECT -> { // "pos": {"x": x, "y": y, "z": z}
                    float x = 0f;
                    float y = 0f;
                    float z = 0f;
                    reader.beginObject();
                    for (var next = reader.peek(); next != JsonToken.END_OBJECT; next = reader.peek()) {
                        val name = reader.nextName();
                        switch (name) {
                            case "x", "X", "_x" -> x = (float) reader.nextDouble();
                            case "y", "Y", "_y" -> y = (float) reader.nextDouble();
                            case "z", "Z", "_z" -> z = (float) reader.nextDouble();
                            default -> throw new IOException("Invalid field in Position definition - " + name);
                        }
                    }
                    reader.endObject();
                    return new Position(x, y, z);
                }
                default -> throw new IOException("Invalid Position definition - " + reader.peek().name());
            }
        }

        @Override
        public void write(JsonWriter writer, Position i) throws IOException {
            writer.beginArray();
            writer.value(i.getX());
            writer.value(i.getY());
            writer.value(i.getZ());
            writer.endArray();
        }
    }

    class EnumTypeAdapterFactory implements TypeAdapterFactory {
        @SuppressWarnings("unchecked")
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
            Class<T> enumClass = (Class<T>) type.getRawType();
            if (!enumClass.isEnum()) return null;

View on GitHub (pinned to f373827a83)

Solutions

  1. Set the field to the canonical array form [x, y, z] in the JSON
  2. Set the field to an object form {"x":..,"y":..,"z":..}
  3. Replace null with a valid position or make the containing field optional/generic so the adapter is not invoked on null
  4. Validate/repair the JSON file with a linter before loading

Example fix

// before
"pos": "100.5, 200, 300.25"
// after
"pos": [100.5, 200, 300.25]
Defensive patterns

Strategy: type-guard

Validate before calling

boolean isWellFormedPositionValue(com.google.gson.JsonElement el) {
  return el != null && (el.isJsonArray()
      || (el.isJsonObject() && el.getAsJsonObject().keySet().stream()
          .allMatch(k -> k.matches("[xyzXYZ]|_[xyz]"))));
}

Type guard

boolean isPositionLike(com.google.gson.JsonElement el) {
  return el != null && (el.isJsonArray() && el.getAsJsonArray().size() == 3 || el.isJsonObject());
}

Try / catch

try {
  Position p = gson.fromJson(el, Position.class);
} catch (IOException e) {
  logger.warn("Skipping malformed Position (token {})", e.getMessage());
}

Prevention

When it happens

Trigger: Parsing JSON where a Position field holds a non-structural value: "pos": null, "pos": "1,2,3", "pos": 100, or malformed input where the reader is positioned at a NAME token due to earlier mis-parsing.

Common situations: Null coordinates in exported configs; positions serialized as delimited strings by other tools; truncated or corrupted JSON files leaving the reader at an unexpected token.

Related errors


AI-assisted analysis of Grasscutters/Grasscutter@f373827a83 (2026-09-03). Data as JSON: /api/errors/e83bec0532c98aba. Report an issue: GitHub.