From ecea9067e5a297c2c89e0b406b47c8d78a9c4ba7 Mon Sep 17 00:00:00 2001 From: Daichi Isami Date: Mon, 31 Aug 2026 02:49:31 -0700 Subject: [PATCH] Fix JsonWireSerializedValue hash code contract Use JsonValueKind for a stable hash when structurally equal JSON values are compared, and add a regression test for separately parsed equivalent values. --- .../Checkpointing/JsonWireSerializedValue.cs | 5 ++++- .../JsonSerializationTests.cs | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/Checkpointing/JsonWireSerializedValue.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/Checkpointing/JsonWireSerializedValue.cs index 6b97c8c3e2d..83b22363ea5 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows/Checkpointing/JsonWireSerializedValue.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows/Checkpointing/JsonWireSerializedValue.cs @@ -58,6 +58,9 @@ public override bool Equals(object? obj) public override int GetHashCode() { - return this.Data.GetHashCode(); + // JsonElement does not provide a structural hash code. ValueKind is necessarily equal + // whenever DeepEquals returns true, preserving the equality contract without recursively + // reimplementing JsonElement.DeepEquals semantics. + return this.Data.ValueKind.GetHashCode(); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/JsonSerializationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/JsonSerializationTests.cs index 8d053334f91..c72db5be890 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/JsonSerializationTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/JsonSerializationTests.cs @@ -34,6 +34,24 @@ private static JsonSerializerOptions TestCustomSerializedJsonOptions private static EdgeId TakeEdgeId() => new(Interlocked.Increment(ref s_nextEdgeId)); + [Fact] + public void Test_JsonWireSerializedValue_EqualValuesHaveEqualHashCodes() + { + // Arrange + JsonMarshaller marshaller = new(); + using JsonDocument firstDocument = JsonDocument.Parse("""{"a":1,"b":[true,null]}"""); + using JsonDocument secondDocument = JsonDocument.Parse("""{"a":1,"b":[true,null]}"""); + JsonWireSerializedValue first = new(marshaller, firstDocument.RootElement); + JsonWireSerializedValue second = new(marshaller, secondDocument.RootElement); + + // Act + bool areEqual = first.Equals(second); + + // Assert + areEqual.Should().BeTrue(); + first.GetHashCode().Should().Be(second.GetHashCode()); + } + internal static T RunJsonRoundtrip(T value, JsonSerializerOptions? externalOptions = null, Expression>? predicate = null) { JsonMarshaller marshaller = new(externalOptions);