Fix Primitive Collection serialization
This was broken by both #5871 and #5682: 1. When a List<Guid> was serialized, it was recognized as a primitive collection and thus plainly written to the JSON without any type information: ["d4d8404c-4357-47ff-a343-649a116539f5"] 2. When this JSON was deserialized, due to lack of type info, it was deserialized as List<object>, containing strings. This is already not good. 3. When this List<object> gets serialized again (e.g. due to multiple workflow suspends causing WorkflowState serialization), this time it fails the primitive collection recognition, because object is not a primitive type. It now gets serialized as {"_items": ["d4d8404c-4357-47ff-a343-649a116539f5"], "_type": "Object[]"} 4. When that JSON gets deserialized, it tries to ReadType() but ReadType() fails to parse Object[] since it lacks the logic from TypeJsonConverter to throw away the [] before looking up Object in the WellKnownTypeRegistry, so it returns null as a type. Without a type but being faced with a json object { ... } it now deserializes into an ExpandoObject 5. Any further serialization / deserializations will now cause the expando object to get nested deeper and deeper every time.
This commit is contained in:
parent
97b59974d9
commit
c4a6ce2796
|
|
@ -31,7 +31,7 @@ public class PolymorphicObjectConverter(IWellKnownTypeRegistry wellKnownTypeRegi
|
|||
if (reader.TokenType != JsonTokenType.StartObject && reader.TokenType != JsonTokenType.StartArray)
|
||||
return ReadPrimitive(ref reader, newOptions);
|
||||
|
||||
var targetType = ReadType(reader);
|
||||
var targetType = ReadType(reader, options);
|
||||
if (targetType == null)
|
||||
return ReadObject(ref reader, newOptions);
|
||||
|
||||
|
|
@ -183,15 +183,7 @@ public class PolymorphicObjectConverter(IWellKnownTypeRegistry wellKnownTypeRegi
|
|||
|| valueType.IsEnum;
|
||||
}
|
||||
|
||||
bool IsListOfPrimitives(Type valueType)
|
||||
{
|
||||
var isEnumerable = typeof(IEnumerable).IsAssignableFrom(valueType) && valueType.IsGenericType && valueType.GetGenericArguments().Length == 1;
|
||||
if (!isEnumerable) return false;
|
||||
var elementType = valueType.GetGenericArguments()[0];
|
||||
return IsPrimitive(elementType);
|
||||
}
|
||||
|
||||
if (IsPrimitive(type) || IsListOfPrimitives(type))
|
||||
if (IsPrimitive(type))
|
||||
{
|
||||
// Remove the converter so that we don't end up in an infinite loop.
|
||||
newOptions.Converters.RemoveWhere(x => x is PolymorphicObjectConverterFactory);
|
||||
|
|
@ -207,8 +199,8 @@ public class PolymorphicObjectConverter(IWellKnownTypeRegistry wellKnownTypeRegi
|
|||
if (type == typeof(JObject) || type == typeof(JArray) || type == typeof(JsonObject) || type == typeof(JsonArray))
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString(IslandPropertyName, value.ToString());
|
||||
writer.WriteString(TypePropertyName, type.GetSimpleAssemblyQualifiedName());
|
||||
writer.WriteString(IslandPropertyName, value.ToString());
|
||||
writer.WriteEndObject();
|
||||
return;
|
||||
}
|
||||
|
|
@ -253,20 +245,6 @@ public class PolymorphicObjectConverter(IWellKnownTypeRegistry wellKnownTypeRegi
|
|||
|
||||
writer.WriteStartObject();
|
||||
|
||||
if (jsonElement.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
writer.WritePropertyName(ItemsPropertyName);
|
||||
jsonElement.WriteTo(writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var property in jsonElement.EnumerateObject().Where(property => !property.NameEquals(TypePropertyName)))
|
||||
{
|
||||
writer.WritePropertyName(property.Name);
|
||||
property.Value.WriteTo(writer);
|
||||
}
|
||||
}
|
||||
|
||||
if (type != typeof(ExpandoObject))
|
||||
{
|
||||
if (shouldWriteTypeField)
|
||||
|
|
@ -283,10 +261,24 @@ public class PolymorphicObjectConverter(IWellKnownTypeRegistry wellKnownTypeRegi
|
|||
}
|
||||
}
|
||||
|
||||
if (jsonElement.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
writer.WritePropertyName(ItemsPropertyName);
|
||||
jsonElement.WriteTo(writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var property in jsonElement.EnumerateObject().Where(property => !property.NameEquals(TypePropertyName)))
|
||||
{
|
||||
writer.WritePropertyName(property.Name);
|
||||
property.Value.WriteTo(writer);
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
private Type? ReadType(Utf8JsonReader reader)
|
||||
private Type? ReadType(Utf8JsonReader reader, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType != JsonTokenType.StartObject)
|
||||
return null;
|
||||
|
|
@ -301,7 +293,14 @@ public class PolymorphicObjectConverter(IWellKnownTypeRegistry wellKnownTypeRegi
|
|||
if (reader.TokenType == JsonTokenType.PropertyName && reader.ValueTextEquals(TypePropertyName))
|
||||
{
|
||||
reader.Read(); // Move to the value of the _type property
|
||||
typeName = reader.GetString();
|
||||
if (options.Converters.OfType<TypeJsonConverter>().FirstOrDefault() is { } typeJsonConverter)
|
||||
{
|
||||
return typeJsonConverter.Read(ref reader, typeof(Type), options);
|
||||
}
|
||||
else
|
||||
{
|
||||
typeName = reader.GetString();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -331,7 +330,7 @@ public class PolymorphicObjectConverter(IWellKnownTypeRegistry wellKnownTypeRegi
|
|||
}
|
||||
|
||||
// If we found the _type property, attempt to resolve the type.
|
||||
var targetType = typeName != null ? wellKnownTypeRegistry.TryGetType(typeName, out var type) ? type : Type.GetType(typeName) : default;
|
||||
var targetType = typeName != null ? Type.GetType(typeName) : default;
|
||||
return targetType;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,23 @@ public class SerializationTests(ITestOutputHelper testOutputHelper)
|
|||
Assert.Equal(typeof(List<TestObject>), result.GetType());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RoundtripPrimitiveCollections()
|
||||
{
|
||||
var dict = new Dictionary<string, object>
|
||||
{
|
||||
{ "Content", new List<Guid>
|
||||
{
|
||||
Guid.NewGuid()
|
||||
}
|
||||
}
|
||||
};
|
||||
var jsonSerialized = SerializeUsingPayloadSerializer(dict);
|
||||
var transformationModel = DeSerializeDictionaryUsingPayloadSerializer(jsonSerialized);
|
||||
var result = transformationModel["Content"];
|
||||
Assert.Equal(typeof(List<Guid>), result.GetType());
|
||||
}
|
||||
|
||||
private string SerializeUsingPayloadSerializer(object obj)
|
||||
{
|
||||
var payloadSerializer = _services.GetRequiredService<IPayloadSerializer>();
|
||||
|
|
|
|||
Loading…
Reference in a new issue