9

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?

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
Arys
  • 477
  • 3
  • 12

1 Answers1

7

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);
}
Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
  • Now its crystal clear. Basically casting and treating the object directly instead of doing it using LoadFromObject. Accepted your soluction. Thanks for the nice explanation. – Arys Mar 17 '14 at 11:10
  • I want to use the JsonConverter attribute on a base class, this means that the subclass instances all have different attributes, so I cannot do something manually like you are doing here. Do you know how to fix this? – The Coding Wombat Aug 22 '21 at 11:57
  • @TheCodingWombat I would suggest posting a new question so you can properly describe what you are trying to do. Comments are not a good place to ask new questions. In the new question you can include a link back to this one if it will help provide context. – Brian Rogers Aug 22 '21 at 19:54
  • @BrianRogers I found the answer in another one of your answers, so thank you: https://stackoverflow.com/a/29281107/4798397 – The Coding Wombat Aug 22 '21 at 19:59
  • 1
    @TheCodingWombat Good to hear; I'm glad you found what you needed to solve the problem. – Brian Rogers Aug 23 '21 at 00:48