diff --git a/release_notes.md b/release_notes.md
index a39623b2f..75b79eeaa 100644
--- a/release_notes.md
+++ b/release_notes.md
@@ -18,6 +18,8 @@
### Bug Fixes
+- Support Polymorphic input payload deserialization (https://github.com/Azure/azure-functions-durable-extension/pull/3250)
+
### Breaking Changes
### Dependency Updates
diff --git a/src/Worker.Extensions.DurableTask/ObjectConverterShim.cs b/src/Worker.Extensions.DurableTask/ObjectConverterShim.cs
index 9f8abd884..f9b070b9a 100644
--- a/src/Worker.Extensions.DurableTask/ObjectConverterShim.cs
+++ b/src/Worker.Extensions.DurableTask/ObjectConverterShim.cs
@@ -3,7 +3,9 @@
using System;
using System.IO;
+using System.Reflection;
using System.Text;
+using System.Text.Json.Serialization;
using Azure.Core.Serialization;
using Microsoft.DurableTask;
@@ -39,7 +41,69 @@ public ObjectConverterShim(ObjectSerializer serializer)
return null;
}
- BinaryData data = this.serializer.Serialize(value, value.GetType(), default);
+ Type valueType = value.GetType();
+
+ // Special handling for object[] arrays - DTFx wraps activity inputs in object[]
+ // When System.Text.Json serializes object[], it treats elements as type 'object' (the static array element type),
+ // not as their runtime concrete type, which prevents polymorphic type discriminators from being added.
+ // We must serialize each element individually with its polymorphic base type and build the JSON array manually.
+ if (valueType == typeof(object[]))
+ {
+ object[] array = (object[])value;
+ var jsonBuilder = new StringBuilder();
+ jsonBuilder.Append('[');
+
+ for (int i = 0; i < array.Length; i++)
+ {
+ if (i > 0)
+ {
+ jsonBuilder.Append(',');
+ }
+
+ object? element = array[i];
+ if (element != null)
+ {
+ // Serialize each element with its polymorphic base type to include type discriminators
+ Type elementType = element.GetType();
+ Type serializeAs = GetPolymorphicBaseType(elementType) ?? elementType;
+
+ BinaryData elementData = this.serializer.Serialize(element, serializeAs, default);
+ jsonBuilder.Append(elementData.ToString());
+ }
+ else
+ {
+ jsonBuilder.Append("null");
+ }
+ }
+
+ jsonBuilder.Append(']');
+ return jsonBuilder.ToString();
+ }
+
+ // For non-array values (or non-object[] arrays), serialize as polymorphic base type
+ Type typeToSerialize = GetPolymorphicBaseType(valueType) ?? valueType;
+
+ BinaryData data = this.serializer.Serialize(value, typeToSerialize, default);
return data.ToString();
}
+
+ ///
+ /// Finds the polymorphic base type for a given type by walking up the inheritance chain.
+ /// Returns the first base type decorated with .
+ ///
+ /// The concrete type to check.
+ /// The polymorphic base type if found, otherwise null.
+ private static Type? GetPolymorphicBaseType(Type concreteType)
+ {
+ Type? current = concreteType.BaseType;
+ while (current != null && current != typeof(object))
+ {
+ if (current.GetCustomAttribute() != null)
+ {
+ return current;
+ }
+ current = current.BaseType;
+ }
+ return null;
+ }
}
diff --git a/test/Worker.Extensions.DurableTask.Tests/ObjectConverterShimTests.cs b/test/Worker.Extensions.DurableTask.Tests/ObjectConverterShimTests.cs
new file mode 100644
index 000000000..925df65ae
--- /dev/null
+++ b/test/Worker.Extensions.DurableTask.Tests/ObjectConverterShimTests.cs
@@ -0,0 +1,527 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Azure.Core.Serialization;
+using Microsoft.Azure.Functions.Worker.Extensions.DurableTask;
+using Xunit;
+
+namespace Microsoft.Azure.Functions.Worker.Tests;
+
+///
+/// Unit tests for .
+/// Tests polymorphic serialization/deserialization capabilities.
+///
+public class ObjectConverterShimTests
+{
+ #region Test Model Classes
+
+ [JsonPolymorphic(TypeDiscriminatorPropertyName = "__type")]
+ [JsonDerivedType(typeof(Dog), "Dog")]
+ [JsonDerivedType(typeof(Cat), "Cat")]
+ [JsonDerivedType(typeof(Bird), "Bird")]
+ public class Animal
+ {
+ public string? Name { get; set; }
+ }
+
+ public class Dog : Animal
+ {
+ public string? Breed { get; set; }
+ }
+
+ public class Cat : Animal
+ {
+ public int Lives { get; set; }
+ }
+
+ public class Bird : Animal
+ {
+ public double WingSpan { get; set; }
+ }
+
+ public class SimpleClass
+ {
+ public string? Value { get; set; }
+ }
+
+ #endregion
+
+ private ObjectConverterShim CreateShim()
+ {
+ var jsonOptions = new JsonSerializerOptions
+ {
+ WriteIndented = false,
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ DefaultIgnoreCondition = JsonIgnoreCondition.Never,
+ TypeInfoResolver = new System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver()
+ };
+ var serializer = new JsonObjectSerializer(jsonOptions);
+ return new ObjectConverterShim(serializer);
+ }
+
+ #region Serialization Tests
+
+ [Fact]
+ public void Serialize_SimpleString_ReturnsJsonString()
+ {
+ // Arrange
+ var shim = CreateShim();
+ var value = "Hello World";
+
+ // Act
+ string? result = shim.Serialize(value);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal("\"Hello World\"", result);
+ }
+
+ [Fact]
+ public void Serialize_Null_ReturnsNull()
+ {
+ // Arrange
+ var shim = CreateShim();
+
+ // Act
+ string? result = shim.Serialize(null);
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public void Serialize_SimpleObject_ReturnsJson()
+ {
+ // Arrange
+ var shim = CreateShim();
+ var obj = new SimpleClass { Value = "test" };
+
+ // Act
+ string? result = shim.Serialize(obj);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Contains("\"value\":\"test\"", result);
+ }
+
+ [Fact]
+ public void Serialize_PolymorphicType_IncludesTypeDiscriminator()
+ {
+ // Arrange
+ var shim = CreateShim();
+ var dog = new Dog { Name = "Buddy", Breed = "Golden Retriever" };
+
+ // Act
+ string? result = shim.Serialize(dog);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Contains("\"__type\":\"Dog\"", result);
+ Assert.Contains("\"name\":\"Buddy\"", result);
+ Assert.Contains("\"breed\":\"Golden Retriever\"", result);
+ }
+
+ [Fact]
+ public void Serialize_PolymorphicCat_IncludesTypeDiscriminator()
+ {
+ // Arrange
+ var shim = CreateShim();
+ var cat = new Cat { Name = "Whiskers", Lives = 9 };
+
+ // Act
+ string? result = shim.Serialize(cat);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Contains("\"__type\":\"Cat\"", result);
+ Assert.Contains("\"name\":\"Whiskers\"", result);
+ Assert.Contains("\"lives\":9", result);
+ }
+
+ [Fact]
+ public void Serialize_ObjectArray_WithPolymorphicElements_IncludesTypeDiscriminators()
+ {
+ // Arrange
+ var shim = CreateShim();
+ object[] animals = new object[]
+ {
+ new Dog { Name = "Rex", Breed = "Beagle" },
+ new Cat { Name = "Mittens", Lives = 7 },
+ new Bird { Name = "Tweety", WingSpan = 0.3 }
+ };
+
+ // Act
+ string? result = shim.Serialize(animals);
+
+ // Assert
+ Assert.NotNull(result);
+
+ // Should be a JSON array
+ Assert.StartsWith("[", result);
+ Assert.EndsWith("]", result);
+
+ // Should contain type discriminators for all elements
+ Assert.Contains("\"__type\":\"Dog\"", result);
+ Assert.Contains("\"__type\":\"Cat\"", result);
+ Assert.Contains("\"__type\":\"Bird\"", result);
+
+ // Should contain all properties
+ Assert.Contains("\"name\":\"Rex\"", result);
+ Assert.Contains("\"breed\":\"Beagle\"", result);
+ Assert.Contains("\"name\":\"Mittens\"", result);
+ Assert.Contains("\"lives\":7", result);
+ Assert.Contains("\"name\":\"Tweety\"", result);
+ Assert.Contains("\"wingSpan\":0.3", result);
+ }
+
+ [Fact]
+ public void Serialize_ObjectArray_WithNull_HandlesNullElements()
+ {
+ // Arrange
+ var shim = CreateShim();
+ object?[] array = new object?[]
+ {
+ new Dog { Name = "Max", Breed = "Poodle" },
+ null,
+ new Cat { Name = "Shadow", Lives = 8 }
+ };
+
+ // Act
+ string? result = shim.Serialize(array);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Contains("null", result);
+ Assert.Contains("\"__type\":\"Dog\"", result);
+ Assert.Contains("\"__type\":\"Cat\"", result);
+ }
+
+ [Fact]
+ public void Serialize_ObjectArray_WithMixedTypes_SerializesAll()
+ {
+ // Arrange
+ var shim = CreateShim();
+ object[] mixed = new object[]
+ {
+ new Dog { Name = "Fido", Breed = "Labrador" },
+ "plain string",
+ 42,
+ new SimpleClass { Value = "simple" }
+ };
+
+ // Act
+ string? result = shim.Serialize(mixed);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Contains("\"__type\":\"Dog\"", result);
+ Assert.Contains("\"plain string\"", result);
+ Assert.Contains("42", result);
+ Assert.Contains("\"value\":\"simple\"", result);
+ }
+
+ [Fact]
+ public void Serialize_EmptyObjectArray_ReturnsEmptyJsonArray()
+ {
+ // Arrange
+ var shim = CreateShim();
+ object[] empty = Array.Empty