Fix JSON serialization issues
This commit is contained in:
parent
8ba344f95a
commit
8289b03f79
|
|
@ -22,7 +22,7 @@ public record ObjectConverterOptions(JsonSerializerOptions? SerializerOptions =
|
|||
public static class ObjectConverter
|
||||
{
|
||||
public static Result TryConvertTo<T>(this object? value, ObjectConverterOptions? serializerOptions = null) => value.TryConvertTo(typeof(T), serializerOptions);
|
||||
|
||||
|
||||
public static Result TryConvertTo(this object? value, Type targetType, ObjectConverterOptions? serializerOptions = null)
|
||||
{
|
||||
try
|
||||
|
|
@ -64,10 +64,10 @@ public static class ObjectConverter
|
|||
{
|
||||
if (jsonObject.ValueKind == JsonValueKind.String && underlyingTargetType != typeof(string))
|
||||
return jsonObject.GetString().ConvertTo(underlyingTargetType);
|
||||
|
||||
|
||||
return jsonObject.Deserialize(targetType, options);
|
||||
}
|
||||
|
||||
|
||||
if (underlyingSourceType == typeof(string) && !underlyingTargetType.IsPrimitive && underlyingTargetType != typeof(object))
|
||||
{
|
||||
var stringValue = (string)value;
|
||||
|
|
@ -75,7 +75,7 @@ public static class ObjectConverter
|
|||
try
|
||||
{
|
||||
var firstChar = stringValue.TrimStart().FirstOrDefault();
|
||||
|
||||
|
||||
if (firstChar is '{' or '[')
|
||||
return JsonSerializer.Deserialize(stringValue, underlyingTargetType, options);
|
||||
}
|
||||
|
|
@ -84,7 +84,7 @@ public static class ObjectConverter
|
|||
throw new TypeConversionException($"Failed to deserialize {stringValue} to {underlyingTargetType}", value, underlyingTargetType, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (targetType == typeof(object))
|
||||
return value;
|
||||
|
||||
|
|
@ -94,15 +94,18 @@ public static class ObjectConverter
|
|||
if (underlyingSourceType == underlyingTargetType)
|
||||
return value;
|
||||
|
||||
if (underlyingSourceType == typeof(ExpandoObject) && underlyingTargetType.IsClass)
|
||||
if (typeof(IDictionary<string, object>).IsAssignableFrom(underlyingSourceType) && underlyingTargetType.IsClass)
|
||||
{
|
||||
if (value is IDictionary<string, object> dictionary && typeof(IDictionary<string, object>).IsAssignableFrom(underlyingTargetType))
|
||||
return new Dictionary<string, object>(dictionary);
|
||||
if (typeof(ExpandoObject) == underlyingTargetType)
|
||||
{
|
||||
var expandoJson = JsonSerializer.Serialize(value);
|
||||
return ConvertTo(expandoJson, underlyingTargetType, converterOptions);
|
||||
}
|
||||
|
||||
var expandoJson = JsonSerializer.Serialize(value);
|
||||
return ConvertTo(expandoJson, underlyingTargetType, converterOptions);
|
||||
if (typeof(IDictionary<string, object>).IsAssignableFrom(underlyingTargetType))
|
||||
return new Dictionary<string, object>((IDictionary<string, object>)value);
|
||||
}
|
||||
|
||||
|
||||
var targetTypeConverter = TypeDescriptor.GetConverter(underlyingTargetType);
|
||||
|
||||
if (targetTypeConverter.CanConvertFrom(underlyingSourceType))
|
||||
|
|
@ -126,16 +129,16 @@ public static class ObjectConverter
|
|||
if (underlyingSourceType == typeof(double))
|
||||
return Enum.ToObject(underlyingTargetType, Convert.ChangeType(value, typeof(int)));
|
||||
}
|
||||
|
||||
|
||||
if (value is string s)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(s))
|
||||
if (string.IsNullOrWhiteSpace(s))
|
||||
return null;
|
||||
|
||||
if(underlyingTargetType == typeof(Type))
|
||||
|
||||
if (underlyingTargetType == typeof(Type))
|
||||
return converterOptions?.WellKnownTypeRegistry != null ? converterOptions.WellKnownTypeRegistry.GetTypeOrDefault(s) : Type.GetType(s);
|
||||
}
|
||||
|
||||
|
||||
if (value is IEnumerable enumerable)
|
||||
{
|
||||
if (underlyingTargetType is { IsGenericType: true })
|
||||
|
|
@ -147,7 +150,7 @@ public static class ObjectConverter
|
|||
{
|
||||
var collectionType = typeof(List<>).MakeGenericType(desiredCollectionItemType);
|
||||
var collection = (IList)Activator.CreateInstance(collectionType)!;
|
||||
|
||||
|
||||
foreach (var item in enumerable)
|
||||
{
|
||||
var convertedItem = ConvertTo(item, desiredCollectionItemType);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace Elsa.MassTransit.Messages;
|
|||
|
||||
public record DispatchResumeWorkflows(
|
||||
string ActivityTypeName,
|
||||
[property: JsonConverter(typeof(PolymorphicConverter))]
|
||||
[property: JsonConverter(typeof(PolymorphicObjectConverterFactory))]
|
||||
object BookmarkPayload,
|
||||
string? CorrelationId,
|
||||
string? WorkflowInstanceId,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ namespace Elsa.MassTransit.Messages;
|
|||
public record DispatchTriggerWorkflows
|
||||
(
|
||||
string ActivityTypeName,
|
||||
[property: JsonConverter(typeof(PolymorphicConverter))]
|
||||
[property: JsonConverter(typeof(PolymorphicObjectConverterFactory))]
|
||||
object BookmarkPayload,
|
||||
string? CorrelationId,
|
||||
string? WorkflowInstanceId,
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Elsa.Extensions;
|
||||
|
||||
namespace Elsa.Workflows.Core.Serialization.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// A converter that stores type information in order to deserialize the object back into the same type.
|
||||
/// </summary>
|
||||
public class PolymorphicConverter : JsonConverter<object>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
|
||||
{
|
||||
var typeName = value.GetType().GetSimpleAssemblyQualifiedName();
|
||||
var newOptions = new JsonSerializerOptions(options);
|
||||
newOptions.Converters.RemoveWhere(x => x is PolymorphicConverter);
|
||||
var wrappedValue = JsonSerializer.SerializeToNode(value, newOptions)!;
|
||||
wrappedValue["$type"] = typeName;
|
||||
wrappedValue.WriteTo(writer);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var newOptions = new JsonSerializerOptions(options);
|
||||
newOptions.Converters.RemoveWhere(x => x is PolymorphicConverter);
|
||||
|
||||
var element = JsonElement.ParseValue(ref reader);
|
||||
var typeName = element.GetProperty("$type").GetString()!;
|
||||
var type = Type.GetType(typeName)!;
|
||||
var value = element.Deserialize(type, newOptions);
|
||||
|
||||
return value!;
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ public class PolymorphicDictionaryConverter : JsonConverter<IDictionary<string,
|
|||
public PolymorphicDictionaryConverter(JsonSerializerOptions options)
|
||||
{
|
||||
var newOptions = new JsonSerializerOptions(options);
|
||||
newOptions.Converters.Insert(0, new SystemObjectWithTypeHandlingConverterFactory());
|
||||
newOptions.Converters.Insert(0, new PolymorphicObjectConverterFactory());
|
||||
_objectConverter = (JsonConverter<object>)newOptions.GetConverter(typeof(object));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ namespace Elsa.Workflows.Core.Serialization.Converters;
|
|||
/// <summary>
|
||||
/// Used for reading objects as primitive types rather than <see cref="JsonElement"/> values while also maintaining the .NET type name for reconstructing the actual type.
|
||||
/// </summary>
|
||||
public class SystemObjectWithTypeHandlingConverter : JsonConverter<object>
|
||||
public class PolymorphicObjectConverter : JsonConverter<object>
|
||||
{
|
||||
private const string TypePropertyName = "_type";
|
||||
private const string ItemsPropertyName = "_items";
|
||||
|
||||
/// <inheritdoc />
|
||||
public SystemObjectWithTypeHandlingConverter()
|
||||
public PolymorphicObjectConverter()
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -33,6 +33,7 @@ public class SystemObjectWithTypeHandlingConverter : JsonConverter<object>
|
|||
|
||||
if (!jsonObject.TryGetProperty(TypePropertyName, out var typeNameElement))
|
||||
{
|
||||
newOptions.Converters.RemoveWhere(x => x is PolymorphicObjectConverterFactory);
|
||||
return jsonObject.Deserialize(typeof(ExpandoObject), newOptions)!;
|
||||
}
|
||||
|
||||
|
|
@ -74,9 +75,9 @@ public class SystemObjectWithTypeHandlingConverter : JsonConverter<object>
|
|||
var newOptions = new JsonSerializerOptions(options);
|
||||
var type = value.GetType();
|
||||
|
||||
newOptions.Converters.RemoveWhere(x => x is SystemObjectWithTypeHandlingConverterFactory);
|
||||
newOptions.Converters.RemoveWhere(x => x is PolymorphicObjectConverterFactory);
|
||||
|
||||
if (type.IsPrimitive || value is string or DateTimeOffset )
|
||||
if (type.IsPrimitive || value is string or DateTimeOffset or JsonElement)
|
||||
{
|
||||
JsonSerializer.Serialize(writer, value, newOptions);
|
||||
return;
|
||||
|
|
@ -99,7 +100,12 @@ public class SystemObjectWithTypeHandlingConverter : JsonConverter<object>
|
|||
}
|
||||
}
|
||||
|
||||
writer.WriteString(TypePropertyName, type.GetSimpleAssemblyQualifiedName());
|
||||
if(type != typeof(ExpandoObject))
|
||||
{
|
||||
// Write the type name so that we can reconstruct the actual type when deserializing.
|
||||
writer.WriteString(TypePropertyName, type.GetSimpleAssemblyQualifiedName());
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
|
|
@ -6,11 +6,11 @@ namespace Elsa.Workflows.Core.Serialization.Converters;
|
|||
/// <summary>
|
||||
/// A JSON converter for <see cref="System.Object"/> objects.
|
||||
/// </summary>
|
||||
public class SystemObjectWithTypeHandlingConverterFactory : JsonConverterFactory
|
||||
public class PolymorphicObjectConverterFactory : JsonConverterFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvert(Type typeToConvert) => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new SystemObjectWithTypeHandlingConverter();
|
||||
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new PolymorphicObjectConverter();
|
||||
}
|
||||
|
|
@ -23,7 +23,6 @@ public class PropertyBagConverter : JsonConverter<PropertyBag>
|
|||
{
|
||||
var newOptions = new JsonSerializerOptions();
|
||||
newOptions.Converters.Add(new PolymorphicDictionaryConverterFactory());
|
||||
|
||||
JsonSerializer.Serialize(writer, value.Dictionary, newOptions);
|
||||
}
|
||||
}
|
||||
|
|
@ -27,6 +27,6 @@ public class SystemObjectPrimitiveConverter : JsonConverter<object>
|
|||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
|
||||
{
|
||||
throw new InvalidOperationException("Should not get here.");
|
||||
JsonSerializer.Serialize(writer, value, value.GetType());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Workflows.Core.Contracts;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
|
||||
namespace Elsa.Workflows.Core.Services;
|
||||
|
||||
|
|
@ -11,7 +12,7 @@ namespace Elsa.Workflows.Core.Services;
|
|||
public class WorkflowStorageDriver : IStorageDriver
|
||||
{
|
||||
/// <summary>
|
||||
/// The key used to store the variables dictionary in the workflow state.
|
||||
/// The key used to store the variables propertyBag in the workflow state.
|
||||
/// </summary>
|
||||
public const string VariablesDictionaryStateKey = "PersistentVariablesDictionary";
|
||||
|
||||
|
|
@ -26,7 +27,7 @@ public class WorkflowStorageDriver : IStorageDriver
|
|||
public ValueTask<object?> ReadAsync(string id, StorageDriverContext context)
|
||||
{
|
||||
var dictionary = GetVariablesDictionary(context);
|
||||
var value = dictionary.TryGetValue(id, out var v) ? v : default;
|
||||
var value = dictionary.Dictionary.TryGetValue(id, out var v) ? v : default;
|
||||
return new(value);
|
||||
}
|
||||
|
||||
|
|
@ -37,13 +38,13 @@ public class WorkflowStorageDriver : IStorageDriver
|
|||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
private IDictionary<string, object> GetVariablesDictionary(StorageDriverContext context) => context.ExecutionContext.Properties.GetOrAdd(VariablesDictionaryStateKey, () => new Dictionary<string, object>());
|
||||
private void SetVariablesDictionary(StorageDriverContext context, IDictionary<string, object> dictionary) => context.ExecutionContext.Properties[VariablesDictionaryStateKey] = dictionary;
|
||||
private PropertyBag GetVariablesDictionary(StorageDriverContext context) => context.ExecutionContext.Properties.GetOrAdd(VariablesDictionaryStateKey, () => new PropertyBag());
|
||||
private void SetVariablesDictionary(StorageDriverContext context, PropertyBag propertyBag) => context.ExecutionContext.Properties[VariablesDictionaryStateKey] = propertyBag;
|
||||
|
||||
private void UpdateVariablesDictionary(StorageDriverContext context, Action<IDictionary<string, object>> update)
|
||||
{
|
||||
var dictionary = GetVariablesDictionary(context);
|
||||
update(dictionary);
|
||||
update(dictionary.Dictionary);
|
||||
SetVariablesDictionary(context, dictionary);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue