-
Notifications
You must be signed in to change notification settings - Fork 116
Description
What happened?
Description
The valueToObject method in A2ACommonFieldMapper throws a NullPointerException when processing a protobuf Value that contains an empty Struct (i.e., a struct with no fields).
Root Cause
In the valueToObject method, when handling STRUCT_VALUE case, it directly calls structToMap(value.getStructValue()). However, the structToMap method returns null when the struct is empty (has 0 fields):
default Map<String, Object> structToMap(Struct struct) {
if (struct == null || struct.getFieldsCount() == 0) {
return null; // Returns null for empty struct
}
// ...
}This causes issues when the result is used in contexts that expect a non-null Map, such as when collecting stream results into a Map.
Stack Trace
java.lang.NullPointerException
at java.base/java.util.Objects.requireNonNull(Objects.java:233)
at java.base/java.util.stream.Collectors.lambda$uniqKeysMapAccumulator$1(Collectors.java:180)
at java.base/java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.base/java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet.lambda$entryConsumer$0(Collections.java:1778)
at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1939)
at java.base/java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntrySetSpliterator.forEachRemaining(Collections.java:1803)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
at io.a2a.grpc.mapper.A2ACommonFieldMapper.structToMap(A2ACommonFieldMapper.java:185)
at io.a2a.grpc.mapper.DataPartMapperImpl.fromProto(DataPartMapperImpl.java:39)
at io.a2a.grpc.mapper.PartMapper.fromProto(PartMapper.java:64)
at io.a2a.grpc.mapper.MessageMapperImpl.partListToPartArray(MessageMapperImpl.java:88)
at io.a2a.grpc.mapper.MessageMapperImpl.fromProto(MessageMapperImpl.java:72)
at io.a2a.grpc.mapper.MessageSendParamsMapperImpl.fromProto(MessageSendParamsMapperImpl.java:45)
at io.a2a.grpc.utils.ProtoUtils$FromProto.lambda$4(ProtoUtils.java:231)
at io.a2a.grpc.utils.ProtoUtils$FromProto.convert(ProtoUtils.java:193)
at io.a2a.grpc.utils.ProtoUtils$FromProto.messageSendParams(ProtoUtils.java:231)
Steps to Reproduce
- Create a JSON message containing an empty struct, for example:
{
"message": {
"messageId": "test-id",
"role": "ROLE_USER",
"parts": [{
"data": {
"data": {
"response": {
},
"id": "call_id",
"name": "transfer_to_agent"
}
}
}]
}
}-
Parse this JSON into a protobuf message and convert it using
ProtoUtils.FromProto.messageSendParams() -
A
NullPointerExceptionis thrown during the conversion
Expected Behavior
The method should return an empty Map instead of null when encountering an empty struct, allowing the conversion to complete successfully.
Proposed Fix
Add a null/empty check in the STRUCT_VALUE case of valueToObject method:
private Object valueToObject(Value value) {
switch (value.getKindCase()) {
case STRUCT_VALUE:
if (value.getStructValue() == null || value.getStructValue().getFieldsCount() < 1) {
return new java.util.HashMap<String, Object>();
}
return structToMap(value.getStructValue());
// ... other cases remain unchanged
}
}Affected File
spec-grpc/src/main/java/io/a2a/grpc/mapper/A2ACommonFieldMapper.java
Environment
- a2a-java version: 1.0.0.Alpha1 (or latest main branch)
- Java version: 17+
Relevant log output
Code of Conduct
- I agree to follow this project's Code of Conduct