Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

StackOverflowException in my JsonConverter class when using [JsonConverter] attribute

I have a test class which I am trying to serialize:

public class Testing
{
    private string _name;
    private string _firstname = "firstname";
    public string _lastname;
    private DateTime _datenow = DateTime.Now;
    public DateTime _birthdate = DateTime.Now;

    public string Name { get { return _name; } set { _name = value; } }
}

I am using a custom JsonConverter to handle the serialization for the test class:

public class TestingConverter : JsonConverter
{
    private Type[] _types = new Type[] { typeof(Testing)};

    /// <summary>
    /// Writes the JSON representation of the object.
    /// </summary>
    /// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
    /// <param name="value">The value.</param>
    /// <param name="serializer">The calling serializer.</param>
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        try
        {
            JToken t = JToken.FromObject(value); //This is what i want to do
            //rest of code
        }
        catch (Exception ex)
        {
            Console.Write(ex.Message);
            throw;
        }
    }

    public override bool CanRead
    {
        get 
        { 
            return false;
        }
    }
}

If I perform the serialization by passing the converter to the JsonSerializer object it works fine:

Testing t = new Testing();
t._lastname = "USER LAST NAME";
JsonSerializer p = JsonSerializer.CreateDefault();
p.Converters.Add(new TestingConverter());

using (StreamWriter file = File.CreateText("output.json"))
using (JsonTextWriter writer = new JsonTextWriter(file))
{
    p.Serialize(writer, t);
}

But, if I instead mark my test class with a [JsonConverter] attribute:

[JsonConverter(typeof(TestingConverter))]
public class Testing
{
    private string _name;
    private string _firstname = "firstname";
    public string _lastname;
    private DateTime _datenow = DateTime.Now;
    public DateTime _birthdate = DateTime.Now;

    public string Name { get { return _name; } set { _name = value; } }
}

and serialize like this:

Testing t = new Testing();
t._lastname = "USER LAST NAME";
JsonSerializer p = JsonSerializer.CreateDefault();


using (StreamWriter file = File.CreateText("output.json"))
using (JsonTextWriter writer = new JsonTextWriter(file))
{
   p.Serialize(writer, t);
}

my TestingConverter class is called, but it goes into a recursive loop at the JToken.FromObject(value) method and finally crashes with a StackOverflowException.

Can anyone tell me why this is happening? What am I missing?

like image 301
Arys Avatar asked Mar 14 '14 14:03

Arys


Video Answer


1 Answers

When you pass an instance of your converter to the serializer, only that instance of the serializer knows about the converter. Inside your converter, when you call JToken.FromObject(value) it uses a different serializer instance to convert the value to a JToken. That instance doesn't know about your converter, so it uses Json.Net's default serialization logic as expected. All is well.

On the other hand, if you put a [JsonConverter] attribute on a type to indicate that the type is handled by your converter, now all serializer instances know about your converter. The call to JToken.FromObject(value) inside your converter starts a new serializer instance; that instance sees that it should be using your converter to handle this object type, so it recursively calls your converter. This repeats until you run out of stack space.

If you want to use the [JsonConverter] attribute, then you need to change the internals of your converter to avoid the recursive call chain. Usually this involves handling all the individual properties of the type manually. For example, the following version will work with the attribute applied:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
    Testing t = (Testing)value;
    JObject jo = new JObject();
    jo.Add("name", t.Name);
    jo.Add("lastname", t._lastname);
    jo.Add("birthdate", t._birthdate);
    jo.WriteTo(writer);
}
like image 58
Brian Rogers Avatar answered Oct 14 '22 01:10

Brian Rogers