HMCL-dev/HMCL · error · JsonParseException

Cannot deserialize

Error message

Cannot deserialize 

What it means

JsonTypeAdapterFactory's read() selects a subtype adapter by looking up a type-label property in the JSON object. This JsonParseException is thrown when the JSON object lacks the field named by @JsonType(property=...), so the polymorphic subtype cannot be determined.

Solutions

  1. Add the discriminator field (jsonType.property()) with a valid label to the JSON object.
  2. Verify the property name in the @JsonType annotation matches what the JSON actually contains.
  3. Re-serialize the object with the same Gson instance so the type label is written.
  4. Handle legacy payloads by migrating them to include the type field before deserialization.

Example fix

// before
{"name": "x"}
// after
{"type": "concrete", "name": "x"}
Defensive patterns

Strategy: validation

Validate before calling

JsonObject obj = JsonParser.parseString(json).getAsJsonObject();
if (!obj.has("type") || obj.get("type").isJsonNull())
    throw new IllegalArgumentException("Missing discriminator field 'type'");

Type guard

static boolean hasTypeLabel(JsonObject obj, String property) {
    JsonElement e = obj.get(property);
    return e != null && e.isJsonPrimitive() && e.getAsJsonPrimitive().isString();
}

Try / catch

try {
    return gson.fromJson(json, PolyType.class);
} catch (JsonParseException e) {
    log.error("Missing type discriminator: {}", e.getMessage());
}

Prevention

When it happens

Trigger: Deserializing a polymorphic type whose JSON is missing the discriminator property, e.g. an object without the "type" field the factory was configured with.

Common situations: Hand-written JSON missing the type tag, an upstream API dropping the field, serialization produced by a version where the property had a different name.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10). Data as JSON: /api/errors/a7eab4fdcfd381da. Report an issue: GitHub.

Appendix: source

Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonTypeAdapterFactory.java:73

                TypeAdapter<T> delegate = (TypeAdapter<T>) classTypeAdapterMap.get(type);
                if (delegate == null) {
                    throw new JsonParseException("Cannot serialize " + type.getName() + ". Please check your @JsonType configuration");
                }
                JsonSubtype subtype = classJsonSubtypeMap.get(type);
                JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();
                if (jsonObject.has(jsonType.property())) {
                    throw new JsonParseException("Cannot serialize " + type.getName() + ". Because it has already defined a field named '" + jsonType.property() + "'");
                }
                jsonObject.add(jsonType.property(), new JsonPrimitive(subtype.name()));
                Streams.write(jsonObject, out);
            }

            @Override
            public T read(JsonReader in) {
                JsonElement jsonElement = Streams.parse(in);
                JsonElement typeLabelElement = jsonElement.getAsJsonObject().get(jsonType.property());
                if (typeLabelElement == null) {
                    throw new JsonParseException("Cannot deserialize " + type + ". Because it does not define a field named '" + jsonType.property() + "'");
                }
                String typeLabel = typeLabelElement.getAsString();
                @SuppressWarnings("unchecked")
                TypeAdapter<T> delegate = (TypeAdapter<T>) labelTypeAdapterMap.get(typeLabel);
                if (delegate == null) {
                    throw new JsonParseException("Cannot deserialize " + type + " with subtype '" + typeLabel + "'");
                }

                return delegate.fromJsonTree(jsonElement);
            }
        };
    }

    private <T> TypeAdapter<T> createForJsonSubtype(Gson gson, TypeToken<T> type) {
        Class<? super T> rawType = type.getRawType();
        if (rawType.getSuperclass() == null) return null;
        JsonType jsonType = rawType.getSuperclass().getDeclaredAnnotation(JsonType.class);
        if (jsonType == null)

View on GitHub (pinned to 24702dc5a0)