HMCL-dev/HMCL · error · JsonParseException
Expected JsonObject but got
Error message
Expected JsonObject but got
What it means
GameInstanceManifest's Gson JsonDeserializer expects the incoming JsonElement to be a JsonObject; when it is not, it throws JsonParseException('Expected JsonObject but got ' + element class name), naming the actual class (e.g. JsonPrimitive, JsonArray). This surfaces type mismatches between the configured JSON and the expected manifest object shape.
Solutions
- Ensure the JSON being deserialized is a single object at the root (unwrap arrays before parsing)
- Parse the element yourself, check jsonElement.isJsonObject(), and give a clearer error or skip it
- Fix the producer so it emits an object, not a quoted string or array
- Register the TypeAdapter only for fields that truly hold manifest objects
Example fix
// before
GameInstanceManifest m = GSON.fromJson(element, GameInstanceManifest.class);
// after
if (element.isJsonObject()) {
GameInstanceManifest m = GSON.fromJson(element, GameInstanceManifest.class);
} else {
throw new JsonParseException("Manifest must be an object, got: " + element);
} Defensive patterns
Strategy: type-guard
Validate before calling
if (element == null || !element.isJsonObject()) throw new JsonParseException("Expected JsonObject, got: " + (element == null ? "null" : element.getClass().getSimpleName())); Type guard
static boolean isManifestObject(JsonElement e) {
return e != null && e.isJsonObject();
} Try / catch
try { m = GSON.fromJson(element, GameInstanceManifest.class); }
catch (JsonParseException e) { if (e.getMessage().startsWith("Expected JsonObject but got")) { LOG.warning(e.getMessage()); m = null; } else throw e; } Prevention
- Check isJsonObject() before handing JsonElements to manifest deserialization
- Ensure producers emit objects, not arrays or double-encoded strings
- Register the manifest TypeAdapter only where manifest objects are expected
When it happens
Trigger: Gson deserializing a manifest where the JSON node is an array, string, number, or JsonNull instead of an object — e.g. a manifest file whose root is [ ... ] or a field deserialized with this adapter holding the wrong JSON type.
Common situations: Manifest file saved as a JSON array of instances instead of one object; wrong field wired to a TypeAdapter expecting a manifest; API/remote returning a JSON string that was double-encoded; JsonNull from a missing optional field passed to the adapter.
Related errors
- json.toString()
- Protected payload is not a
- Config is not an object:
- Protected payload lane is not a string
- Theme background field must be a string:
AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10).
Data as JSON: /api/errors/c8a917b1fcb2024d.
Report an issue: GitHub.
Appendix: source
Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameInstanceManifest.java:978
rawJson
);
}
}
static final class Adapter extends TypeAdapter<@Nullable GameInstanceManifest> {
@Override
public @Nullable GameInstanceManifest read(JsonReader in) {
JsonElement jsonElement = JsonParser.parseReader(in);
if (jsonElement.isJsonNull()) {
return null;
}
if (jsonElement instanceof JsonObject jsonObject) {
return GameInstanceManifest.fromJson(jsonObject, false);
}
throw new JsonParseException("Expected JsonObject but got " + jsonElement.getClass().getName());
}
@Override
public void write(JsonWriter out, @Nullable GameInstanceManifest value) throws IOException {
if (value != null)
JsonUtils.GSON.toJson(value.toJsonObject(), out);
else
out.nullValue();
}
}
}
View on GitHub (pinned to 24702dc5a0)