Grasscutters/Grasscutter · error · IOException

Invalid DynamicFloat definition -

Error message

Invalid DynamicFloat definition - 

What it means

Grasscutter's DynamicFloat Gson adapter expects a JSON array whose elements are only strings, numbers, or booleans (stack ops). When the next JSON token is anything else (e.g. OBJECT, NULL, or a non-array value), the adapter throws this IOException with the offending token name appended. Here the token name is empty in the message, indicating the peek value was unexpected during parsing of a resource file.

Source

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

                case STRING -> {
                    return new DynamicFloat(reader.nextString());
                }
                case NUMBER -> {
                    return new DynamicFloat((float) reader.nextDouble());
                }
                case BOOLEAN -> {
                    return new DynamicFloat(reader.nextBoolean());
                }
                case BEGIN_ARRAY -> {
                    reader.beginArray();
                    val opStack = new ArrayList<DynamicFloat.StackOp>();
                    while (reader.hasNext()) {
                        opStack.add(
                                switch (reader.peek()) {
                                    case STRING -> new DynamicFloat.StackOp(reader.nextString());
                                    case NUMBER -> new DynamicFloat.StackOp((float) reader.nextDouble());
                                    case BOOLEAN -> new DynamicFloat.StackOp(reader.nextBoolean());
                                    default -> throw new IOException(
                                            "Invalid DynamicFloat definition - " + reader.peek().name());
                                });
                    }
                    reader.endArray();
                    return new DynamicFloat(opStack);
                }
                default -> throw new IOException(
                        "Invalid DynamicFloat definition - " + reader.peek().name());
            }
        }

        @Override
        public void write(JsonWriter writer, DynamicFloat f) {}
    }

    class IntListAdapter extends TypeAdapter<IntList> {
        @Override
        public IntList read(JsonReader reader) throws IOException {

View on GitHub (pinned to f373827a83)

Solutions

  1. Open the resource JSON named in the stack trace and fix the DynamicFloat field to be an array of strings/numbers/booleans
  2. Replace null or nested objects in the field with a valid array, e.g. ["%HP"] or [100.0]
  3. Re-download or re-dump the resources from a source matching your Grasscutter version
  4. Check the JsonAdapters DynamicFloat adapter version matches the resource data format

Example fix

// before (resource JSON)
"battle_progress": {"value": 100}
// after
"battle_progress": [100.0]
Defensive patterns

Strategy: validation

Validate before calling

// Pre-validate a DynamicFloat resource value before handing it to Gson
static boolean isValidDynamicFloat(com.google.gson.JsonElement e) {
    if (e == null || e.isJsonNull()) return false;
    if (e.isJsonPrimitive()) {
        var p = e.getAsJsonPrimitive();
        return p.isString() || p.isNumber() || p.isBoolean();
    }
    if (e.isJsonArray()) {
        for (var el : e.getAsJsonArray()) {
            if (!el.isJsonPrimitive()) return false;
            var p = el.getAsJsonPrimitive();
            if (!(p.isString() || p.isNumber() || p.isBoolean())) return false;
        }
        return true;
    }
    return false; // objects and other tokens are rejected by the adapter
}

Type guard

static boolean isDynamicFloatPrimitive(com.google.gson.JsonElement el) {
    return el != null && el.isJsonPrimitive()
        && (el.getAsJsonPrimitive().isString()
            || el.getAsJsonPrimitive().isNumber()
            || el.getAsJsonPrimitive().isBoolean());
}

Try / catch

try {
    DynamicFloat f = gson.fromJson(json, DynamicFloat.class);
} catch (IOException | JsonParseException e) {
    logger.error("Malformed DynamicFloat in resource JSON: {}", e.getMessage());
    DynamicFloat f = new DynamicFloat(0f); // safe default
}

Prevention

When it happens

Trigger: Parsing a resource/quest JSON file whose DynamicFloat field contains a nested JSON object, null, or a non-array scalar (e.g. a bare number or string where an array of stack ops was expected) while loading game resources via Gson with this TypeAdapter registered.

Common situations: Game data files from a mismatched resources version (dumped data newer/older than the parser expects), hand-edited JSON that replaced the array with an object or null, or a corrupted/truncated download where the array content is malformed.

Related errors


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