对于我正在进行的一个去中心化项目,我们需要使用服务总线在C#应用程序之间异步传递数据的能力,并决定使用JSON(使用Newtonsoft.Json
库)作为消息体格式,这样我们就可以在未来使用其他语言,而不用担心语言之间的类型差异。
我们有一个共享的接口库,这样两端都可以自由地处理他们认为合适的消息,只要他们实现了所需的接口。这需要一个自定义的JsonConverter
来处理接口类型的集合,但我们不想每次添加新的接口类型时都更新它,所以我们编写了一个允许这样做的自定义序列化程序。
这已经工作了一年多了,没有任何问题。上周,我对共享库中的另一个类型进行了一个小的更改,该类型与序列化程序和转换器类型无关。现在,由于方法调用循环(循环中调用3个方法),我们试图反序列化的每条消息都会导致StackOverflow。以下是相关代码:
/// <summary>
/// Custom Converter that allows mapping of interface types to concrete
/// types for deserialization.
/// </summary>
/// <remarks>
/// Instead of needing to create multiple JsonConverter classes
/// for each scenario, you can just call the <see cref="AddTypeMapping"/>
/// method to specify a mapping between interface and concrete type.
/// </remarks>
public class CustomJsonConverter : JsonConverter
{
private Dictionary<string, Type> _map;
public CustomJsonConverter()
{
_map = new Dictionary<string, Type>();
}
/// <summary>
/// Adds a new interface to concrete type mapping to the converter.
/// </summary>
/// <param name="interfaceTypeName">Interface type to create a mapping for.</param>
/// <param name="concreteType">Concrete type to use for interface type.</param>
public void AddTypeMapping( string interfaceTypeName, Type concreteType )
{
_map.Add( interfaceTypeName, concreteType );
}
#region JsonConverter Overrides
/// <summary>
/// Determines if we can successfully convert an object of the
/// specified type.
/// </summary>
/// <param name="objectType">Type to convert.</param>
/// <returns>
/// true if a conversion is possible; false otherwise.
/// </returns>
public override bool CanConvert( Type objectType )
{
if ( _map.Count == 0 || _map.ContainsKey( objectType.FullName ) )
{
return true;
}
return false;
}
/// <summary>
/// Gets a value indicating whether this <see cref="JsonConverter"/> can write JSON.
/// </summary>
/// <value><c>true</c> if this <see cref="JsonConverter"/> can write JSON; otherwise, <c>false</c>.</value>
/// <remarks>
/// In this case (inside a custom converter), we need to return <c>false</c>
/// otherwise we end up in a 'self referencing loop error' when handing
/// control back to the default JsonSerializer.
/// </remarks>
public override bool CanWrite
{
get { return false; }
}
/// <summary>
/// Gets a value indicating whether this <see cref="JsonConverter"/> can read JSON.
/// </summary>
/// <value><c>true</c> if this <see cref="JsonConverter"/> can read JSON; otherwise, <c>false</c>.</value>
public override bool CanRead
{
get { return true; } // THIS SEEMS TO BE THE ERROR, BUT WHY?
}
/// <summary>
/// Parses the supplied JSON data into the proper types, applying conversions where neccessary.
/// </summary>
/// <param name="reader">JSON data reader instance.</param>
/// <param name="objectType">Type to convert the JSON data into.</param>
/// <param name="existingValue"></param>
/// <param name="serializer">Instance of the serializer to use.</param>
/// <returns></returns>
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
{
if ( _map.Count == 0 )
{
return serializer.Deserialize( reader, objectType );
}
if ( _map.ContainsKey( objectType.FullName ) )
{
return serializer.Deserialize( reader, _map[objectType.FullName] );
}
throw new NotSupportedException( string.Format( "No mapping for Type '{0}' found.", objectType.FullName ) );
}
/// <summary>
/// Writes the supplied object into the JSON data stream.
/// </summary>
/// <param name="writer">JSON data writer instance.</param>
/// <param name="value">Object to serialize into a JSON data stream.</param>
/// <param name="serializer">Instance of the serializer to use.</param>
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer )
{
serializer.Serialize( writer, value );
}
#endregion
}
/// <summary>
/// Manages the serialization and deserialization of objects to and from JSON.
/// </summary>
/// <typeparam name="T">The object type to be de/serialized.</typeparam>
public class MessageSerializer<T> where T : class
{
/// <summary>Serializer instance.</summary>
private readonly JsonSerializer _serializer;
/// <summary>Custome converter instance.</summary>
private readonly CustomJsonConverter _converter;
public MessageSerializer()
{
_converter = new CustomJsonConverter();
_serializer = new JsonSerializer
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Formatting = Formatting.None,
};
_serializer.Converters.Add( _converter );
_serializer.Converters.Add( new GuidJsonConverter() );
}
#region Converters
/// <summary>
/// Adds a new interface to concrete type mapping in order to allow
/// deserialization of JSON data involving an interface.
/// </summary>
/// <param name="interfaceType">The interface type to map to concrete type.</param>
/// <param name="concreteType">The concrete type to use for the interface.</param>
public void MapType( Type interfaceType, Type concreteType )
{
_converter.AddTypeMapping( interfaceType.FullName, concreteType );
}
#endregion
#region Serialization
/// <summary>
/// Serializes an object to a JSON string.
/// </summary>
/// <param name="message">The type to be serialized.</param>
/// <returns>
/// The JSON equivalent of the supplied type.
/// </returns>
public string Serialize( T message )
{
var result = new StringBuilder();
StringWriter sw = null;
try
{
sw = new StringWriter( result );
using ( var writer = new JsonTextWriter( sw ) )
{
_serializer.Serialize( writer, message );
}
}
finally
{
if ( sw != null )
{
sw.Dispose();
}
}
return result.ToString();
}
/// <summary>
/// Deserializes a JSON string to it's original type.
/// </summary>
/// <param name="message">The JSON string to deserialize.</param>
/// <returns>
/// Instance of Object Type from the supplied JSON string.
/// </returns>
public T Deserialize( string message )
{
T obj;
StringReader sr = null;
try
{
sr = new StringReader( message );
using ( var reader = new JsonTextReader( sr ) )
{
obj = _serializer.Deserialize<T>( reader );
}
}
finally
{
if (sr != null)
{
sr.Dispose();
}
}
return obj;
}
#endregion
}
// SAMPLE USAGE:
var ser = new MessageSerializer<EmailMessage>();
ser.MapType( typeof(IEmailMessage), typeof(EmailMessage) );
var jsonData = ser.Serialize( data ); // works fine
var deserializedData = ser.Deserialize( jsonData ); // StackOverflow occurs during this call
问题似乎集中在CustomJsonConverter.CanRead
属性上。这之前已经为所有内容返回了true
,并且运行良好。现在,只有当我将其设置为返回false
(仍在测试以完全确认)时,它才会工作。
真正奇怪的是,这个属性开始返回false
,直到几乎整整一年前,它开始无缘无故地失败,直到我将其交换为true
的当前值。现在看来我必须把它切换回false
(直到明年)?我错过了什么?
原来它很简单。。。都是NuGet的错!(真的是我的)。
使用服务总线处理消息的库是作为内部NuGet包构建的,以便每个人在更新可用时都能轻松更新(自该库最初创建以来,我们对其几乎没有更改)。
原始消息没有任何接口类型的属性,因此让CanRead
属性返回false
运行良好,并且任何服务处理程序都使用NuGet包的版本1
除非CanRead
属性返回true
,并且任何处理这些消息的新服务处理程序都愉快地使用NuGet包的版本2
结果是,我们需要对核心消息库进行更改,这将影响ALL服务处理程序,因此当它们更新到最新版本时,那些使用版本1的服务处理程序停止工作。因此,我将CanRead
的逻辑更新为如下,一切都恢复正常:
/// <summary>
/// Gets a value indicating whether this <see cref="JsonConverter"/> can read JSON.
/// </summary>
/// <value><c>true</c> if this <see cref="JsonConverter"/> can read JSON; otherwise, <c>false</c>.</value>
public override bool CanRead
{
get { return _map.Count > 0; }
}
它总是很简单。。。