Add support for mixed-cased JSON keys

This commit is contained in:
Sipke Schoorstra 2023-07-06 19:32:30 +02:00
parent b9defca7bd
commit b5e882efc0
6 changed files with 48 additions and 19 deletions

View file

@ -12,7 +12,9 @@ public static class DictionaryExtensions
/// </summary>
public static T? TryGetValue<T>(this IDictionary<string, object> dictionary, string key, Func<T>? defaultValue = default, JsonSerializerOptions? serializerOptions = default)
{
if (dictionary.TryGetValue(key, out var value) && value is not JsonElement { ValueKind: JsonValueKind.Undefined })
var caseInsensitiveDictionary = new Dictionary<string, object>(dictionary, StringComparer.OrdinalIgnoreCase);
if (caseInsensitiveDictionary.TryGetValue(key, out var value) && value is not JsonElement { ValueKind: JsonValueKind.Undefined })
{
var convertedValue = value.ConvertTo<T>(new ObjectConverterOptions(serializerOptions));
@ -33,7 +35,9 @@ public static class DictionaryExtensions
/// </summary>
public static object? TryGetValue(this IDictionary<string, object> dictionary, string key, Func<object>? defaultValue = default)
{
if (dictionary.TryGetValue(key, out var value) && value is not JsonElement { ValueKind: JsonValueKind.Undefined })
var caseInsensitiveDictionary = new Dictionary<string, object>(dictionary, StringComparer.OrdinalIgnoreCase);
if (caseInsensitiveDictionary.TryGetValue(key, out var value) && value is not JsonElement { ValueKind: JsonValueKind.Undefined })
return value;
if (defaultValue == null)

View file

@ -53,7 +53,7 @@ export class DefaultActivityTemplate {
const activity: Activity = nodeMap[activityId];
const ports = portProvider.getOutboundPorts({activityDescriptor, activity});
const embeddedPorts = ports.filter(x => x.type == PortType.Embedded && x.isBrowsable !== false);
const canStartWorkflow = activity?.customProperties?.CanStartWorkflow ?? activity?.canStartWorkflow ?? false;
const canStartWorkflow: boolean = activity?.customProperties?.canStartWorkflow ?? activity?.customProperties?.CanStartWorkflow ?? activity?.canStartWorkflow ?? false;
const icon = this.icon;
const hasIcon = !!icon;
const textColor = canStartWorkflow ? 'tw-text-white' : 'tw-text-gray-700';

View file

@ -218,7 +218,7 @@ export class ActivityPropertiesEditor {
const activity: Activity = this.activity;
const inputElement = e.target as HTMLInputElement;
activity.customProperties.CanStartWorkflow = inputElement.checked;
activity.customProperties.canStartWorkflow = inputElement.checked;
this.updateActivity();
}
@ -299,7 +299,7 @@ export class ActivityPropertiesEditor {
const {activity} = this.renderContext;
const activityId = activity.id;
const displayText: string = activity.metadata?.displayText ?? '';
const canStartWorkflow: boolean = activity.customProperties?.CanStartWorkflow ?? activity.canStartWorkflow ?? false;
const canStartWorkflow: boolean = activity.customProperties?.canStartWorkflow ?? activity?.customProperties?.CanStartWorkflow ?? activity.canStartWorkflow ?? false;
const key = `${activityId}`;
return <div key={key}>

View file

@ -8,9 +8,9 @@ namespace Elsa.Extensions;
/// </summary>
public static class ActivityPropertyExtensions
{
private const string CanStartWorkflowPropertyName = "CanStartWorkflow";
private const string RunAsynchronouslyPropertyName = "RunAsynchronously";
private const string SourcePropertyName = "Source";
private static readonly string[] CanStartWorkflowPropertyName = {"canStartWorkflow", "CanStartWorkflow", };
private static readonly string[] RunAsynchronouslyPropertyName = {"runAsynchronously", "RunAsynchronously" };
private static readonly string[] SourcePropertyName = {"source", "Source"};
/// <summary>
/// Gets a flag indicating whether this activity can be used for starting a workflow.
@ -21,7 +21,7 @@ public static class ActivityPropertyExtensions
/// <summary>
/// Sets a flag indicating whether this activity can be used for starting a workflow.
/// </summary>
public static void SetCanStartWorkflow(this IActivity activity, bool value) => activity.CustomProperties[CanStartWorkflowPropertyName] = value;
public static void SetCanStartWorkflow(this IActivity activity, bool value) => activity.CustomProperties[CanStartWorkflowPropertyName[0]] = value;
/// <summary>
/// Gets a flag indicating if this activity should execute synchronously or asynchronously.
@ -35,7 +35,7 @@ public static class ActivityPropertyExtensions
/// By default, activities with an <see cref="Workflows.Core.Models.ActivityKind"/> of <see cref="Workflows.Core.Models.ActivityKind.Action"/>, <see cref="Workflows.Core.Models.ActivityKind.Task"/> or <see cref="Workflows.Core.Models.ActivityKind.Trigger"/>
/// will execute synchronously, while activities of the <see cref="Workflows.Core.Models.ActivityKind.Job"/> kind will execute asynchronously.
/// </summary>
public static void SetRunAsynchronously(this IActivity activity, bool value) => activity.CustomProperties[RunAsynchronouslyPropertyName] = value;
public static void SetRunAsynchronously(this IActivity activity, bool value) => activity.CustomProperties[RunAsynchronouslyPropertyName[0]] = value;
/// <summary>
/// Gets the source file and line number where this activity was instantiated, if any.
@ -45,7 +45,7 @@ public static class ActivityPropertyExtensions
/// <summary>
/// Sets the source file and line number where this activity was instantiated, if any.
/// </summary>
public static void SetSource(this IActivity activity, string value) => activity.CustomProperties[SourcePropertyName] = value;
public static void SetSource(this IActivity activity, string value) => activity.CustomProperties[SourcePropertyName[0]] = value;
/// <summary>
/// Sets the source file and line number where this activity was instantiated, if any.

View file

@ -6,6 +6,7 @@ namespace Elsa.Extensions;
public static class DictionaryExtensions
{
public static bool TryGetValue<T>(this IDictionary<string, object> dictionary, string key, out T value) => dictionary.TryGetValue<string, T>(key, out value);
public static bool TryGetValue<T>(this IDictionary<string, object> dictionary, IEnumerable<string> keys, out T value) => dictionary.TryGetValue<string, T>(keys, out value);
public static bool TryGetValue<T>(this IDictionary<object, object> dictionary, string key, out T value) => dictionary.TryGetValue<object, T>(key, out value);
public static bool TryGetValue<TKey, T>(this IDictionary<TKey, object> dictionary, TKey key, out T value)
@ -19,6 +20,21 @@ public static class DictionaryExtensions
value = ConvertValue<T>(item);
return true;
}
public static bool TryGetValue<TKey, T>(this IDictionary<TKey, object> dictionary, IEnumerable<TKey> keys, out T value)
{
foreach (var key in keys)
{
if (dictionary.TryGetValue(key, out var item))
{
value = ConvertValue<T>(item);
return true;
}
}
value = default!;
return false;
}
public static T? GetValue<TKey, T>(this IDictionary<TKey, T> dictionary, TKey key) => ConvertValue<T>(dictionary[key]);
public static T? GetValue<T>(this IDictionary<string, object> dictionary, string key) => ConvertValue<T>(dictionary[key]);
@ -26,6 +42,7 @@ public static class DictionaryExtensions
public static T? GetValueOrDefault<TKey, T>(this IDictionary<TKey, object> dictionary, TKey key, Func<T?> defaultValueFactory) => TryGetValue<TKey, T>(dictionary, key, out var value) ? value : defaultValueFactory();
public static T? GetValueOrDefault<TKey, T>(this IDictionary<TKey, object> dictionary, TKey key) => GetValueOrDefault<TKey, T>(dictionary, key, () => default);
public static T? GetValueOrDefault<T>(this IDictionary<string, object> dictionary, string key, Func<T?> defaultValueFactory) => TryGetValue<T>(dictionary, key, out var value) ? value : defaultValueFactory();
public static T? GetValueOrDefault<T>(this IDictionary<string, object> dictionary, IEnumerable<string> keys, Func<T?> defaultValueFactory) => TryGetValue<T>(dictionary, keys, out var value) ? value : defaultValueFactory();
public static T? GetValueOrDefault<T>(this IDictionary<string, object> dictionary, string key) => GetValueOrDefault<T>(dictionary, key, () => default);
public static object? GetValueOrDefault(this IDictionary<string, object> dictionary, string key) => GetValueOrDefault<object>(dictionary, key, () => default);

View file

@ -16,21 +16,29 @@ public class ActivityFactory : IActivityFactory
var canStartWorkflow = GetBoolean(context.Element, "canStartWorkflow");
var runAsynchronously = GetBoolean(context.Element, "runAsynchronously");
var activity = (IActivity)context.Element.Deserialize(type, context.SerializerOptions)!;
activity.SetCanStartWorkflow(canStartWorkflow);
activity.SetRunAsynchronously(runAsynchronously);
return activity;
}
private static bool GetBoolean(JsonElement element, string propertyName)
{
if (element.TryGetProperty("customProperties", out var customPropertyElement))
var propertyNames = new[] { propertyName.Camelize(), propertyName.Pascalize() };
foreach (var name in propertyNames)
{
if(customPropertyElement.TryGetProperty(propertyName.Pascalize(), out var canStartWorkflowElement))
return canStartWorkflowElement.GetBoolean();
if (element.TryGetProperty("customProperties", out var customPropertyElement))
{
if (customPropertyElement.TryGetProperty(name, out var canStartWorkflowElement))
return canStartWorkflowElement.GetBoolean();
}
if (element.TryGetProperty(propertyName.Camelize(), out var property) && property.GetBoolean())
return true;
}
return element.TryGetProperty(propertyName.Camelize(), out var property) && property.GetBoolean();
return false;
}
}