GenericData.containsKey() returns true for unset (null) declared fields, violating Map contract
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 84/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- java
- Domain
- developer-experience
Research direction
Start in com.google.api.client.util.GenericData.java and inspect containsKey alongside DataMap.containsKey and the referenced FieldInfo access. Verify the ExampleModel reproduction, then confirm declared null fields are absent while non-null declared and unknown fields retain the expected Map behavior.
Written by the indexing model from the issue text.
Description
Description
PR #2151 introduced an override for GenericData.containsKey(Object name) that queries classInfo.hasFieldInfo(fieldName). However, ClassInfo.hasFieldInfo checks only whether the @Key field is declared in class reflection metadata, rather than checking whether the field holds a non-null value on the instance.
Impact & Broken Invariants
In GenericData, declared fields with null values are treated as absent from the map:
keySet()/entrySet()Contradiction:
Onnew MyData(),containsKey("field")returnstrue, butget("field")isnull,entrySet()has size0, andkeySet().toString()outputs[].Set.containsvsIteratorInconsistency:
Becausejava.util.AbstractMap.keySet().contains(k)delegates toMap.containsKey(k),model.keySet().contains("field")evaluates totrue, while iterating overmodel.keySet()yields0elements.- Client Breakages:
Code patterns checking for field presence (such as pagination checks likeif (response.containsKey("pageToken"))) now evaluate totrueeven when the server never populated the field.
Reproduction
public class ExampleModel extends GenericData {
@Key private String optionalField;
}
ExampleModel model = new ExampleModel();
// Prior to 2.2.0:
// model.containsKey("optionalField") == false
// In 2.2.0:
model.containsKey("optionalField"); // returns true!
model.get("optionalField"); // returns null
model.keySet(); // prints []
model.keySet().contains("optionalField"); // returns true while iterator is empty
Proposed Fix
In com.google.api.client.util.GenericData.java, check whether the declared field value is non-null, matching DataMap.containsKey():
@Override
public final boolean containsKey(Object name) {
if (!(name instanceof String)) {
return false;
}
String fieldName = (String) name;
FieldInfo fieldInfo = classInfo.getFieldInfo(fieldName);
if (fieldInfo != null) {
return fieldInfo.getValue(this) != null;
}
if (classInfo.getIgnoreCase()) {
fieldName = fieldName.toLowerCase(Locale.US);
}
return unknownFields.containsKey(fieldName);
}
- Dominant language
- Java
- Stars
- 1.4k
- Forks
- 473
- PR merge metrics
- No merged PRs in 30d
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from googleapis/google-http-java-client
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
-
Core: LowLevelHttpResponse not disconnected when HttpResponse construction throws RuntimeException Open
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
-
type: cleanup
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
priority: p2 type: cleanup
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
All issues in googleapis/google-http-java-client
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
infinispan/infinispan#18150 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
-
untriaged
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
opensearch-project/k-NN#3597 ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 82/100