< Summary - Kestrun — Combined Coverage

Information
Class: Kestrun.Utilities.YamlHelper
Assembly: Kestrun
File(s): /home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Utilities/YamlHelper.cs
Tag: Kestrun/Kestrun@9d3a582b2d63930269564a7591aa77ef297cadeb
Line coverage
92%
Covered lines: 66
Uncovered lines: 5
Coverable lines: 71
Total lines: 241
Line coverage: 92.9%
Branch coverage
90%
Covered branches: 49
Total branches: 54
Branch coverage: 90.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
ToYaml(...)100%11100%
ToHashtable(...)50%2275%
ToPSCustomObject(...)100%11100%
NormalizePSObject(...)92.85%141488.88%
NormalizeDictionary(...)100%22100%
NormalizeEnumerable(...)100%22100%
NormalizeByProperties(...)100%4472.72%
ConvertToPSCompatible(...)83.33%1212100%
ConvertToPSCustomObject(...)94.44%1818100%
AddMember(...)100%11100%

File(s)

/home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Utilities/YamlHelper.cs

#LineLine coverage
 1using System.Collections;
 2using System.Management.Automation;
 3using System.Reflection;
 4using YamlDotNet.Serialization;
 5using YamlDotNet.Serialization.NamingConventions;
 6namespace Kestrun.Utilities;
 7
 8/// <summary>
 9/// Provides helper methods for serializing and deserializing YAML content, with special handling for PowerShell objects
 10/// </summary>
 11public static class YamlHelper
 12{
 113    private static readonly ISerializer _serializer = new SerializerBuilder()
 114        .WithNamingConvention(CamelCaseNamingConvention.Instance).DisableAliases()
 115        .Build();
 16
 117    private static readonly IDeserializer _deserializer = new DeserializerBuilder()
 118        .WithNamingConvention(CamelCaseNamingConvention.Instance)
 119        .Build();
 20
 21    /// <summary>
 22    /// Serializes any PowerShell object to YAML format.
 23    /// </summary>
 24    /// <param name="input">The PowerShell object to serialize. Can be null.</param>
 25    /// <returns>A string containing the YAML representation of the input object.</returns>
 26    public static string ToYaml(object? input)
 27    {
 528        var normalized = NormalizePSObject(input);
 529        return _serializer.Serialize(normalized);
 30    }
 31
 32    /// <summary>
 33    /// Deserializes a YAML string into a PowerShell Hashtable.
 34    /// </summary>
 35    /// <param name="yaml">The YAML string to deserialize.</param>
 36    /// <returns>A Hashtable containing the deserialized YAML content.</returns>
 37    public static Hashtable ToHashtable(string yaml)
 38    {
 239        if (string.IsNullOrEmpty(yaml))
 40        {
 041            return [];
 42        }
 243        var obj = _deserializer.Deserialize<object>(yaml);
 244        return (Hashtable)ConvertToPSCompatible(obj);
 45    }
 46
 47    /// <summary>
 48    /// Deserializes a YAML string into a PowerShell PSObject (PSCustomObject).
 49    /// </summary>
 50    /// <param name="yaml">The YAML string to deserialize.</param>
 51    /// <returns>A PSObject containing the deserialized YAML content.</returns>
 52    public static PSObject ToPSCustomObject(string yaml)
 53    {
 254        ArgumentNullException.ThrowIfNull(yaml);
 255        var obj = _deserializer.Deserialize<object>(yaml);
 256        var hash = (Hashtable)ConvertToPSCompatible(obj);
 257        return ConvertToPSCustomObject(hash);
 58    }
 59
 60    /// <summary>
 61    /// Normalizes a PowerShell object into a plain .NET structure that can be serialized to YAML.
 62    /// </summary>
 63    /// <param name="obj">The object to normalize. Can be null.</param>
 64    /// <returns>A normalized object suitable for YAML serialization, or null if the input is null.</returns>
 65    /// <remarks>
 66    /// This method handles various PowerShell-specific types and converts them into standard .NET types:
 67    /// - PSObjects are unwrapped to their base objects
 68    /// - Dictionaries are converted to Dictionary&lt;object, object?&gt;
 69    /// - Collections are converted to List&lt;object&gt;
 70    /// - Objects with properties are converted to Dictionary&lt;string, object?&gt;
 71    /// </remarks>
 72    private static object? NormalizePSObject(object? obj)
 73    {
 74        // Unwrap PSObject
 3075        if (obj is PSObject psObj)
 76        {
 077            return NormalizePSObject(psObj.BaseObject);
 78        }
 79
 80        // Dictionaries → Dictionary<object, object?>
 3081        if (obj is IDictionary dict)
 82        {
 283            return NormalizeDictionary(dict);
 84        }
 85
 86        // Enumerables (not string) → List<object?>
 2887        if (obj is IEnumerable enumerable and not string)
 88        {
 189            return NormalizeEnumerable(enumerable);
 90        }
 91
 92        // Null, primitives, and string → return as-is
 2793        if (obj is null || obj.GetType().IsPrimitive || obj is string)
 94        {
 2495            return obj;
 96        }
 97
 98        // Objects with properties → Dictionary<string, object?>
 399        return NormalizeByProperties(obj);
 100    }
 101
 102    private static Dictionary<object, object?> NormalizeDictionary(IDictionary dict)
 103    {
 2104        var d = new Dictionary<object, object?>();
 12105        foreach (var key in dict.Keys)
 106        {
 4107            var value = dict[key];
 4108            d[key] = NormalizePSObject(value);
 109        }
 2110        return d;
 111    }
 112
 113    private static List<object?> NormalizeEnumerable(IEnumerable enumerable)
 114    {
 1115        var list = new List<object?>();
 6116        foreach (var item in enumerable)
 117        {
 2118            list.Add(NormalizePSObject(item));
 119        }
 120
 1121        return list;
 122    }
 123
 124    private static Dictionary<string, object?> NormalizeByProperties(object obj)
 125    {
 3126        var result = new Dictionary<string, object?>();
 3127        var props = obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
 44128        foreach (var prop in props)
 129        {
 130            // Skip indexers
 19131            if (prop.GetIndexParameters().Length > 0)
 132            {
 133                continue;
 134            }
 135
 136            try
 137            {
 19138                var propValue = prop.GetValue(obj);
 19139                result[prop.Name] = NormalizePSObject(propValue);
 19140            }
 0141            catch
 142            {
 0143                result[prop.Name] = null;
 0144            }
 145        }
 3146        return result;
 147    }
 148
 149    /// <summary>
 150    /// Converts a deserialized YAML object into PowerShell-compatible types recursively.
 151    /// </summary>
 152    /// <param name="obj">The object to convert.</param>
 153    /// <returns>A PowerShell-compatible object structure using Hashtable and ArrayList.</returns>
 154    /// <remarks>
 155    /// This method performs the following conversions:
 156    /// - Dictionaries are converted to PowerShell Hashtables
 157    /// - Lists are converted to ArrayLists
 158    /// - Null values are converted to empty strings
 159    /// </remarks>
 160    private static object ConvertToPSCompatible(object obj)
 161    {
 162        switch (obj)
 163        {
 164            case IDictionary dict:
 4165                var ht = new Hashtable();
 24166                foreach (DictionaryEntry entry in dict)
 167                {
 8168                    ht[entry.Key] = entry.Value is not null ? ConvertToPSCompatible(entry.Value) : null;
 169                }
 170
 4171                return ht;
 172
 173            case IList list:
 2174                var array = new ArrayList();
 12175                foreach (var item in list)
 176                {
 4177                    _ = array.Add(ConvertToPSCompatible(item));
 178                }
 179
 2180                return array;
 181
 182            default:
 10183                return obj ?? string.Empty;
 184        }
 185    }
 186
 187    /// <summary>
 188    /// Converts a Hashtable or ArrayList into a PowerShell PSObject (PSCustomObject) recursively.
 189    /// </summary>
 190    /// <param name="obj">The object to convert, typically a Hashtable or ArrayList.</param>
 191    /// <returns>A PSObject representing the input structure.</returns>
 192    /// <remarks>
 193    /// This method performs deep conversion of nested structures:
 194    /// - Hashtables are converted to PSObjects with NoteProperties
 195    /// - ArrayLists are converted to arrays of PSObjects
 196    /// - Other types are wrapped in PSObject using AsPSObject
 197    /// </remarks>
 198    private static PSObject ConvertToPSCustomObject(object obj)
 199    {
 5200        if (obj is Hashtable ht)
 201        {
 2202            var result = new PSObject();
 12203            foreach (DictionaryEntry entry in ht)
 204            {
 4205                var key = entry.Key.ToString();
 4206                var value = entry.Value;
 207
 4208                if (value is Hashtable or ArrayList)
 209                {
 1210                    value = ConvertToPSCustomObject(value);
 211                }
 212
 4213                if (key != null)
 214                {
 4215                    AddMember(result, key, value ?? string.Empty);
 216                }
 217            }
 2218            return result;
 219        }
 3220        else if (obj is ArrayList list)
 221        {
 1222            var resultList = new List<object>();
 6223            foreach (var item in list)
 224            {
 2225                resultList.Add(ConvertToPSCustomObject(item));
 226            }
 227
 1228            return new PSObject(resultList.ToArray());
 229        }
 230
 2231        return PSObject.AsPSObject(obj);
 232    }
 233
 234    /// <summary>
 235    /// Adds a new note property to a PSObject.
 236    /// </summary>
 237    /// <param name="obj">The PSObject to add the property to.</param>
 238    /// <param name="name">The name of the property.</param>
 239    /// <param name="value">The value of the property.</param>
 4240    private static void AddMember(PSObject obj, string name, object value) => obj.Properties.Add(new PSNoteProperty(name
 241}