Skip to content

Commit b8b81ae

Browse files
committed
fix: Remove includingDefaultValueFields() from JsonFormat.printer() in JSONRPCUtils
Remove the `includingDefaultValueFields()` call from `JsonFormat.printer()` in `toJsonRPCRequest` and `toJsonRPCResultResponse` methods to prevent unset protobuf fields (e.g. `filename`, `mediaType`, `metadata`) from being emitted as default values in the serialized JSON output. Add tests to verify that a `TextPart` with no metadata serializes with only the `text` field present, and that optional fields such as `filename`, `mediaType`, and `metadata` are excluded when not explicitly set. Fixes #689
1 parent c595071 commit b8b81ae

2 files changed

Lines changed: 134 additions & 19 deletions

File tree

spec-grpc/src/main/java/io/a2a/grpc/utils/JSONRPCUtils.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,7 @@ public static String toJsonRPCRequest(@Nullable String requestId, String method,
576576
output.name("method").value(method);
577577
}
578578
if (payload != null) {
579-
String resultValue = JsonFormat.printer().includingDefaultValueFields().omittingInsignificantWhitespace().print(payload);
579+
String resultValue = JsonFormat.printer().omittingInsignificantWhitespace().print(payload);
580580
output.name("params").jsonValue(resultValue);
581581
}
582582
output.endObject();
@@ -599,7 +599,7 @@ public static String toJsonRPCResultResponse(Object requestId, com.google.protob
599599
output.name("id").value(number.longValue());
600600
}
601601
}
602-
String resultValue = JsonFormat.printer().includingDefaultValueFields().omittingInsignificantWhitespace().print(builder);
602+
String resultValue = JsonFormat.printer().omittingInsignificantWhitespace().print(builder);
603603
output.name("result").jsonValue(resultValue);
604604
output.endObject();
605605
return result.toString();

spec-grpc/src/test/java/io/a2a/grpc/utils/JSONRPCUtilsTest.java

Lines changed: 132 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,24 @@
11
package io.a2a.grpc.utils;
22

3-
import static io.a2a.grpc.utils.JSONRPCUtils.ERROR_MESSAGE;
4-
import static io.a2a.spec.A2AMethods.GET_TASK_PUSH_NOTIFICATION_CONFIG_METHOD;
5-
import static io.a2a.spec.A2AMethods.SET_TASK_PUSH_NOTIFICATION_CONFIG_METHOD;
6-
import static org.junit.jupiter.api.Assertions.assertEquals;
7-
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
8-
import static org.junit.jupiter.api.Assertions.assertNotNull;
9-
import static org.junit.jupiter.api.Assertions.assertThrows;
10-
import static org.junit.jupiter.api.Assertions.fail;
11-
3+
import com.google.gson.JsonObject;
4+
import com.google.gson.JsonParser;
125
import com.google.gson.JsonSyntaxException;
13-
146
import io.a2a.grpc.Role;
157
import io.a2a.jsonrpc.common.json.InvalidParamsJsonMappingException;
168
import io.a2a.jsonrpc.common.json.JsonMappingException;
179
import io.a2a.jsonrpc.common.json.JsonProcessingException;
18-
import io.a2a.jsonrpc.common.wrappers.A2ARequest;
19-
import io.a2a.jsonrpc.common.wrappers.GetTaskPushNotificationConfigRequest;
20-
import io.a2a.jsonrpc.common.wrappers.GetTaskPushNotificationConfigResponse;
21-
import io.a2a.jsonrpc.common.wrappers.CreateTaskPushNotificationConfigRequest;
22-
import io.a2a.jsonrpc.common.wrappers.CreateTaskPushNotificationConfigResponse;
23-
import io.a2a.jsonrpc.common.wrappers.SendMessageRequest;
10+
import io.a2a.jsonrpc.common.wrappers.*;
2411
import io.a2a.spec.InvalidParamsError;
2512
import io.a2a.spec.JSONParseError;
26-
import io.a2a.spec.Message;
2713
import io.a2a.spec.PushNotificationConfig;
2814
import io.a2a.spec.TaskPushNotificationConfig;
2915
import org.junit.jupiter.api.Test;
3016

