JsonExceptionConverter
A converter to correctly serialize exception objects.
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace NJsonSchema.Converters
{
public class JsonExceptionConverter : JsonConverter
{
private readonly DefaultContractResolver _defaultContractResolver = new DefaultContractResolver();
private readonly IDictionary<string, Assembly> _searchedNamespaces;
private readonly bool _hideStackTrace;
public override bool CanWrite => true;
public JsonExceptionConverter()
: this(true, new Dictionary<string, Assembly>())
{
}
public JsonExceptionConverter(bool hideStackTrace, IDictionary<string, Assembly> searchedNamespaces)
{
_hideStackTrace = hideStackTrace;
_searchedNamespaces = searchedNamespaces;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Exception ex = value as Exception;
if (ex != null) {
DefaultContractResolver defaultContractResolver = (serializer.ContractResolver as DefaultContractResolver) ?? _defaultContractResolver;
JObject jObject = new JObject();
jObject.Add(defaultContractResolver.GetResolvedPropertyName("discriminator"), ex.GetType().Name);
jObject.Add(defaultContractResolver.GetResolvedPropertyName("Message"), ex.Message);
jObject.Add(defaultContractResolver.GetResolvedPropertyName("StackTrace"), _hideStackTrace ? "HIDDEN" : ex.StackTrace);
jObject.Add(defaultContractResolver.GetResolvedPropertyName("Source"), ex.Source);
jObject.Add(defaultContractResolver.GetResolvedPropertyName("InnerException"), (ex.InnerException != null) ? JToken.FromObject(ex.InnerException, serializer) : null);
foreach (KeyValuePair<PropertyInfo, string> exceptionProperty in GetExceptionProperties(value.GetType())) {
object value2 = exceptionProperty.Key.GetValue(ex);
if (value2 != null)
jObject.AddFirst(new JProperty(defaultContractResolver.GetResolvedPropertyName(exceptionProperty.Value), JToken.FromObject(value2, serializer)));
}
value = jObject;
}
serializer.Serialize(writer, value);
}
public override bool CanConvert(Type objectType)
{
return typeof(Exception).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jObject = serializer.Deserialize<JObject>(reader);
if (jObject == null)
return null;
JsonSerializer jsonSerializer = new JsonSerializer();
jsonSerializer.ContractResolver = (IContractResolver)Activator.CreateInstance(serializer.ContractResolver.GetType());
FieldInfo field = GetField(typeof(DefaultContractResolver), "_sharedCache");
if (field != (FieldInfo)null)
field.SetValue(jsonSerializer.ContractResolver, false);
dynamic contractResolver = jsonSerializer.ContractResolver;
if (jsonSerializer.ContractResolver.GetType().GetRuntimeProperty("IgnoreSerializableAttribute") != (PropertyInfo)null)
contractResolver.IgnoreSerializableAttribute = true;
if (jsonSerializer.ContractResolver.GetType().GetRuntimeProperty("IgnoreSerializableInterface") != (PropertyInfo)null)
contractResolver.IgnoreSerializableInterface = true;
if (jObject.TryGetValue("discriminator", StringComparison.OrdinalIgnoreCase, out JToken value)) {
string text = value.Value<string>();
if (!objectType.Name.Equals(text)) {
Type type = Type.GetType("System." + text, false);
if (type != (Type)null)
objectType = type;
else {
foreach (KeyValuePair<string, Assembly> searchedNamespace in _searchedNamespaces) {
type = searchedNamespace.Value.GetType(searchedNamespace.Key + "." + text);
if (type != (Type)null) {
objectType = type;
break;
}
}
}
}
}
object obj = jObject.ToObject(objectType, jsonSerializer);
foreach (KeyValuePair<PropertyInfo, string> exceptionProperty in GetExceptionProperties(obj.GetType())) {
object value2 = jObject.GetValue(contractResolver.GetResolvedPropertyName(exceptionProperty.Value))?.ToObject(exceptionProperty.Key.PropertyType);
if (exceptionProperty.Key.SetMethod != (MethodInfo)null)
exceptionProperty.Key.SetValue(obj, value2);
else {
field = GetField(objectType, "m_" + exceptionProperty.Value.Substring(0, 1).ToLowerInvariant() + exceptionProperty.Value.Substring(1));
if (field != (FieldInfo)null)
field.SetValue(obj, value2);
else {
field = GetField(objectType, "_" + exceptionProperty.Value.Substring(0, 1).ToLowerInvariant() + exceptionProperty.Value.Substring(1));
if (field != (FieldInfo)null)
field.SetValue(obj, value2);
}
}
}
this.SetExceptionFieldValue(jObject, "Message", obj, "_message", contractResolver, jsonSerializer);
this.SetExceptionFieldValue(jObject, "StackTrace", obj, "_stackTraceString", contractResolver, jsonSerializer);
this.SetExceptionFieldValue(jObject, "Source", obj, "_source", contractResolver, jsonSerializer);
this.SetExceptionFieldValue(jObject, "InnerException", obj, "_innerException", contractResolver, serializer);
return obj;
}
private FieldInfo GetField(Type type, string fieldName)
{
FieldInfo declaredField = type.GetTypeInfo().GetDeclaredField(fieldName);
if (declaredField == (FieldInfo)null && type.GetTypeInfo().BaseType != (Type)null)
return GetField(type.GetTypeInfo().BaseType, fieldName);
return declaredField;
}
private IDictionary<PropertyInfo, string> GetExceptionProperties(Type exceptionType)
{
Dictionary<PropertyInfo, string> dictionary = new Dictionary<PropertyInfo, string>();
foreach (PropertyInfo item in from p in exceptionType.GetRuntimeProperties()
where p.GetMethod?.IsPublic ?? false
select p) {
JsonPropertyAttribute customAttribute = item.GetCustomAttribute<JsonPropertyAttribute>();
string value = (customAttribute != null) ? customAttribute.PropertyName : item.Name;
if (!new string[8] {
"Message",
"StackTrace",
"Source",
"InnerException",
"Data",
"TargetSite",
"HelpLink",
"HResult"
}.Contains(value))
dictionary[item] = value;
}
return dictionary;
}
private void SetExceptionFieldValue(JObject jObject, string propertyName, object value, string fieldName, IContractResolver resolver, JsonSerializer serializer)
{
FieldInfo declaredField = typeof(Exception).GetTypeInfo().GetDeclaredField(fieldName);
string jsonPropertyName = (resolver is DefaultContractResolver) ? ((DefaultContractResolver)resolver).GetResolvedPropertyName(propertyName) : propertyName;
JProperty jProperty = jObject.Properties().FirstOrDefault((JProperty p) => string.Equals(p.Name, jsonPropertyName, StringComparison.OrdinalIgnoreCase));
if (jProperty != null) {
object value2 = jProperty.Value.ToObject(declaredField.FieldType, serializer);
declaredField.SetValue(value, value2);
}
}
}
}