{"id":"ee168c75da16d8c5","repo":"google/gson","slug":"cannot-serialize-srctype-getname-because-it-al","errorCode":null,"errorMessage":"cannot serialize {srcType.getName()} because it already defines a field named {typeFieldName}","messagePattern":"cannot serialize (.+?) because it already defines a field named (.+?)","errorType":"exception","errorClass":"JsonParseException","httpStatus":null,"severity":"error","filePath":"extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java","lineNumber":316,"sourceCode":"        Class<?> srcType = value.getClass();\n        String label = subtypeToLabel.get(srcType);\n        @SuppressWarnings(\"unchecked\") // registration requires that subtype extends T\n        TypeAdapter<R> delegate = (TypeAdapter<R>) subtypeToDelegate.get(srcType);\n        if (delegate == null) {\n          throw new JsonParseException(\n              \"cannot serialize \" + srcType.getName() + \"; did you forget to register a subtype?\");\n        }\n        JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();\n\n        if (maintainType) {\n          jsonElementAdapter.write(out, jsonObject);\n          return;\n        }\n\n        JsonObject clone = new JsonObject();\n\n        if (jsonObject.has(typeFieldName)) {\n          throw new JsonParseException(\n              \"cannot serialize \"\n                  + srcType.getName()\n                  + \" because it already defines a field named \"\n                  + typeFieldName);\n        }\n        clone.add(typeFieldName, new JsonPrimitive(label));\n\n        for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) {\n          clone.add(e.getKey(), e.getValue());\n        }\n        jsonElementAdapter.write(out, clone);\n      }\n    }.nullSafe();\n  }\n}\n","sourceCodeStart":298,"sourceCodeEnd":332,"githubUrl":"https://github.com/google/gson/blob/8b8628c65699bc4421696183c62ae0c1b9b281dc/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java#L298-L332","documentation":"Thrown during serialization by RuntimeTypeAdapterFactory's write() when the subtype's own serialized fields already include a key equal to the configured typeFieldName, and maintainType is false (the default). Because the adapter needs to prepend the discriminator field to the cloned object, a pre-existing field with the same name would collide. This is a JsonParseException.","triggerScenarios":"The subtype class (or its serialized form) has a field literally named \"type\" (the default typeFieldName), or you configured of(Shape.class, \"kind\") but the class already has a field named \"kind\"; a custom type adapter or @SerializedName produces a JSON member matching the discriminator name; maintainType=false (default) so the adapter tries to add the field itself.","commonSituations":"Domain models with a natural \"type\" field (common in tagged unions, AST nodes, discriminated unions); ORM entities that expose a type discriminator column; renaming the typeFieldName to something that happens to collide with an existing field; using a custom serializer that adds extra keys.","solutions":["Choose a typeFieldName that does not collide with any subtype field, e.g. of(Shape.class, \"@type\") or \"_type\".","Rename or exclude the conflicting field on the subtype class (e.g. @SerializedName to a different name, or transient).","If you want the subtype's own field preserved verbatim, use of(Shape.class, \"type\", true) (maintainType=true) which skips the prepend logic, but then you are responsible for the discriminator on the producer side."],"exampleFix":"// before: Shape subclass has a field named \"type\"\nclass Node { String type; int value; }\nRuntimeTypeAdapterFactory<Node> f = RuntimeTypeAdapterFactory.of(Node.class) // typeFieldName defaults to \"type\"\n    .registerSubtype(Node.class, \"leaf\");\ngson.toJson(node, Node.class); // throws: already defines \"type\"\n\n// after: use a non-colliding discriminator name\nRuntimeTypeAdapterFactory<Node> f = RuntimeTypeAdapterFactory.of(Node.class, \"@type\")\n    .registerSubtype(Node.class, \"leaf\");","handlingStrategy":"validation","validationCode":"// Pick a discriminator name that does not collide with any subtype field\nString typeFieldName = \"@type\"; // or \"_type\"\n// Verify no subtype declares a field/serializedName equal to typeFieldName\nList<Class<?>> subtypes = List.of(Node.class, Leaf.class);\nfor (Class<?> c : subtypes) {\n  for (Field f : c.getDeclaredFields()) {\n    if (typeFieldName.equals(f.getName())) throw new IllegalStateException(\"Collision on \" + typeFieldName);\n  }\n}\nRuntimeTypeAdapterFactory<Node> f = RuntimeTypeAdapterFactory.of(Node.class, typeFieldName);","typeGuard":"static boolean discriminatorCollides(String typeFieldName, List<Class<?>> subtypes) {\n  return subtypes.stream().flatMap(c -> Arrays.stream(c.getDeclaredFields()))\n      .anyMatch(f -> typeFieldName.equals(f.getName()));\n}","tryCatchPattern":null,"preventionTips":["Choose a discriminator name unlikely to collide, e.g. \"@type\" or \"_type\".","Audit subtype fields (and @SerializedName values) for collisions before wiring the factory.","If the subtype legitimately owns the field, consider maintainType=true and manage the discriminator yourself.","Add a unit test serializing each subtype to confirm no collision at runtime."],"tags":["runtime-type-adapter","polymorphic","serialization","field-collision","naming"],"analyzedSha":"8b8628c65699bc4421696183c62ae0c1b9b281dc","analyzedAt":"2026-08-04T19:12:22.202Z","schemaVersion":2}