Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ internal static class DataSerializationHelper
#if NETFRAMEWORK
DataContractSurrogate = SerializationSurrogateProvider.Instance,
#endif
KnownTypes = [typeof(SurrogatedDateOnly), typeof(SurrogatedTimeOnly)],
KnownTypes = [typeof(SurrogatedDateOnly), typeof(SurrogatedTimeOnly), typeof(SurrogatedSystemType)],
};

/// <summary>
Expand Down Expand Up @@ -50,7 +50,22 @@ internal static class DataSerializationHelper
continue;
}

Type type = data[i]!.GetType();
object valueToSerialize = data[i]!;
Type type = valueToSerialize.GetType();

if (valueToSerialize is Type serializableType)
{
string assemblyQualifiedName = serializableType.AssemblyQualifiedName
?? throw new SerializationException($"Cannot serialize '{serializableType}' because it does not have an assembly-qualified name.");

valueToSerialize = new SurrogatedSystemType
{
AssemblyQualifiedName = assemblyQualifiedName,
};

type = typeof(SurrogatedSystemType);
}

string? typeName = type.AssemblyQualifiedName;

serializedData[typeIndex] = typeName;
Expand All @@ -66,7 +81,7 @@ internal static class DataSerializationHelper
// Not the best solution, maybe we can replace this with System.Text.Json, but the we need one generator calling the other.
#pragma warning disable IL3050 // IL3050: Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as Native AOT
#pragma warning disable IL2026 // IL2026: Members attributed with RequiresUnreferencedCode may break when trimming
serializer.WriteObject(memoryStream, data[i]);
serializer.WriteObject(memoryStream, valueToSerialize);
#pragma warning restore IL3050 // IL3050: Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as Native AOT
#pragma warning restore IL2026 // IL2026: Members attributed with RequiresUnreferencedCode may break when trimming
byte[] serializerData = memoryStream.ToArray();
Expand Down Expand Up @@ -135,7 +150,7 @@ private static DataContractJsonSerializer GetSerializer(string assemblyQualified
// Not the best solution, maybe we can replace this with System.Text.Json, but the we need one generator calling the other.
#pragma warning disable IL3050 // IL3050: Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as Native AOT
#pragma warning disable IL2026 // IL2026: Members attributed with RequiresUnreferencedCode may break when trimming
_ => new DataContractJsonSerializer(PlatformServiceProvider.Instance.ReflectionOperations.GetType(assemblyQualifiedName) ?? typeof(object), SerializerSettings));
_ => new DataContractJsonSerializer(GetSerializationType(assemblyQualifiedName), SerializerSettings));
#pragma warning restore IL3050 // IL3050: Avoid calling members annotated with 'RequiresDynamicCodeAttribute' when publishing as Native AOT
#pragma warning restore IL2026 // IL2026: Members attributed with RequiresUnreferencedCode may break when trimming

Expand All @@ -149,6 +164,19 @@ private static DataContractJsonSerializer GetSerializer(Type type)
#pragma warning disable IL2026 // IL2026: Members attributed with RequiresUnreferencedCode may break when trimming
_ => new DataContractJsonSerializer(type, SerializerSettings));

private static Type GetSerializationType(string assemblyQualifiedName)
{
Type? serializedType = PlatformServiceProvider.Instance.ReflectionOperations.GetType(assemblyQualifiedName);
return serializedType
?? (assemblyQualifiedName.StartsWith(typeof(SurrogatedSystemType).FullName + ",", StringComparison.Ordinal)
? typeof(SurrogatedSystemType)
: assemblyQualifiedName.StartsWith(typeof(SurrogatedDateOnly).FullName + ",", StringComparison.Ordinal)
? typeof(SurrogatedDateOnly)
: assemblyQualifiedName.StartsWith(typeof(SurrogatedTimeOnly).FullName + ",", StringComparison.Ordinal)
? typeof(SurrogatedTimeOnly)
: typeof(object));
}

[DataContract]
private sealed class SurrogatedDateOnly
{
Expand All @@ -163,6 +191,13 @@ private sealed class SurrogatedTimeOnly
public long Ticks { get; set; }
}

[DataContract]
private sealed class SurrogatedSystemType
{
[DataMember]
public string AssemblyQualifiedName { get; set; } = null!;
}

private sealed class SerializationSurrogateProvider
#if NETFRAMEWORK
: IDataContractSurrogate
Expand Down Expand Up @@ -201,8 +236,10 @@ internal static object GetDeserializedObject(object obj)
return new TimeOnly(surrogatedTimeOnly.Ticks);
}
#endif

return obj;
return obj is SurrogatedSystemType surrogatedSystemType
? PlatformServiceProvider.Instance.ReflectionOperations.GetType(surrogatedSystemType.AssemblyQualifiedName)
?? throw new SerializationException($"Cannot deserialize type '{surrogatedSystemType.AssemblyQualifiedName}'.")
: obj;
}

public object GetObjectToSerialize(object obj, Type targetType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,9 @@ public async Task DataRowsShouldHandleNonSerializableValues()
// Assert
VerifyE2E.TestsDiscovered(
testCases,
"DataRowNonSerializable");
"DataRowNonSerializable (System.String)",
"DataRowNonSerializable (System.Int32)",
"DataRowNonSerializable (DataRowTestProject.DataRowTests_DerivedClass)");

VerifyE2E.TestsPassed(
testResults,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,33 @@ public void DataSerializerShouldRoundTripTimeOnly()
actual[0]!.Equals(source).Should().BeTrue();
}
#endif

public void DataSerializerShouldRoundTripSystemType()
{
Type[] source = [typeof(Console), typeof(DataSerializationHelperTests), typeof(DataSerializationHelper)];

foreach (Type type in source)
{
object?[]? actual = DataSerializationHelper.Deserialize(DataSerializationHelper.Serialize([type]));

actual!.Length.Should().Be(1);
actual[0].Should().BeAssignableTo<Type>();
((Type)actual[0]!).Should().Be(type);
}
}

public void DataSerializerShouldRoundTripMixedPayloadIncludingSystemType()
{
object?[] source = [typeof(string), 42, "hello", null, new DateTime(638450000000000000)];

object?[]? actual = DataSerializationHelper.Deserialize(DataSerializationHelper.Serialize(source));

actual.Should().NotBeNull();
actual!.Length.Should().Be(source.Length);
actual[0].Should().Be(typeof(string));
actual[1].Should().Be(42);
actual[2].Should().Be("hello");
actual[3].Should().BeNull();
actual[4].Should().Be(source[4]);
}
}
Loading