Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,12 @@ private Value objectToValue(Object value) {
private Object valueToObject(Value value) {
switch (value.getKindCase()) {
case STRUCT_VALUE:
if (value.getStructValue() == null) {
return null;
}
if (value.getStructValue().getFieldsCount() == 0) {
return java.util.Collections.emptyMap();
}
return structToMap(value.getStructValue());
case LIST_VALUE:
return value.getListValue().getValuesList().stream()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package io.a2a.grpc.mapper;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import java.util.Map;

import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.util.JsonFormat;

import io.a2a.grpc.utils.ProtoUtils;
import io.a2a.spec.MessageSendParams;
import org.junit.jupiter.api.Test;

public class A2ACommonFieldMapperTest {

/**
* Test that valueToObject handles empty struct correctly without throwing NullPointerException.
*
* This test verifies the fix for the bug where an empty struct in the JSON
* (e.g., "response": {}) would cause a NullPointerException because structToMap
* returns null for empty structs.
*/
@Test
void testValueToObject_WithEmptyStruct_ReturnsEmptyMap() throws InvalidProtocolBufferException {
// JSON containing an empty struct in "response" field
String json = "{\n" +
" \"message\": {\n" +
" \"messageId\": \"b3b1ab58-c3d0-4e6d-9e47-9d8a12fe0809\",\n" +
" \"role\": \"ROLE_USER\",\n" +
" \"parts\": [{\n" +
" \"text\": \"Hello\"\n" +
" }, {\n" +
" \"data\": {\n" +
" \"data\": {\n" +
" \"id\": \"call_94yo5ymj3qi5glbpkw5eicfd\",\n" +
" \"args\": {\n" +
" \"agent_name\": \"Default Agent\"\n" +
" },\n" +
" \"name\": \"transfer_to_agent\"\n" +
" }\n" +
" }\n" +
" }, {\n" +
" \"data\": {\n" +
" \"data\": {\n" +
" \"response\": {\n" +
" },\n" +
" \"id\": \"call_94yo5ymj3qi5glbpkw5eicfd\",\n" +
" \"name\": \"transfer_to_agent\"\n" +
" }\n" +
" }\n" +
" }, {\n" +
" \"text\": \"World\"\n" +
" }],\n" +
" \"metadata\": {\n" +
" }\n" +
" },\n" +
" \"configuration\": {\n" +
" \"blocking\": true\n" +
" },\n" +
" \"metadata\": {\n" +
" }\n" +
"}";

io.a2a.grpc.SendMessageRequest.Builder builder = io.a2a.grpc.SendMessageRequest.newBuilder();
JsonFormat.parser().merge(json, builder);

// This should not throw NullPointerException
MessageSendParams messageSendParams = ProtoUtils.FromProto.messageSendParams(builder);

assertNotNull(messageSendParams);
assertNotNull(messageSendParams.message());
assertEquals(4, messageSendParams.message().parts().size());
}

/**
* Test that valueToObject handles nested empty struct correctly.
*/
@Test
void testValueToObject_WithNestedEmptyStruct_ReturnsEmptyMap() throws InvalidProtocolBufferException {
String json = "{\n" +
" \"message\": {\n" +
" \"messageId\": \"test-id\",\n" +
" \"role\": \"ROLE_USER\",\n" +
" \"parts\": [{\n" +
" \"data\": {\n" +
" \"data\": {\n" +
" \"nested\": {\n" +
" \"empty\": {\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }]\n" +
" }\n" +
"}";

io.a2a.grpc.SendMessageRequest.Builder builder = io.a2a.grpc.SendMessageRequest.newBuilder();
JsonFormat.parser().merge(json, builder);

// This should not throw NullPointerException
MessageSendParams messageSendParams = ProtoUtils.FromProto.messageSendParams(builder);

assertNotNull(messageSendParams);
assertNotNull(messageSendParams.message());
}
}