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

  1. Open the config file and fix the account entry to be a JSON object (or delete the malformed entry so HMCL recreates it)
  2. Back up and remove the corrupted accounts config, then re-add the account in the UI
  3. If migrating from an old format, use the HMCL version that supports that format to convert first
  4. 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

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)