Files
agent-framework/dotnet/src/Microsoft.Agents.AI.Workflows/Checkpointing/JsonMarshaller.cs
T
Jacob Alber e489ac0fa3 .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
2026-02-10 15:50:57 +00:00

78 lines
2.9 KiB
C#

// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
namespace Microsoft.Agents.AI.Workflows.Checkpointing;
internal sealed class JsonMarshaller : IWireMarshaller<JsonElement>
{
private readonly JsonSerializerOptions _internalOptions;
private readonly JsonSerializerOptions? _externalOptions;
public JsonMarshaller(JsonSerializerOptions? serializerOptions = null)
{
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());
this._internalOptions.Converters.Add(new EdgeIdConverter());
this._internalOptions.Converters.Add(new CheckpointInfoConverter());
this._externalOptions = serializerOptions;
}
private JsonTypeInfo LookupTypeInfo(Type type)
{
if (!this._internalOptions.TryGetTypeInfo(type, out JsonTypeInfo? typeInfo))
{
if (this._externalOptions is null ||
!this._externalOptions.TryGetTypeInfo(type, out typeInfo))
{
throw new InvalidOperationException($"No JSON type info is available for type '{type}'.");
}
}
return typeInfo;
}
public JsonElement Marshal(object value, Type type)
=> JsonSerializer.SerializeToElement(value, this.LookupTypeInfo(type));
public JsonElement Marshal<TValue>(TValue value)
=> JsonSerializer.SerializeToElement(value, this.LookupTypeInfo(typeof(TValue)));
public TValue Marshal<TValue>(JsonElement data)
{
object value = data.Deserialize(this.LookupTypeInfo(typeof(TValue))) ??
throw new InvalidOperationException($"Could not deserialize the value as the expected type {typeof(TValue)}.");
if (value is TValue typedValue)
{
return typedValue;
}
throw new InvalidOperationException($"Deserialized value is not of the expected type {typeof(TValue)}.");
}
public object Marshal(Type targetType, JsonElement data)
{
object value = data.Deserialize(this.LookupTypeInfo(targetType)) ??
throw new InvalidOperationException($"Could not deserialize the value as the expected type {targetType}.");
if (targetType.IsInstanceOfType(value))
{
return value;
}
throw new InvalidOperationException($"Deserialized value is not of the expected type {targetType}.");
}
}