// 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 { 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 value) => JsonSerializer.SerializeToElement(value, this.LookupTypeInfo(typeof(TValue))); public TValue Marshal(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}."); } }