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]
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
}
}
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 "{}";
}
}
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.
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)
Also check out my Resources Page for referrals that would help me.