Skip to content

Commit 668d5bb

Browse files
committed
Alternate implementation for kind serialization
Fixes #131
1 parent 1dbc533 commit 668d5bb

File tree

8 files changed

+29
-124
lines changed

8 files changed

+29
-124
lines changed

src/A2A/A2AJsonUtilities.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,11 @@ public static partial class A2AJsonUtilities
4646
[JsonSerializable(typeof(AgentCard))]
4747
[JsonSerializable(typeof(AgentTask))]
4848
[JsonSerializable(typeof(GetTaskPushNotificationConfigParams))]
49-
[JsonSerializable(typeof(Message))]
5049
[JsonSerializable(typeof(MessageSendParams))]
5150
[JsonSerializable(typeof(TaskIdParams))]
5251
[JsonSerializable(typeof(TaskPushNotificationConfig))]
5352
[JsonSerializable(typeof(List<TaskPushNotificationConfig>))]
54-
[JsonSerializable(typeof(TaskArtifactUpdateEvent))]
5553
[JsonSerializable(typeof(TaskQueryParams))]
56-
[JsonSerializable(typeof(TaskStatusUpdateEvent))]
5754

5855
[ExcludeFromCodeCoverage]
5956
internal sealed partial class JsonContext : JsonSerializerContext;

src/A2A/Models/A2AResponse.cs

Lines changed: 15 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,115 +1,30 @@
1-
using System.Text.Json;
21
using System.Text.Json.Serialization;
32

43
namespace A2A;
54

65
/// <summary>
76
/// Base class for A2A events.
87
/// </summary>
9-
[JsonConverter(typeof(A2AEventConverter))]
10-
public abstract class A2AEvent
8+
/// <param name="kind">The event kind discriminator.</param>
9+
[JsonPolymorphic(TypeDiscriminatorPropertyName = "kind")]
10+
[JsonDerivedType(typeof(TaskStatusUpdateEvent), "status-update")]
11+
[JsonDerivedType(typeof(TaskArtifactUpdateEvent), "artifact-update")]
12+
[JsonDerivedType(typeof(Message), "message")]
13+
[JsonDerivedType(typeof(AgentTask), "task")]
14+
public abstract class A2AEvent(string kind)
1115
{
1216
/// <summary>
13-
/// Event object discriminator.
17+
/// Gets the event kind discriminator used for polymorphic serialization.
1418
/// </summary>
15-
[JsonPropertyName("kind")]
16-
public abstract string Kind { get; }
17-
}
18-
19-
/// <summary>
20-
/// JSON converter for A2AEvent.
21-
/// </summary>
22-
sealed class A2AEventConverter : JsonConverter<A2AEvent>
23-
{
24-
/// <inheritdoc/>
25-
public override A2AEvent? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
26-
{
27-
if (reader.TokenType != JsonTokenType.StartObject)
28-
{
29-
throw new JsonException("Expected StartObject token");
30-
}
31-
32-
using var document = JsonDocument.ParseValue(ref reader);
33-
if (!document.RootElement.TryGetProperty("kind", out var kindProperty) || kindProperty.ValueKind != JsonValueKind.String)
34-
{
35-
throw new JsonException("Missing or invalid 'kind' discriminator for A2AEvent.");
36-
}
37-
38-
var kind = kindProperty.GetString();
39-
A2AEvent? result = kind switch
40-
{
41-
"message" => document.RootElement.Deserialize(A2AJsonUtilities.JsonContext.Default.Message),
42-
"task" => document.RootElement.Deserialize(A2AJsonUtilities.JsonContext.Default.AgentTask),
43-
"status-update" => document.RootElement.Deserialize(A2AJsonUtilities.JsonContext.Default.TaskStatusUpdateEvent),
44-
"artifact-update" => document.RootElement.Deserialize(A2AJsonUtilities.JsonContext.Default.TaskArtifactUpdateEvent),
45-
_ => null,
46-
};
47-
48-
if (result is null)
49-
{
50-
throw new JsonException($"Unknown A2AEvent kind '{kind}'.");
51-
}
52-
53-
return result;
54-
}
55-
56-
/// <inheritdoc/>
57-
public override void Write(Utf8JsonWriter writer, A2AEvent value, JsonSerializerOptions options)
58-
{
59-
if (value is null)
60-
{
61-
writer.WriteNullValue();
62-
return;
63-
}
64-
65-
switch (value)
66-
{
67-
case Message message:
68-
JsonSerializer.Serialize(writer, message, A2AJsonUtilities.JsonContext.Default.Message);
69-
break;
70-
case AgentTask task:
71-
JsonSerializer.Serialize(writer, task, A2AJsonUtilities.JsonContext.Default.AgentTask);
72-
break;
73-
case TaskStatusUpdateEvent taskStatusUpdateEvent:
74-
JsonSerializer.Serialize(writer, taskStatusUpdateEvent, A2AJsonUtilities.JsonContext.Default.TaskStatusUpdateEvent);
75-
break;
76-
case TaskArtifactUpdateEvent taskArtifactUpdateEvent:
77-
JsonSerializer.Serialize(writer, taskArtifactUpdateEvent, A2AJsonUtilities.JsonContext.Default.TaskArtifactUpdateEvent);
78-
break;
79-
default:
80-
throw new JsonException($"Unsupported A2AEvent runtime type {value.GetType().Name}");
81-
}
82-
}
19+
[JsonIgnore]
20+
public string Kind { get; } = kind;
8321
}
8422

