// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
namespace Microsoft.Agents.AI.Workflows.Checkpointing;
///
/// Provides special handling for serialization and deserialization, enabling delayed deserialization
/// of the inner value. This is used to enable serialization/deserialization of objects whose type information is not available
/// at the time of initial deserialization, e.g. user-defined state types.
///
/// This operates in conjuction with and to abstract
/// away the speicfics of a given serialization format in favor of and
/// and related methods.
///
///
internal sealed class PortableValueConverter(JsonMarshaller marshaller) : JsonConverter
{
public override PortableValue? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
SequencePosition initial = reader.Position;
JsonTypeInfo baseTypeInfo = WorkflowsJsonUtilities.JsonContext.Default.PortableValue;
PortableValue? maybeValue = JsonSerializer.Deserialize(ref reader, baseTypeInfo);
if (maybeValue is null)
{
throw new JsonException($"Could not deserialize a PortableValue from JSON at position {initial}.");
}
else if (maybeValue.Value is JsonElement element)
{
// This happens when we do not have the type information available to deserialize the value directly.
// We need to wrap it in a JsonWireSerializedValue so that we can deserialize it
return new PortableValue(maybeValue.TypeId, new JsonWireSerializedValue(marshaller, element));
}
else if (maybeValue.TypeId.IsMatch(maybeValue.Value.GetType()))
{
return maybeValue;
}
throw new JsonException($"Deserialized PortableValue contains a value of type {maybeValue.Value.GetType()} which does not match the expected type {maybeValue.TypeId} at position {initial}.");
}
public override void Write(Utf8JsonWriter writer, PortableValue value, JsonSerializerOptions options)
{
PortableValue proxyValue;
if (value.IsDelayedDeserialization && !value.IsDeserialized)
{
if (value.Value is JsonWireSerializedValue jsonWireValue)
{
proxyValue = new(value.TypeId, jsonWireValue.Data);
}
else
{
// Users should never see this unless they're trying to cross wire formats
throw new InvalidOperationException("Cannot serialize a PortableValue that has not been deserialized. Please deserialize it with .As/AsType() or Is/IsType() methods first.");
}
}
else
{
JsonElement element = marshaller.Marshal(value.Value, value.Value.GetType());
proxyValue = new(value.TypeId, element);
}
JsonTypeInfo baseTypeInfo = WorkflowsJsonUtilities.JsonContext.Default.PortableValue;
JsonSerializer.Serialize(writer, proxyValue, baseTypeInfo);
}
}