Reflection for testing serialization

November 18, 2024    .Net AutomatedTesting

Reflection for testing serialization

an image of a mirror reflecting a computer screen full of .net C# code - generated by Microsoft CoPilot

I had a failure when a class couldn’t serialize to JSON for a logging call (old classes that mix data and functions, with dependency injection and the .Net 8 Newtonsoft update blew up. I wish I could refactor the properties from the function, but don’t have the time now for changes and testing).

We are using Newtonsoft.Json.

It was failing on this logging line we have:

if (_logMessage) op.Telemetry.Properties.Add("payload", message.SafeToJson());

Here’s the exception "System.IO.FileNotFoundException: 'Could not load file or assembly 'Mindbox.Data.Linq, Version=3.2.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.'"

I don’t want serialization to try to load files just to give us a property. So I added [JsonObject(MemberSerialization.OptIn)] and [JsonProperty]

A Class Example

using Newtonsoft.Json;
namespace Me;

[JsonObject(MemberSerialization.OptIn)]
public class MyMessage(ISomeService _someService) : base(_someService), IMessage{
   [JsonProperty]
   public Guid AccountId;

   public async Task<Result> Process(CancellationToken){
     // code to process
   }
}

SafeToJson

    public static string SafeToJson(this object value) => SafeToJson(value, Formatting.Indented);

    public static string SafeToJson(this object value, Formatting formatting)
    {
        try
        {
            var settings = new JsonSerializerSettings
            {
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore
            };

            return JsonConvert.SerializeObject(value, formatting, settings);
        }
        catch (Exception)
        {
            // could pss in ILogger and log
            return "{}";
        }
    }

The Unit Test

namespace Me.Tests;
public class IMessageSerializationTests(ITestOutputHelper _testOutputHelper)
{

    private static object GetDefaultValue(Type type)
    {
        // Return default values for value types and null for reference types
        if (type.IsValueType) { return Activator.CreateInstance(type); }
        return null;
    }

    [Fact]
    public void CanSerializeAllIMessages()
    {
        // Load the assembly
        Assembly assembly = Assembly.GetAssembly(typeof(MyClassInTheAssembly));

        // Get all types in the assembly
        Type[] types = assembly.GetTypes();

        // Filter types based on a specific condition (e.g., all classes)
        var classes = types
            .Where(t => typeof(IMessage).IsAssignableFrom(t) && t.IsClass && !t.IsAbstract)
            // skip no property messages
            .Where(t => t.Name != nameof(AMessageWithoutProperties))
            .ToList();
        var anyFailed = false;
        foreach (var type in classes)
        {
            // Prepare default values for the constructor parameters
            var constructor = type.GetConstructors().FirstOrDefault();
            var parameters = constructor.GetParameters();
            var arguments = parameters.Select(p => GetDefaultValue(p.ParameterType)).ToArray();

            // Thanks Microsoft CoPilot for help with this code :-)
            try
            {
                var instance = Activator.CreateInstance(type, arguments);
                var json = instance.SafeToJson();
                Assert.NotEqual("{}", json);
                _testOutputHelper.WriteLine($"serialized {type.FullName}");
            }
            catch (Exception ex)
            {
                _testOutputHelper.WriteLine($"failed {ex} {type.FullName}");
                anyFailed = true;
            }
        }

        Assert.False(anyFailed, "All IMessages can be serialized for the Telemetry and Json");
    }
}

We could leverage source generation to automate reflection-based tasks, enhancing performance and maintainability, but this unit test doesn’t have to be that fast.

Now I know the messages will serialize and I didn’t miss adding [JsonObject(MemberSerialization.OptIn)] for new classes.



Watch the Story for Good News
I gladly accept BTC Lightning Network tips at [email protected]

Please consider using Brave and adding me to your BAT payment ledger. Then you won't have to see ads! (when I get to $100 in Google Ads for a payout (I'm at $95.73!), I pledge to turn off ads)

Use Brave

Also check out my Resources Page for referrals that would help me.


Swan logo
Use Swan Bitcoin to onramp with low fees and automatic daily cost averaging and get $10 in BTC when you sign up.