正在反序列化自定义JsonConverter中的数据,导致StackOverflow



对于我正在进行的一个去中心化项目,我们需要使用服务总线在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; }
}

它总是很简单。。。

相关内容

  • 没有找到相关文章

最新更新