8523
/// <summary>
8624
/// A2A response objects.
8725
/// </summary>
88-
[JsonConverter(typeof(A2AResponseConverter))]
89-
public abstract class A2AResponse : A2AEvent;
90-
91-
/// <summary>
92-
/// JSON converter for A2AResponse.
93-
/// </summary>
94-
sealed class A2AResponseConverter : JsonConverter<A2AResponse>
95-
{
96-
private static readonly A2AEventConverter _eventConverter = new();
97-
98-
/// <inheritdoc/>
99-
public override A2AResponse? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
100-
{
101-
// Delegate the deserialization to A2AEventConverter.
102-
var a2aEvent = _eventConverter.Read(ref reader, typeof(A2AEvent), options);
103-
if (a2aEvent is A2AResponse a2aResponse)
104-
{
105-
return a2aResponse;
106-
}
107-
throw new JsonException("JSON did not represent an A2AResponse instance.");
108-
}
109-
110-
/// <inheritdoc/>
111-
public override void Write(Utf8JsonWriter writer, A2AResponse value, JsonSerializerOptions options)
112-
{
113-
JsonSerializer.Serialize(writer, value, A2AJsonUtilities.JsonContext.Default.A2AEvent);
114-
}
115-
}
26+
/// <param name="kind">The event kind discriminator.</param>
27+
[JsonPolymorphic(TypeDiscriminatorPropertyName = "kind")]
28+
[JsonDerivedType(typeof(Message), "message")]
29+
[JsonDerivedType(typeof(AgentTask), "task")]
30+
public abstract class A2AResponse(string kind) : A2AEvent(kind);

src/A2A/Models/AgentTask.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@ namespace A2A;
66
/// <summary>
77
/// Represents a task that can be processed by an agent.
88
/// </summary>
9-
public sealed class AgentTask : A2AResponse
9+
public sealed class AgentTask() : A2AResponse(KindValue)
1010
{
11-
/// <inheritdoc />
12-
[JsonPropertyName("kind")]
13-
public override string Kind => "task";
11+
const string KindValue = "task";
1412

1513
/// <summary>
1614
/// Unique identifier for the task.

src/A2A/Models/Message.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,9 @@ public override void Write(Utf8JsonWriter writer, MessageRole value, JsonSeriali
5252
/// <summary>
5353
/// Represents a single message exchanged between user and agent.
5454
/// </summary>
55-
public sealed class Message : A2AResponse
55+
public sealed class Message() : A2AResponse(KindValue)
5656
{
57-
/// <inheritdoc />
58-
[JsonPropertyName("kind")]
59-
public override string Kind => "message";
57+
const string KindValue = "message";
6058

6159
/// <summary>
6260
/// Message sender's role.

src/A2A/Models/TaskArtifactUpdateEvent.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@ namespace A2A;
55
/// <summary>
66
/// Sent by server during sendStream or subscribe requests.
77
/// </summary>
8-
public sealed class TaskArtifactUpdateEvent : TaskUpdateEvent
8+
public sealed class TaskArtifactUpdateEvent() : TaskUpdateEvent(KindValue)
99
{
10-
/// <inheritdoc />
11-
[JsonPropertyName("kind")]
12-
public override string Kind => "artifact-update";
10+
const string KindValue = "artifact-update";
1311

1412
/// <summary>
1513
/// Generated artifact.

src/A2A/Models/TaskStatusUpdateEvent.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@ namespace A2A;
55
/// <summary>
66
/// Event sent by server during sendStream or subscribe requests.
77
/// </summary>
8-
public sealed class TaskStatusUpdateEvent : TaskUpdateEvent
8+
public sealed class TaskStatusUpdateEvent() : TaskUpdateEvent(KindValue)
99
{
10-
/// <inheritdoc />
11-
[JsonPropertyName("kind")]
12-
public override string Kind => "status-update";
10+
const string KindValue = "status-update";
1311

1412
/// <summary>
1513
/// Gets or sets the current status of the task.

src/A2A/Models/TaskUpdateEvent.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ namespace A2A;
66
/// <summary>
77
/// Base class for task update events.
88
/// </summary>
9-
public abstract class TaskUpdateEvent : A2AEvent
9+
/// <param name="kind">The event kind discriminator.</param>
10+
public abstract class TaskUpdateEvent(string kind) : A2AEvent(kind)
1011
{
1112
/// <summary>
1213
/// Gets or sets the task ID.

tests/A2A.UnitTests/Models/A2AResponseTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ public void A2AEvent_Deserialize_MissingKind_Throws()
387387
""";
388388

389389
// Act / Assert
390-
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<A2AEvent>(json, A2AJsonUtilities.DefaultOptions));
390+
Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<A2AEvent>(json, A2AJsonUtilities.DefaultOptions));
391391
}
392392

393393
[Fact]
@@ -403,7 +403,7 @@ public void A2AResponse_Deserialize_MissingKind_Throws()
403403
""";
404404

405405
// Act / Assert
406-
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<A2AResponse>(json, A2AJsonUtilities.DefaultOptions));
406+
Assert.Throws<NotSupportedException>(() => JsonSerializer.Deserialize<A2AResponse>(json, A2AJsonUtilities.DefaultOptions));
407407
}
408408

409409
[Fact]
@@ -412,8 +412,8 @@ public void A2AEvent_Deserialize_KindNotBeingFirst_Succeeds()
412412
// Arrange
413413
const string json = """
414414
{
415-
"role": "user",
416415
"kind": "message",
416+
"role": "user",
417417
"parts": [ { "kind": "text", "text": "hi" } ],
418418
"messageId": "m-7"
419419
}
@@ -439,8 +439,8 @@ public void A2AResponse_Deserialize_KindNotBeingFirst_Succeeds()
439439
// Arrange
440440
const string json = """
441441
{
442-
"role": "user",
443442
"kind": "message",
443+
"role": "user",
444444
"parts": [ { "kind": "text", "text": "hi" } ],
445445
"messageId": "m-7"
446446
}

0 commit comments

Comments
 (0)