< Summary

Information
Class: Elsa.Workflows.Serialization.Converters.ExcludeFromHashConverterFactory
Assembly: Elsa.Workflows.Core
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Core/Serialization/Converters/ExcludeFromHashConverter.cs
Line coverage
100%
Covered lines: 2
Uncovered lines: 0
Coverable lines: 2
Total lines: 123
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
CanConvert(...)100%11100%
CreateConverter(...)100%11100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Core/Serialization/Converters/ExcludeFromHashConverter.cs

#LineLine coverage
 1using System.Reflection;
 2using System.Runtime.CompilerServices;
 3using System.Text.Json;
 4using System.Text.Json.Serialization;
 5using Elsa.Extensions;
 6using Elsa.Workflows.Attributes;
 7
 8namespace Elsa.Workflows.Serialization.Converters;
 9
 10/// <summary>
 11/// Serializes an object to JSON, excluding properties marked with <see cref="ExcludeFromHashAttribute"/>.
 12/// Properties ignored by <see cref="JsonIgnoreAttribute"/> are also excluded according to their configured ignore condi
 13/// avoid adding or changing these attributes on bookmark or stimulus payloads whose hashes must remain compatible with 
 14/// </summary>
 15public class ExcludeFromHashConverter : JsonConverter<object>
 16{
 17    private static readonly ConditionalWeakTable<Type, PropertyMetadata[]> PropertyCache = new();
 18    private JsonSerializerOptions? _options;
 19
 20    /// <inheritdoc />
 21    public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
 22    {
 23        throw new NotSupportedException();
 24    }
 25
 26    /// <inheritdoc />
 27    public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
 28    {
 29        writer.WriteStartObject();
 30        var newOptions = GetClonedOptions(options);
 31
 32        foreach (var metadata in GetSerializableProperties(value.GetType()))
 33        {
 34            var property = metadata.Property;
 35            var propertyValue = property.GetValue(value);
 36
 37            if (ShouldIgnoreProperty(metadata.JsonIgnoreCondition, property.PropertyType, propertyValue))
 38            {
 39                continue;
 40            }
 41
 42            writer.WritePropertyName(property.Name);
 43            JsonSerializer.Serialize(writer, propertyValue, newOptions);
 44        }
 45
 46        writer.WriteEndObject();
 47    }
 48
 49    private static PropertyMetadata[] GetSerializableProperties(Type type)
 50    {
 51        return PropertyCache.GetValue(type, static itemType => GetPublicInstanceProperties(itemType)
 52            .Where(property => property.GetIndexParameters().Length == 0)
 53            .Select(property => new
 54            {
 55                Property = property,
 56                ExcludeFromHash = property.GetCustomAttribute<ExcludeFromHashAttribute>(),
 57                JsonIgnore = property.GetCustomAttribute<JsonIgnoreAttribute>()
 58            })
 59            .Where(x => x.ExcludeFromHash == null && !ShouldAlwaysIgnoreProperty(x.JsonIgnore?.Condition))
 60            .Select(x => new PropertyMetadata(x.Property, x.JsonIgnore?.Condition))
 61            .ToArray());
 62    }
 63
 64    private static IEnumerable<PropertyInfo> GetPublicInstanceProperties(Type type)
 65    {
 66        for (var currentType = type; currentType != null && currentType != typeof(object); currentType = currentType.Bas
 67        {
 68            foreach (var property in currentType
 69                         .GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public)
 70                         .OrderBy(x => x.MetadataToken))
 71            {
 72                yield return property;
 73            }
 74        }
 75    }
 76
 77    private static bool ShouldAlwaysIgnoreProperty(JsonIgnoreCondition? condition)
 78    {
 79        return condition == JsonIgnoreCondition.Always;
 80    }
 81
 82    private static bool ShouldIgnoreProperty(JsonIgnoreCondition? condition, Type declaredType, object? value)
 83    {
 84        return condition switch
 85        {
 86            null => false,
 87            JsonIgnoreCondition.Never => false,
 88            JsonIgnoreCondition.Always => true,
 89            JsonIgnoreCondition.WhenWritingNull => value == null,
 90            JsonIgnoreCondition.WhenWritingDefault => value == null || IsDefaultValue(declaredType, value),
 91            _ => false
 92        };
 93    }
 94
 95    private static bool IsDefaultValue(Type declaredType, object value)
 96    {
 97        return declaredType.IsValueType && value.Equals(Activator.CreateInstance(declaredType));
 98    }
 99
 100    private sealed record PropertyMetadata(PropertyInfo Property, JsonIgnoreCondition? JsonIgnoreCondition);
 101
 102    private JsonSerializerOptions GetClonedOptions(JsonSerializerOptions options)
 103    {
 104        if(_options != null)
 105            return _options;
 106
 107        var newOptions = new JsonSerializerOptions(options);
 108        newOptions.Converters.RemoveWhere(x => x is ExcludeFromHashConverterFactory);
 109        return _options = newOptions;
 110    }
 111}
 112
 113/// <summary>
 114/// A factory for creating <see cref="ExcludeFromHashConverter"/> instances.
 115/// </summary>
 116public class ExcludeFromHashConverterFactory : JsonConverterFactory
 117{
 118    /// <inheritdoc />
 25119    public override bool CanConvert(Type typeToConvert) => true;
 120
 121    /// <inheritdoc />
 25122    public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new ExcludeFromH
 123}