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

时间:2014-10-28 14:02:22

标签: c# serialization json.net

对于我正在开发的分散项目,我们需要能够使用服务总线异步传递C#应用程序之间的数据,并决定使用JSON(使用Newtonsoft.Json库)作为消息体格式,以便我们可以将来使用其他语言而不必担心语言之间的类型差异。

我们有一个共享的接口库,这样两端都可以随意处理消息,只要它们实现了所需的接口即可。这需要一个自定义JsonConverter来处理接口类型的集合,但是每次添加新的接口类型时我们都不想更新它,因此我们编写了一个允许这样做的自定义序列化器。 / p>

这已经有一年多的工作没有问题了。上周我对共享库中的不同类型进行了一些小改动,与序列化器和转换器类型无关,现在我们尝试反序列化的每条消息都会因为方法调用循环导致 StackOverflow 在循环中调用3个方法)。以下是相关代码:

/// <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(直到明年)?我错过了什么?

1 个答案:

答案 0 :(得分:1)

事实证明这很简单......这就是NuGet的错! (真的是我的。)

用于处理使用服务总线进行消息传递的库是作为内部NuGet包构建的,以便在更新可用时让每个人都可以轻松更新(并且自最初创建以来我们对此库的更改很少)。 / p>

原始邮件没有任何属于接口类型的属性,因此CanRead属性返回false工作正常,任何服务处理程序都使用 版本1 的NuGet包。从不需要更新版本的NuGet包,因为服务也没有改变。

除非CanRead属性返回true并且处理这些消息的任何新服务处理程序都使用了 版本2 ,否则具有属性接口的较新消息将无效NuGet包的em>

我们需要对影响所有服务处理程序的核心消息传递库进行更改,以便在更新到最新版本时使用 版本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; }
}

这总是很简单...