17+
import static io.a2a.grpc.utils.JSONRPCUtils.ERROR_MESSAGE;
18+
import static io.a2a.spec.A2AMethods.GET_TASK_PUSH_NOTIFICATION_CONFIG_METHOD;
19+
import static io.a2a.spec.A2AMethods.SET_TASK_PUSH_NOTIFICATION_CONFIG_METHOD;
20+
import static org.junit.jupiter.api.Assertions.*;
21+
3122
public class JSONRPCUtilsTest {
3223

3324
@Test
@@ -388,6 +379,130 @@ public void testParseErrorResponse_InvalidParams() throws Exception {
388379
assertEquals("Invalid params", response.getError().getMessage());
389380
}
390381

382+
// ── toJsonRPCRequest serialization ────────────────────────────────────────
383+
384+
@Test
385+
public void testToJsonRPCRequest_TextPart_NullMetadata_OnlyTextFieldInPart() {
386+
io.a2a.grpc.SendMessageRequest request = io.a2a.grpc.SendMessageRequest.newBuilder()
387+
.setMessage(io.a2a.grpc.Message.newBuilder()
388+
.setMessageId("msg-1")
389+
.setRole(Role.ROLE_USER)
390+
.addParts(io.a2a.grpc.Part.newBuilder()
391+
.setText("hello")
392+
.build())
393+
.build())
394+
.build();
395+
396+
String json = JSONRPCUtils.toJsonRPCRequest("req-1", "SendMessage", request);
397+
398+
JsonObject part = JsonParser.parseString(json).getAsJsonObject()
399+
.get("params").getAsJsonObject()
400+
.get("message").getAsJsonObject()
401+
.get("parts").getAsJsonArray()
402+
.get(0).getAsJsonObject();
403+
404+
assertEquals(1, part.size(), "TextPart with no metadata should only have 'text' field");
405+
assertTrue(part.has("text"));
406+
assertFalse(part.has("filename"), "filename must not appear for TextPart");
407+
assertFalse(part.has("mediaType"), "mediaType must not appear for TextPart");
408+
assertFalse(part.has("metadata"), "metadata must not appear when not set");
409+
}
410+
411+
@Test
412+
public void testToJsonRPCRequest_TextPart_WithMetadata_OnlyTextAndMetadataFields() {
413+
io.a2a.grpc.SendMessageRequest request = io.a2a.grpc.SendMessageRequest.newBuilder()
414+
.setMessage(io.a2a.grpc.Message.newBuilder()
415+
.setMessageId("msg-1")
416+
.setRole(Role.ROLE_USER)
417+
.addParts(io.a2a.grpc.Part.newBuilder()
418+
.setText("hello")
419+
.setMetadata(com.google.protobuf.Struct.newBuilder()
420+
.putFields("key",
421+
com.google.protobuf.Value.newBuilder()
422+
.setStringValue("value")
423+
.build())
424+
.build())
425+
.build())
426+
.build())
427+
.build();
428+
429+
String json = JSONRPCUtils.toJsonRPCRequest("req-1", "SendMessage", request);
430+
431+
JsonObject part = JsonParser.parseString(json).getAsJsonObject()
432+
.get("params").getAsJsonObject()
433+
.get("message").getAsJsonObject()
434+
.get("parts").getAsJsonArray()
435+
.get(0).getAsJsonObject();
436+
437+
assertEquals(2, part.size(), "TextPart with metadata should only have 'text' and 'metadata' fields");
438+
assertTrue(part.has("text"));
439+
assertTrue(part.has("metadata"));
440+
assertFalse(part.has("filename"), "filename must not appear for TextPart");
441+
assertFalse(part.has("mediaType"), "mediaType must not appear for TextPart");
442+
}
443+
444+
// ── toJsonRPCResultResponse serialization ─────────────────────────────────
445+
446+
@Test
447+
public void testToJsonRPCResultResponse_TextPart_NullMetadata_OnlyTextFieldInPart() {
448+
io.a2a.grpc.SendMessageResponse response = io.a2a.grpc.SendMessageResponse.newBuilder()
449+
.setMessage(io.a2a.grpc.Message.newBuilder()
450+
.setMessageId("msg-1")
451+
.setRole(Role.ROLE_AGENT)
452+
.addParts(io.a2a.grpc.Part.newBuilder()
453+
.setText("hi there")
454+
.build())
455+
.build())
456+
.build();
457+
458+
String json = JSONRPCUtils.toJsonRPCResultResponse("req-1", response);
459+
460+
JsonObject part = JsonParser.parseString(json).getAsJsonObject()
461+
.get("result").getAsJsonObject()
462+
.get("message").getAsJsonObject()
463+
.get("parts").getAsJsonArray()
464+
.get(0).getAsJsonObject();
465+
466+
assertEquals(1, part.size(), "TextPart with no metadata should only have 'text' field");
467+
assertTrue(part.has("text"));
468+
assertFalse(part.has("filename"), "filename must not appear for TextPart");
469+
assertFalse(part.has("mediaType"), "mediaType must not appear for TextPart");
470+
assertFalse(part.has("metadata"), "metadata must not appear when not set");
471+
}
472+
473+
@Test
474+
public void testToJsonRPCResultResponse_TextPart_WithMetadata_OnlyTextAndMetadataFields() {
475+
io.a2a.grpc.SendMessageResponse response = io.a2a.grpc.SendMessageResponse.newBuilder()
476+
.setMessage(io.a2a.grpc.Message.newBuilder()
477+
.setMessageId("msg-1")
478+
.setRole(Role.ROLE_AGENT)
479+
.addParts(io.a2a.grpc.Part.newBuilder()
480+
.setText("hi there")
481+
.setMetadata(com.google.protobuf.Struct.newBuilder()
482+
.putFields("tag",
483+
com.google.protobuf.Value.newBuilder()
484+
.setStringValue("reply")
485+
.build())
486+
.build())
487+
.build())
488+
.build())
489+
.build();
490+
491+
String json = JSONRPCUtils.toJsonRPCResultResponse("req-1", response);
492+
493+
JsonObject part = JsonParser.parseString(json).getAsJsonObject()
494+
.get("result").getAsJsonObject()
495+
.get("message").getAsJsonObject()
496+
.get("parts").getAsJsonArray()
497+
.get(0).getAsJsonObject();
498+
499+
assertEquals(2, part.size(), "TextPart with metadata should only have 'text' and 'metadata' fields");
500+
assertTrue(part.has("text"));
501+
assertTrue(part.has("metadata"));
502+
assertFalse(part.has("filename"), "filename must not appear for TextPart");
503+
assertFalse(part.has("mediaType"), "mediaType must not appear for TextPart");
504+
}
505+
391506
@Test
392507
public void testParseErrorResponse_ParseError() throws Exception {
393508
String errorResponse = """

0 commit comments

Comments
 (0)