Serialize Exceptions as JSON

Serialize Exceptions Using Newtonsoft.Json

public class CustomException : Exception
{
    public string Mitigation { get; }

    public CustomException(string message, string mitigation) 
        : this(message, mitigation, null)
    { }

    public CustomException(string message, string mitigation, Exception? innerException) 
        : base(message, innerException)
    {
        Mitigation = mitigation;
    }
}


[Serializable]
public class CustomVerboseException : CustomException
{
    public CustomVerboseException(string message, string mitigation) 
        : base(message, mitigation) { }
}


try
{
    throw new CustomException("Custom error", "Try later");
}
catch (CustomException ex)
{
    var json = JsonConvert.SerializeObject(ex)!;

    Assert.Contains("Mitigation", json);
}


Serialize Exceptions By Custom JsonConverter

public class SimpleExceptionConverter : JsonConverter<Exception>
{
    public override Exception? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return new NotImplementedException();
    }

    public override void Write(Utf8JsonWriter writer, Exception value, JsonSerializerOptions options)
    {
        writer.WriteStartObject();

        writer.WriteString("Error", value.Message);
        writer.WriteString("Type", value.GetType().Name);

        if (value.InnerException is { } innerException)
        {
            writer.WritePropertyName("InnerException");
            Write(writer, innerException, options);
        }

        writer.WriteEndObject();
    }
}


public class DetailExceptionConverter : JsonConverter<Exception>
{
    public override void Write(Utf8JsonWriter writer, Exception value, JsonSerializerOptions options)
    {
        writer.WriteStartObject();

        var exceptionType = value.GetType();

        writer.WriteString("ClassName", exceptionType.FullName);

        var properties = exceptionType.GetProperties()
            .Where(e => e.PropertyType != typeof(Type))
            .Where(e => e.PropertyType.Namespace != typeof(MemberInfo).Namespace)
            .ToList();

        foreach (var property in properties)
        {
            var propertyValue = property.GetValue(value, null);

            if (options.DefaultIgnoreCondition == JsonIgnoreCondition.WhenWritingNull && propertyValue == null)
                continue;

            writer.WritePropertyName(property.Name);

            JsonSerializer.Serialize(writer, propertyValue, property.PropertyType, options);
        }

        writer.WriteEndObject();
    }
}


try
{
    var innerException = new InvalidOperationException("Bad operation");

    throw new CustomException("Custom error", "Try later", innerException);
}
catch (Exception ex)
{
    JsonSerializerOptions options = new(JsonSerializerOptions.Default);
    options.Converters.Add(new SimpleExceptionConverter());

    var json = JsonSerializer.Serialize(ex, options);

    Assert.NotEmpty(json);
}


Serialize Exceptions By Object Transformation

public class ExceptionObject
{
    public string Message { get; set; }

    public string? StackTrace { get; set; }

    public ExceptionObject? InnerException { get; set; }

    public ExceptionObject(Exception exception)
    {
        Message = exception.Message;
        StackTrace = exception.StackTrace;

        if (exception.InnerException is { } innerException)
            InnerException = new ExceptionObject(innerException);
    }
}


try
{
    var innerException = new InvalidOperationException("Bad operation");

    throw new CustomException("Custom error", "Try later", innerException);
}
catch (Exception ex)
{
    var interimObject = new ExceptionObject(ex);

    var json = JsonSerializer.Serialize(interimObject);

    Assert.NotEmpty(json);
}