google/gson · error · JsonParseException
cannot deserialize because it does not define a field named
Error message
cannot deserialize ${baseType} because it does not define a field named ${typeFieldName} What it means
Thrown during deserialization by RuntimeTypeAdapterFactory when the incoming JSON object has no member matching the configured typeFieldName (the discriminator). The adapter reads the whole object into a JsonElement, then removes (or gets) the discriminator field; if it is absent it cannot decide which concrete subtype to instantiate, so it throws a JsonParseException. This guards against ambiguous or malformed polymorphic payloads.
Solutions
- Confirm the incoming JSON object actually contains the discriminator key configured via RuntimeTypeAdapterFactory.of(base, typeFieldName).
- Align the typeFieldName on both the serializing and deserializing Gson instances (spelling AND case).
- If legacy data lacks the field, pre-process: parse to JsonObject, detect missing discriminator, inject a default label, then deserialize.
- If payloads legitimately omit the discriminator, do not route them through this adapter (deserialize as the concrete type directly).
Example fix
// before: factory expects "type" but JSON has none
factory = RuntimeTypeAdapterFactory.of(Shape.class, "type");
// {"width":10,"height":5} -> throws
// after: ensure producer emits the discriminator, or align the key
factory = RuntimeTypeAdapterFactory.of(Shape.class, "@type");
// and incoming JSON: {"@type":"Rectangle","width":10,"height":5} Defensive patterns
Strategy: validation
Validate before calling
// Ensure discriminator is present before handing to Gson
String discriminator = "type"; // must match factory config
JsonElement el = JsonParser.parseString(json);
if (el.isJsonObject() && !el.getAsJsonObject().has(discriminator)) {
throw new IllegalArgumentException("Missing discriminator '" + discriminator + "' in JSON");
}
Shape s = gson.fromJson(json, Shape.class); Try / catch
try {
gson.fromJson(json, Shape.class);
} catch (JsonParseException e) {
if (e.getMessage().contains("does not define a field named")) { /* re-route / default */ }
else throw e;
} Prevention
- Pin the typeFieldName as a shared constant between producer and consumer.
- Write a contract test asserting produced JSON contains the discriminator.
- For legacy data lacking the field, transform the JsonObject to inject a default label before deserializing.
When it happens
Trigger: Deserializing JSON that was serialized without the discriminator (e.g. produced by a plain Gson without the factory, or by another system); using a typeFieldName that differs between writer and reader (case mismatch included); payload only contains base-type fields; JSON was trimmed/manipulated dropping the type field.
Common situations: Producer and consumer disagree on the discriminator key ("type" vs "@type" vs "kind"); migrating an API to polymorphic encoding while old persisted data lacks the field; a third party sends the base type only; rename refactor of typeFieldName on one side only.
Related errors
- cannot deserialize subtype named ; did you forget to…
- cannot serialize because it already defines a field named
- cannot serialize ; did you forget to register a subtype?
- types and labels must be unique
- Array must have size 1, but has size
AI-assisted analysis of google/gson@310ac341f2 (2026-08-10).
Data as JSON: /api/errors/89551b2767efafc2.
Report an issue: GitHub.
Appendix: source
Thrown at extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java:276
for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) {
TypeAdapter<?> delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));
labelToDelegate.put(entry.getKey(), delegate);
subtypeToDelegate.put(entry.getValue(), delegate);
}
return new TypeAdapter<R>() {
@Override
public R read(JsonReader in) throws IOException {
JsonElement jsonElement = jsonElementAdapter.read(in);
JsonElement labelJsonElement;
if (maintainType) {
labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName);
} else {
labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName);
}
if (labelJsonElement == null) {
throw new JsonParseException(
"cannot deserialize "
+ baseType
+ " because it does not define a field named "
+ typeFieldName);
}
String label = labelJsonElement.getAsString();
@SuppressWarnings("unchecked") // registration requires that subtype extends T
TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label);
if (delegate == null) {
throw new JsonParseException(
"cannot deserialize "
+ baseType
+ " subtype named "
+ label
+ "; did you forget to register a subtype?");
}
return delegate.fromJsonTree(jsonElement);
}View on GitHub (pinned to 310ac341f2)