JsonConvert.SerializeObject on an exception loses the message

1

Never seen this before, basically my MSMQ call is throwing an access denied, which in visual studio appears as this (exception.ToString())

System.Messaging.MessageQueueException (0x80004005): Access to Message Queuing system is denied.\r\n at System.Messaging.MessageQueue.MQCacheableInfo.get_WriteHandle()\r\n
at System.Messaging.MessageQueue.StaleSafeSendMessage(MQPROPS properties, IntPtr transaction)\r\n at System.Messaging.MessageQueue.SendInternal(Object obj, MessageQueueTransaction internalTransaction, MessageQueueTransactionType transactionType)\r\n at redacted.d__2.MoveNext() in c:\path\file.cs:line 24

Now this gets logged in a call to a class library that contains my logging code.

My code does this:

var logMessage = new LogEntry(applicationName, exception, message, level);

LogEntry is just a plain old class that puts those things into properties, doesn't do anything else.

var logEntryAsJson = JsonConvert.SerializeObject(logMessage);

This line should serialize it to JSON. When it does however, the exception property changes to this:

"Exception":{"NativeErrorCode":-1072824283,"ClassName":"System.Messaging.MessageQueueException","Message":"External component has thrown an exception.","Data":null,"InnerException":null,"HelpURL":null,"StackTraceString":" at System.Messaging.MessageQueue.MQCacheableInfo.get_WriteHandle()\r\n at System.Messaging.MessageQueue.StaleSafeSendMessage(MQPROPS properties, IntPtr transaction)\r\n at System.Messaging.MessageQueue.SendInternal(Object obj, MessageQueueTransaction internalTransaction, MessageQueueTransactionType transactionType)\r\n at redacted.d__2.MoveNext() in c:\path\file.cs:line 24","RemoteStackTraceString":null,"RemoteStackIndex":0,"ExceptionMethod":"8\nget_WriteHandle\nSystem.Messaging, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a\nSystem.Messaging.MessageQueue+MQCacheableInfo\nSystem.Messaging.Interop.MessageQueueHandle get_WriteHandle()","HResult":-2147467259,"Source":"System.Messaging","WatsonBuckets":null}

The intellisense, hovering over the logMessage object on the serialization line, is showing the first exception data. But when serialized, it's completely changing it. As a result the reason for the error is not logged so I was scratching my head for a while trying to debug it.

Is there a reason for this behaviour? Do I have to force the exception to be stored as a string?

c#
json.net
asked on Stack Overflow Jan 30, 2020 by NibblyPig

1 Answer

1

The Exception class implements ISerializable. Json.NET recognizes this and only serializes the properties specified by the class itself. As the docs explain :

ISerializable

Types that implement ISerializable and are marked with SerializableAttribute are serialized as JSON objects. When serializing, only the values returned from ISerializable.GetObjectData are used; members on the type are ignored. When deserializing, the constructor with a SerializationInfo and StreamingContext is called, passing the JSON object's values.

In situations where this behavior is not wanted, the JsonObjectAttribute can be placed on a .NET type that implements ISerializable to force it to be serialized as a normal JSON object.

The MessageQueueException.Message property is computed at runtime and loads a string from .NET runtime's resources. It's not saved by GetObjectData because the actual text is neither needed nor wanted - when deserialized on another system, Message will return the localized string available for that system.

External component has thrown an exception comes from the parent ExternalException class, which stores a string in Message but doesn't override GetObjectData.

This isn't a problem for serialization and deserialization, as the data is sufficient to rehydrate the correct object. It's a problem with JSON though, which doesn't contain type information.

This behavior can be disabled. One way to do this is to add attributes to the class, which isn't possible with BCL classes. Another possibility is to use a Contract Resolver that ignores ISerializable. The DefaultContractResolver has properties like IgnoreSerializableInterface for this :

string json =
    JsonConvert.SerializeObject(
        exception,
        Formatting.Indented,
        new JsonSerializerSettings { 
             ContractResolver = new DefaultContractRresolver {
                   IgnoreSerializableInterface=true
        }}
    );
answered on Stack Overflow Jan 30, 2020 by Panagiotis Kanavos

User contributions licensed under CC BY-SA 3.0