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

  1. Ensure the JSON being deserialized is a single object at the root (unwrap arrays before parsing)
  2. Parse the element yourself, check jsonElement.isJsonObject(), and give a clearer error or skip it
  3. Fix the producer so it emits an object, not a quoted string or array
  4. 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

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


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)