Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Json.NET limit MaxDepth when serializing

We're using ASP.NET WebAPI with Entity Framework (with lazy loading) and using Json.NET for serializing the data to JSON before returning the data to the client.

We are experiencing intermittent sudden spikes in memory usage which we suspect might originate with Json.NET not recognizing reference loops when serializing data (since Entity Framework might be doing some lazy loading voodoo with proxy classes which goes under the radar of Json.NET).

I thought I'd limit how deep Json.NET was allowed to go to serialize data (at least then we'd get a sensible exception when this happens so we could fix it in the data model), but I soon discovered that the MaxDepth property of JsonSerializerSettings only kicks in when DEserializing objects.

Is there any known way of imposing a limit on Json.NET when serializing?

like image 537
AndreasKnudsen Avatar asked Apr 15 '15 13:04

AndreasKnudsen


People also ask

What is Maxdepth in JSON?

Gets or sets the maximum depth allowed when reading JSON. Reading past this depth will throw a JsonReaderException. A null value means there is no maximum. The default value is null.

What is serializing in JSON?

JSON is a format that encodes objects in a string. Serialization means to convert an object into that string, and deserialization is its inverse operation (convert string -> object).

What is a JSON serialization exception?

JsonSerializationException(String, Exception) Initializes a new instance of the JsonSerializationException class with a specified error message and a reference to the inner exception that is the cause of this exception.

Does JSON serialize data?

Serialization is the process of encoding the from naive data type to JSON format. The Python module json converts a Python dictionary object into JSON object, and list and tuple are converted into JSON array, and int and float converted as JSON number, None converted as JSON null.


1 Answers

I can't think of a way to do this out-of-the-box with Json.NET, since (as you correctly observe) MaxDepth is ignored when serializing. What you could do is to subclass JsonTextWriter and do the checks yourself:

public class MaxDepthJsonTextWriter : JsonTextWriter
{
    public int? MaxDepth { get; set; }
    public int MaxObservedDepth { get; private set; }

    public MaxDepthJsonTextWriter(TextWriter writer, JsonSerializerSettings settings)
        : base(writer)
    {
        this.MaxDepth = (settings == null ? null : settings.MaxDepth);
        this.MaxObservedDepth = 0;
    }

    public MaxDepthJsonTextWriter(TextWriter writer, int? maxDepth)
        : base(writer)
    {
        this.MaxDepth = maxDepth;
    }

    public override void WriteStartArray()
    {
        base.WriteStartArray();
        CheckDepth();
    }

    public override void WriteStartConstructor(string name)
    {
        base.WriteStartConstructor(name);
        CheckDepth();
    }

    public override void WriteStartObject()
    {
        base.WriteStartObject();
        CheckDepth();
    }

    private void CheckDepth()
    {
        MaxObservedDepth = Math.Max(MaxObservedDepth, Top);
        if (Top > MaxDepth)
            throw new JsonSerializationException(string.Format("Depth {0} Exceeds MaxDepth {1} at path \"{2}\"", Top, MaxDepth, Path));
    }
}

Then, to manually generate a JSON string, you would use it like this:

var settings = new JsonSerializerSettings { MaxDepth = 10 };
string json;
try
{
    using (var writer = new StringWriter())
    {
        using (var jsonWriter = new MaxDepthJsonTextWriter(writer, settings))
        {
            JsonSerializer.Create(settings).Serialize(jsonWriter, myClass);
            // Log the MaxObservedDepth here, if you want to.
        }
        json = writer.ToString();
    }
    Debug.WriteLine(json);
}
catch (Exception ex)
{
    Debug.WriteLine(ex);
    throw;
}

Demo fiddle here.

Since your tags include web-api, if you want to do this check inside web API calls, you could follow Rick Strahl's instructions to create a custom MediaTypeFormatter for JSON: Using an alternate JSON Serializer in ASP.NET Web API; then use the code above in the OnWriteToStreamAsync method when generating the json string.

like image 197
dbc Avatar answered Sep 20 '22 14:09

dbc