HMCL-dev/HMCL · error · JsonParseException
Account private data is not an object
Error message
Account private data is not an object
What it means
AccountPrivateData's Gson deserializer accepts JSON null (returns null) but requires that any present value be a JSON object. If the element is an array, string, or primitive, it throws JsonParseException("Account private data is not an object"). This protects the migration/upgrade logic that iterates the object's members and applies schema upgrades.
Solutions
- Open the config file and fix the account entry to be a JSON object (or delete the malformed entry so HMCL recreates it)
- Back up and remove the corrupted accounts config, then re-add the account in the UI
- If migrating from an old format, use the HMCL version that supports that format to convert first
- Wrap deserialization in try-catch for JsonParseException and fall back to an empty/legacy migration path
Example fix
// before
"accounts": [{ "username": "steve" }]
// after
"accounts": { "0": { "username": "steve" } } Defensive patterns
Strategy: validation
Validate before calling
JsonElement el = config.get("accounts");
if (el != null && !el.isJsonNull() && !(el instanceof JsonObject))
throw new IOException("accounts config must be a JSON object"); Type guard
static boolean isAccountObject(JsonElement el) {
return el != null && el instanceof JsonObject;
} Try / catch
try {
AccountPrivateData d = gson.fromJson(el, AccountPrivateData.class);
} catch (JsonParseException e) {
LOGGER.warning("Malformed account entry, skipping: " + e.getMessage());
// keep other accounts, re-create the malformed one
} Prevention
- Never hand-edit the accounts config while HMCL is running
- Back up the config before manual edits or migrations
- Migrate old formats through the HMCL version that supports them
- Validate JSON structure with a schema check before distributing configs
When it happens
Trigger: Deserializing the accounts config when an account entry (or the whole accounts value) is a non-object JSON value — e.g. an old-format array of accounts, an account serialized as a string, or a corrupted config file.
Common situations: Hand-edited hmcl accounts.json with malformed structure; config written by a much older HMCL version using a different layout; file truncated so a fragment (array/string) remains; manual merge of config files gone wrong.
Related errors
AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10).
Data as JSON: /api/errors/855f001d803fe5d2.
Report an issue: GitHub.
Appendix: source
Thrown at HMCL/src/main/java/org/jackhuang/hmcl/setting/AccountPrivateData.java:272
JsonSerializationContext context) {
JsonObject result = new JsonObject();
result.addProperty(JsonSchema.PROPERTY_SCHEMA, accountPrivateData.getSchema().url());
protectionMode().writePayload(result, createPayload(accountPrivateData, context));
accountPrivateData.unknownFields.forEach(result::add);
return result;
}
/// Deserializes the private data store from a protected payload envelope.
@Override
public @Nullable AccountPrivateData deserialize(
@Nullable JsonElement json,
Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
if (json == null || json.isJsonNull()) {
return null;
}
if (!(json instanceof JsonObject object)) {
throw new JsonParseException("Account private data is not an object");
}
AccountPrivateData accountPrivateData = new AccountPrivateData();
Map<String, JsonElement> values = new LinkedHashMap<>(object.asMap());
JsonElement schema = values.remove(JsonSchema.PROPERTY_SCHEMA);
if (schema != null && schema.isJsonPrimitive() && schema.getAsJsonPrimitive().isString()) {
accountPrivateData.setSchema(new JsonSchema(schema.getAsString()));
}
values.remove(ProtectedPayload.PROPERTY_PROTECTION);
values.remove(ProtectedPayload.PROPERTY_NONCE);
values.remove(ProtectedPayload.PROPERTY_PAYLOAD);
accountPrivateData.unknownFields.putAll(values);
readPayload(accountPrivateData, ProtectedPayload.read(object, JsonArray.class), context);
return accountPrivateData;
}
}
}View on GitHub (pinned to 24702dc5a0)