mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: Fix: Checkpoint Deserialization breaks when JSON metadata properties are out of order (#3442)
* Fix checkpoint JSON deserialization with out-of-order metadata properties (#2962) * Simplify: propagate AllowOutOfOrderMetadataProperties from incoming JsonSerializerOptions
This commit is contained in:
committed by
GitHub
Unverified
parent
6c37ce8450
commit
e489ac0fa3
@@ -13,7 +13,13 @@ internal sealed class JsonMarshaller : IWireMarshaller<JsonElement>
|
||||
|
||||
public JsonMarshaller(JsonSerializerOptions? serializerOptions = null)
|
||||
{
|
||||
this._internalOptions = new JsonSerializerOptions(WorkflowsJsonUtilities.DefaultOptions);
|
||||
this._internalOptions = new JsonSerializerOptions(WorkflowsJsonUtilities.DefaultOptions)
|
||||
{
|
||||
// Propagate from the user-provided options if set; enables support for databases
|
||||
// like PostgreSQL jsonb that do not preserve property order.
|
||||
AllowOutOfOrderMetadataProperties = serializerOptions?.AllowOutOfOrderMetadataProperties is true,
|
||||
};
|
||||
|
||||
this._internalOptions.Converters.Add(new PortableValueConverter(this));
|
||||
this._internalOptions.Converters.Add(new ExecutorIdentityConverter());
|
||||
this._internalOptions.Converters.Add(new ScopeKeyConverter());
|
||||
|
||||
@@ -672,4 +672,104 @@ public class JsonSerializationTests
|
||||
|
||||
ValidateCheckpoint(retrievedCheckpoint, prototype);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the default behavior (without AllowOutOfOrderMetadataProperties) fails
|
||||
/// when $type metadata is not the first property, demonstrating the PostgreSQL jsonb issue.
|
||||
/// See: https://github.com/microsoft/agent-framework/issues/2962
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Test_OutOfOrderMetadataProperties_WithoutOption_Fails()
|
||||
{
|
||||
// Arrange
|
||||
JsonMarshaller marshaller = new();
|
||||
EdgeInfo edgeInfo = TestEdgeInfo_DirectNoCondition;
|
||||
|
||||
// Serialize to JSON
|
||||
JsonElement serialized = marshaller.Marshal(edgeInfo);
|
||||
string json = serialized.GetRawText();
|
||||
|
||||
// Simulate PostgreSQL jsonb behavior: reorder properties so $type is not first
|
||||
string reorderedJson = ReorderJsonPropertiesToMoveTypeDiscriminatorLast(json);
|
||||
|
||||
// Act & Assert - Without the option, deserialization should fail
|
||||
JsonElement reorderedElement = JsonDocument.Parse(reorderedJson).RootElement;
|
||||
Action act = () => marshaller.Marshal<EdgeInfo>(reorderedElement);
|
||||
|
||||
act.Should().Throw<JsonException>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simulates PostgreSQL jsonb behavior where property order is not preserved,
|
||||
/// causing $type metadata to not be the first property.
|
||||
/// This test verifies that deserialization works when AllowOutOfOrderMetadataProperties is enabled.
|
||||
/// See: https://github.com/microsoft/agent-framework/issues/2962
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Test_OutOfOrderMetadataProperties_WithOptionEnabled_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
EdgeInfo edgeInfo = TestEdgeInfo_DirectNoCondition;
|
||||
|
||||
// Serialize to JSON using standard marshaller
|
||||
JsonMarshaller marshaller = new();
|
||||
JsonElement serialized = marshaller.Marshal(edgeInfo);
|
||||
string json = serialized.GetRawText();
|
||||
|
||||
// Simulate PostgreSQL jsonb behavior: reorder properties so $type is not first
|
||||
string reorderedJson = ReorderJsonPropertiesToMoveTypeDiscriminatorLast(json);
|
||||
JsonElement reorderedElement = JsonDocument.Parse(reorderedJson).RootElement;
|
||||
|
||||
// Act - Deserialize with AllowOutOfOrderMetadataProperties enabled via JsonSerializerOptions
|
||||
JsonSerializerOptions options = new() { AllowOutOfOrderMetadataProperties = true };
|
||||
JsonMarshaller marshallerWithOption = new(options);
|
||||
EdgeInfo deserialized = marshallerWithOption.Marshal<EdgeInfo>(reorderedElement);
|
||||
|
||||
// Assert
|
||||
deserialized.Should().Match(edgeInfo.CreatePolyValidator());
|
||||
}
|
||||
|
||||
private static string ReorderJsonPropertiesToMoveTypeDiscriminatorLast(string json)
|
||||
{
|
||||
// Parse JSON, extract $type, rebuild with $type at end
|
||||
using JsonDocument doc = JsonDocument.Parse(json);
|
||||
JsonElement root = doc.RootElement;
|
||||
|
||||
Dictionary<string, JsonElement> properties = [];
|
||||
JsonElement? typeValue = null;
|
||||
|
||||
foreach (JsonProperty prop in root.EnumerateObject())
|
||||
{
|
||||
if (prop.Name == "$type")
|
||||
{
|
||||
typeValue = prop.Value.Clone();
|
||||
}
|
||||
else
|
||||
{
|
||||
properties[prop.Name] = prop.Value.Clone();
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild JSON with $type last
|
||||
using System.IO.MemoryStream ms = new();
|
||||
using (Utf8JsonWriter writer = new(ms))
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
foreach (KeyValuePair<string, JsonElement> kvp in properties)
|
||||
{
|
||||
writer.WritePropertyName(kvp.Key);
|
||||
kvp.Value.WriteTo(writer);
|
||||
}
|
||||
|
||||
if (typeValue.HasValue)
|
||||
{
|
||||
writer.WritePropertyName("$type");
|
||||
typeValue.Value.WriteTo(writer);
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
return System.Text.Encoding.UTF8.GetString(ms.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user