< Summary - Kestrun — Combined Coverage

Information
Class: Kestrun.Utilities.Yaml.PSObjectTypeConverter
Assembly: Kestrun
File(s): /home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Utilities/Yaml/PSObjectTypeConverter.cs
Tag: Kestrun/Kestrun@ca54e35c77799b76774b3805b6f075cdbc0c5fbe
Line coverage
89%
Covered lines: 41
Uncovered lines: 5
Coverable lines: 46
Total lines: 136
Line coverage: 89.1%
Branch coverage
84%
Covered branches: 32
Total branches: 38
Branch coverage: 84.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 10/13/2025 - 16:52:37 Line coverage: 86% (43/50) Branch coverage: 76.4% (26/34) Total lines: 143 Tag: Kestrun/Kestrun@10d476bee71c71ad215bb8ab59f219887b5b4a5e01/15/2026 - 23:50:39 Line coverage: 89.1% (41/46) Branch coverage: 84.2% (32/38) Total lines: 136 Tag: Kestrun/Kestrun@2d823cb7ceae127151c8880ca073ffbb9c6322aa 10/13/2025 - 16:52:37 Line coverage: 86% (43/50) Branch coverage: 76.4% (26/34) Total lines: 143 Tag: Kestrun/Kestrun@10d476bee71c71ad215bb8ab59f219887b5b4a5e01/15/2026 - 23:50:39 Line coverage: 89.1% (41/46) Branch coverage: 84.2% (32/38) Total lines: 136 Tag: Kestrun/Kestrun@2d823cb7ceae127151c8880ca073ffbb9c6322aa

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
Accepts(...)100%11100%
ReadYaml(...)0%620%
WriteYaml(...)83.33%6683.33%
EmitNull(...)100%210%
IsDictionaryLike(...)100%66100%
SerializeNonDictionary(...)50%44100%
BeginMapping(...)100%22100%
EndMapping(...)100%11100%
WriteProperty(...)100%88100%
EmitNullProperty(...)100%11100%
UnwrapValue(...)90%1010100%

File(s)

/home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Utilities/Yaml/PSObjectTypeConverter.cs

#LineLine coverage
 1using System.Collections;
 2using System.Management.Automation;
 3using YamlDotNet.Core;
 4using YamlDotNet.Core.Events;
 5using YamlDotNet.Serialization;
 6
 7namespace Kestrun.Utilities.Yaml;
 8
 9/// <summary>
 10/// YAML type converter for PSObject types
 11/// </summary>
 12/// <param name="omitNullValues">If true, null values will be omitted from the output</param>
 13/// <param name="useFlowStyle">If true, the mapping will be emitted in flow style</param>
 3214public class PSObjectTypeConverter(bool omitNullValues = false, bool useFlowStyle = false) : IYamlTypeConverter
 15{
 3216    private readonly bool omitNullValues = omitNullValues;
 3217    private readonly bool useFlowStyle = useFlowStyle;
 18
 19    /// <summary>
 20    /// Check if the type is PSObject
 21    /// </summary>
 22    /// <param name="type">The type to check</param>
 23    /// <returns>true if the type is PSObject; otherwise, false.</returns>
 17724    public bool Accepts(Type type) => typeof(PSObject).IsAssignableFrom(type);
 25
 26    /// <summary>
 27    /// Read a PSObject from YAML
 28    /// </summary>
 29    /// <param name="parser">The YAML parser</param>
 30    /// <param name="type">The target type</param>
 31    /// <param name="rootDeserializer">The root deserializer</param>
 32    /// <returns>The deserialized PSObject</returns>
 33    public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
 34    {
 35        // We don't really need to do any custom deserialization.
 036        var deserialized = rootDeserializer(typeof(IDictionary<string, object>)) as IDictionary;
 37        // Use PSObject.AsPSObject to avoid wrapping an already wrapped PSObject and to safely handle null values.
 038        return PSObject.AsPSObject(deserialized ?? new Hashtable());
 39    }
 40
 41    /// <summary>
 42    /// Write a PSObject to YAML
 43    /// </summary>
 44    /// <param name="emitter">The YAML emitter</param>
 45    /// <param name="value">The PSObject to serialize</param>
 46    /// <param name="type">The target type</param>
 47    /// <param name="serializer">The object serializer</param>
 48    public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
 49    {
 1150        if (value is null)
 51        {
 052            EmitNull(serializer);
 053            return;
 54        }
 55
 1156        var psObj = (PSObject)value;
 1157        if (!IsDictionaryLike(psObj))
 58        {
 259            SerializeNonDictionary(psObj, serializer);
 260            return;
 61        }
 62
 963        BeginMapping(emitter);
 4664        foreach (var prop in psObj.Properties)
 65        {
 1466            WriteProperty(emitter, prop, serializer);
 67        }
 968        EndMapping(emitter);
 969    }
 70
 71    // ----------------- Helper Methods -----------------
 072    private static void EmitNull(ObjectSerializer serializer) => serializer(null, typeof(object));
 73
 74    private static bool IsDictionaryLike(PSObject psObj)
 75    {
 1176        var baseObj = psObj.BaseObject;
 1177        return baseObj is not null and (IDictionary or PSCustomObject);
 78    }
 79
 80    private static void SerializeNonDictionary(PSObject psObj, ObjectSerializer serializer)
 281        => serializer(psObj.BaseObject, psObj.BaseObject?.GetType() ?? typeof(object));
 82
 83    private void BeginMapping(IEmitter emitter)
 84    {
 985        var mappingStyle = useFlowStyle ? MappingStyle.Flow : MappingStyle.Block;
 986        emitter.Emit(new MappingStart(AnchorName.Empty, TagName.Empty, true, mappingStyle));
 987    }
 88
 989    private static void EndMapping(IEmitter emitter) => emitter.Emit(new MappingEnd());
 90
 91    private void WriteProperty(IEmitter emitter, PSPropertyInfo prop, ObjectSerializer serializer)
 92    {
 1493        if (prop.Value is null)
 94        {
 295            if (omitNullValues)
 96            {
 197                return; // skip entirely
 98            }
 199            EmitNullProperty(emitter, prop, serializer);
 1100            return;
 101        }
 102
 103        // Emit key
 12104        serializer(prop.Name, prop.Name.GetType());
 105
 12106        var (val, type) = UnwrapValue(prop.Value);
 107
 12108        if (val is string s && s.Length == 0)
 109        {
 110            // Double-quoted explicit empty string
 2111            emitter.Emit(new Scalar(AnchorName.Empty, TagName.Empty, string.Empty, ScalarStyle.DoubleQuoted, true, false
 2112            return;
 113        }
 114
 10115        serializer(val, type);
 10116    }
 117
 118    private void EmitNullProperty(IEmitter emitter, PSPropertyInfo prop, ObjectSerializer serializer)
 119    {
 1120        serializer(prop.Name, prop.Name.GetType());
 1121        emitter.Emit(new Scalar(AnchorName.Empty, TagName.Empty, "null", ScalarStyle.Plain, true, false));
 1122    }
 123
 124    private static (object value, Type type) UnwrapValue(object raw)
 125    {
 12126        if (raw is PSObject nested)
 127        {
 2128            var nestedType = nested.BaseObject?.GetType();
 2129            if (nestedType != null && nestedType != typeof(PSCustomObject) && nested.BaseObject != null)
 130            {
 1131                return (nested.BaseObject, nestedType);
 132            }
 133        }
 11134        return (raw, raw.GetType());
 135    }
 136}