< Summary

Information
Class: Spdx3.Serialization.SpdxModelConverter<T>
Assembly: Spdx3
File(s): /home/runner/work/Spdx3/Spdx3/Spdx3/Serialization/SpdxModelConverter.cs
Line coverage
95%
Covered lines: 132
Uncovered lines: 6
Coverable lines: 138
Total lines: 352
Line coverage: 95.6%
Branch coverage
90%
Covered branches: 126
Total branches: 139
Branch coverage: 90.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
Read(...)80%1515100%
LoadJsonStringIntoProperty(...)91.66%121294.73%
LoadJsonStringIntoGenericProperty(...)84.61%262696%
Write(...)95.45%222295.45%
WriteSimpleProperty(...)90.62%323296.66%
WriteReferencesToListItems(...)100%44100%
WriteReferencesToEnumValues(...)100%44100%
GetJsonElementNameFromPropertyAttribute(...)100%88100%
GetPropertyFromJsonElementName(...)93.75%221671.42%

File(s)

/home/runner/work/Spdx3/Spdx3/Spdx3/Serialization/SpdxModelConverter.cs

#LineLine coverage
 1using System.Collections;
 2using System.Diagnostics;
 3using System.Reflection;
 4using System.Text.Json;
 5using System.Text.Json.Serialization;
 6using Spdx3.Exceptions;
 7using Spdx3.Model;
 8using Spdx3.Utility;
 9using Uri = System.Uri;
 10
 11namespace Spdx3.Serialization;
 12
 13public class SpdxModelConverter<T> : JsonConverter<T>
 14{
 15    /// <summary>
 16    ///     The main Read method implementation for this implementation of a JsonConverter
 17    /// </summary>
 18    /// <param name="reader">The JSON reader</param>
 19    /// <param name="typeToConvert">The type we're trying/expecting to construct from the JSON</param>
 20    /// <param name="options">Options for the converter</param>
 21    /// <returns>The constructed instance read from the JSON</returns>
 22    /// <exception cref="Spdx3SerializationException">If something specific to SPDX3 goes wrong</exception>
 23    public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
 24    {
 2825        var result = (T?)Activator.CreateInstance(typeToConvert, true) ??
 2826                     throw new Spdx3SerializationException($"Could not create instance of {typeToConvert}");
 27
 28        // Initialize to some throwaway non-null value
 2829        var currentProperty = result.GetType().GetProperties().First();
 30
 22931        while (reader.Read())
 32        {
 20533            switch (reader.TokenType)
 34            {
 35                case JsonTokenType.PropertyName:
 8436                    var newPropName = reader.GetString() ??
 8437                                      throw new Spdx3SerializationException("Couldn't read property name");
 8438                    currentProperty = GetPropertyFromJsonElementName(typeToConvert, newPropName);
 8439                    break;
 40
 41                case JsonTokenType.String:
 7542                    LoadJsonStringIntoProperty(result, currentProperty,
 7543                        reader.GetString() ?? throw new Spdx3SerializationException("Couldn't read string"));
 44
 7545                    break;
 46                case JsonTokenType.Number:
 1447                    if (currentProperty.PropertyType == typeof(int))
 48                    {
 149                        currentProperty.SetValue(result, reader.GetInt32());
 50                    }
 51                    else
 52                    {
 1353                        currentProperty.SetValue(result, reader.GetDouble());
 54                    }
 55
 56                    break;
 57            }
 58        }
 59
 2460        return result;
 61    }
 62
 63    /// <summary>
 64    ///     Take a given string value just read from the JSON, and load its value into whatever the property is that we'
 65    ///     currently reading.
 66    /// </summary>
 67    /// <param name="result">The object with the property</param>
 68    /// <param name="currentProperty">The property of the object that we're loading into</param>
 69    /// <param name="strVal">The string value</param>
 70    /// <exception cref="Spdx3SerializationException">If something specific to SPDX3 goes wrong</exception>
 71    private static void LoadJsonStringIntoProperty(object result, PropertyInfo currentProperty, string strVal)
 72    {
 73        Debug.Assert(currentProperty != null, $"There is no current property to receive for value {strVal}");
 74
 7575        if (currentProperty.PropertyType == typeof(string))
 76        {
 2477            currentProperty.SetValue(result, strVal);
 78        }
 5179        else if (currentProperty.PropertyType == typeof(Uri))
 80        {
 1981            currentProperty.SetValue(result, new Uri(strVal));
 82        }
 3283        else if (currentProperty.PropertyType == typeof(DateTimeOffset))
 84        {
 185            currentProperty.SetValue(result, DateTimeOffset.Parse(strVal));
 86        }
 3187        else if (currentProperty.PropertyType.IsEnum)
 88        {
 389            currentProperty.SetValue(result, Enum.Parse(currentProperty.PropertyType, strVal));
 90        }
 2891        else if (currentProperty.PropertyType.IsGenericType)
 92        {
 1493            LoadJsonStringIntoGenericProperty(result, currentProperty, strVal);
 94        }
 1495        else if (currentProperty.PropertyType.IsSubclassOf(typeof(BaseModelClass)))
 96        {
 97            /*
 98             * At this point we have a string that is the URN of another Element, which we may or may not
 99             * have read yet.  For now, make a placeholder element of the type needed and the ID in the json.
 100             * Later, we're going to need to go through the objects and replace the placeholder with the real one.
 101             */
 14102            var placeHolder = Convert.ChangeType(Activator.CreateInstance(currentProperty.PropertyType, true),
 14103                currentProperty.PropertyType);
 104
 105#pragma warning disable CS8602 // Dereference of a possibly null reference.
 14106            currentProperty.PropertyType.GetProperty("SpdxId").SetValue(placeHolder, new Uri(strVal));
 14107            currentProperty.PropertyType.GetProperty("Type").SetValue(placeHolder,
 14108#pragma warning restore CS8602 // Dereference of a possibly null reference.
 14109                Naming.SpdxTypeForClass(currentProperty.PropertyType));
 14110            currentProperty.SetValue(result, placeHolder);
 111        }
 112        else
 113        {
 0114            throw new Spdx3SerializationException($"No string handler for a {currentProperty.PropertyType.Name}");
 115        }
 116    }
 117
 118    private static void LoadJsonStringIntoGenericProperty(object result, PropertyInfo currentProperty, string strVal)
 119    {
 14120        var genericType = currentProperty.PropertyType.GetGenericArguments()[0];
 121
 14122        if (genericType.IsEnum && currentProperty.GetValue(result) is IList enumList)
 123        {
 2124            enumList.Add(Enum.Parse(genericType, strVal));
 125        }
 12126        else if (genericType.IsEnum)
 127        {
 3128            currentProperty.SetValue(result, Enum.Parse(genericType, strVal));
 129        }
 9130        else if (genericType == typeof(bool))
 131        {
 1132            currentProperty.SetValue(result, bool.Parse(strVal));
 133        }
 8134        else if (genericType == typeof(string) && currentProperty.GetValue(result) is IList strList)
 135        {
 2136            strList.Add(strVal);
 137        }
 6138        else if (genericType == typeof(DateTimeOffset) && currentProperty.GetValue(result) is IList dateTimeOffsetList)
 139        {
 3140            dateTimeOffsetList.Add(DateTimeOffset.Parse(strVal));
 141        }
 3142        else if (genericType == typeof(DateTimeOffset))
 143        {
 1144            currentProperty.SetValue(result, DateTimeOffset.Parse(strVal));
 145        }
 2146        else if (genericType.IsAssignableTo(typeof(BaseModelClass)))
 147        {
 148            /*
 149             * At this point we have a string that is the URN of another Element, which we may or may not
 150             * have read yet.  For now, make a placeholder element of the type needed and the ID in the json.
 151             * Later, we're going to need to go through the objects and replace the placeholder with the real one.
 152             */
 2153            var placeHolder = Convert.ChangeType(Activator.CreateInstance(genericType, true), genericType) ??
 2154                              throw new Spdx3SerializationException($"Could not create instance of {genericType}");
 2155            var type = placeHolder.GetType();
 2156            var spdxIdProperty = type.GetProperty("SpdxId") ??
 2157                                 throw new Spdx3SerializationException("Could not get spdxId property");
 2158            spdxIdProperty.SetValue(placeHolder, new Uri(strVal));
 159
 2160            var typeProperty = type.GetProperty("Type") ??
 2161                               throw new Spdx3SerializationException("Could not get type property");
 2162            typeProperty.SetValue(placeHolder, Naming.SpdxTypeForClass(genericType));
 163
 164#pragma warning disable CS8602 // Dereference of a possibly null reference.
 2165            (currentProperty.GetValue(result) as IList).Add(placeHolder);
 166#pragma warning restore CS8602 // Dereference of a possibly null reference.
 167        }
 168        else
 169        {
 0170            throw new Spdx3SerializationException($"The type {genericType} is not supported");
 171        }
 172    }
 173
 174    /// <summary>
 175    ///     The main Write operation for this implementation of the JsonConverter
 176    /// </summary>
 177    /// <param name="writer">The writer we are writing to</param>
 178    /// <param name="value">The value we're writing</param>
 179    /// <param name="options">The options for the JsonSerializer</param>
 180    public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
 181    {
 139182        if (value == null)
 183        {
 0184            return;
 185        }
 186
 139187        var props = value.GetType().GetProperties()
 2425188            .Where(prop => Attribute.IsDefined(prop, typeof(JsonPropertyNameAttribute)));
 189
 139190        writer.WriteStartObject();
 191
 5485192        foreach (var prop in props.Where(prop => prop.GetValue(value) != null))
 193        {
 1530194            var jsonElementName = GetJsonElementNameFromPropertyAttribute(prop);
 195
 1530196            var propType = prop.PropertyType;
 1530197            var propVal = prop.GetValue(value);
 198
 199            // If it's a list of OTHER SpdxClasses, don't serialize the objects, just serialize an array of references
 1530200            if (propType.IsGenericType)
 201            {
 750202                if (propType.GenericTypeArguments[0].IsSubclassOf(typeof(BaseModelClass)) &&
 750203                    propVal is IList spdxClasses)
 204                {
 570205                    if (spdxClasses.Count > 0)
 206                    {
 212207                        WriteReferencesToListItems(writer, spdxClasses, jsonElementName);
 208                    }
 209
 212210                    continue;
 211                }
 212
 213                // If it's a list of Enum values, serialize the names of the values
 180214                if (propType.GenericTypeArguments[0].IsEnum && propVal is IList enums)
 215                {
 43216                    if (enums.Count > 0)
 217                    {
 14218                        WriteReferencesToEnumValues(writer, propType.GenericTypeArguments[0], enums, jsonElementName);
 219                    }
 220
 14221                    continue;
 222                }
 223            }
 224
 917225            WriteSimpleProperty(writer, propVal, jsonElementName);
 226        }
 227
 139228        writer.WriteEndObject();
 139229    }
 230
 231    private static void WriteSimpleProperty(Utf8JsonWriter writer, object? propVal, string jsonElementName)
 232    {
 233        switch (propVal)
 234        {
 235            case Enum:
 57236                writer.WriteString(jsonElementName, Enum.GetName(propVal.GetType(), propVal));
 57237                break;
 238            case int val:
 2239                writer.WriteNumber(jsonElementName, val);
 2240                break;
 241            case double val:
 11242                writer.WriteNumber(jsonElementName, val);
 11243                break;
 244            case bool val:
 11245                writer.WriteBoolean(jsonElementName, val);
 11246                break;
 247            case Uri val:
 148248                writer.WriteString(jsonElementName, val.ToString());
 148249                break;
 250            case DateTimeOffset val:
 66251                writer.WriteString(jsonElementName, val.UtcDateTime.ToString("yyyy-MM-ddTHH:mm:ssZ"));
 66252                break;
 253            case string val:
 404254                writer.WriteString(jsonElementName, val);
 404255                break;
 256            case BaseModelClass:
 172257                writer.WriteString(jsonElementName, (string)(propVal as dynamic).SpdxId.ToString());
 172258                break;
 259            case List<string> list:
 30260                if (list.Count > 0)
 261                {
 9262                    writer.WritePropertyName(jsonElementName);
 9263                    writer.WriteStartArray();
 9264                    list.ForEach(writer.WriteStringValue);
 9265                    writer.WriteEndArray();
 266                }
 267
 9268                break;
 269            case List<Uri> list:
 16270                if (list.Count > 0)
 271                {
 5272                    writer.WritePropertyName(jsonElementName);
 5273                    writer.WriteStartArray();
 13274                    list.ForEach(u => writer.WriteStringValue(u.ToString()));
 5275                    writer.WriteEndArray();
 276                }
 277
 5278                break;
 279            default:
 0280                throw new Spdx3Exception($"Unhandled class type {propVal?.GetType().FullName}");
 281        }
 32282    }
 283
 284    private static void WriteReferencesToListItems(Utf8JsonWriter writer, IList spdxClasses, string jsonElementName)
 285    {
 212286        writer.WritePropertyName(jsonElementName);
 212287        writer.WriteStartArray();
 288
 876289        foreach (var spdxClass in spdxClasses)
 290        {
 226291            if (spdxClass != null)
 292            {
 293#pragma warning disable CS8602 // Dereference of a possibly null reference.
 226294                writer.WriteStringValue((spdxClass as BaseModelClass).SpdxId.ToString());
 295#pragma warning restore CS8602 // Dereference of a possibly null reference.
 296            }
 297        }
 298
 212299        writer.WriteEndArray();
 212300    }
 301
 302    private static void WriteReferencesToEnumValues(Utf8JsonWriter writer, Type enumType, IList enumValues,
 303        string jsonElementName)
 304    {
 14305        writer.WritePropertyName(jsonElementName);
 14306        writer.WriteStartArray();
 307
 70308        foreach (var enumValue in enumValues)
 309        {
 21310            if (enumValue != null)
 311            {
 21312                writer.WriteStringValue(Enum.GetName(enumType, enumValue));
 313            }
 314        }
 315
 14316        writer.WriteEndArray();
 14317    }
 318
 319
 320    private static string GetJsonElementNameFromPropertyAttribute(PropertyInfo prop)
 321    {
 1530322        var jsonElementName = "";
 323
 10710324        foreach (var propAttr in prop.GetCustomAttributes())
 325        {
 3825326            if (propAttr is JsonPropertyNameAttribute)
 327            {
 1530328                jsonElementName = (propAttr as dynamic).Name;
 329            }
 330        }
 331
 1530332        return jsonElementName;
 333    }
 334
 335
 336    private static PropertyInfo GetPropertyFromJsonElementName(Type typeToConvert, string elementName)
 337    {
 1974338        foreach (var prop in typeToConvert.GetProperties())
 339        {
 6284340            foreach (var propAttr in prop.GetCustomAttributes())
 341            {
 2239342                if (propAttr is JsonPropertyNameAttribute && elementName == (propAttr as dynamic).Name)
 343                {
 84344                    return prop;
 345                }
 346            }
 347        }
 348
 0349        throw new Spdx3SerializationException(
 0350            $"Unable to get property on {typeToConvert.Name} for JSON element named {elementName}");
 84351    }
 352}