Search code examples
c#jsonidisposable.net-core-3.0system.text.json

Am I creating a leak here?


I am using the new JsonSerializer from NETCore 3.0's System.Text.Json namespace to deserialize Json documents like this:

var result = JsonSerializer.Deserialize<Response>(json, options);

Response is defined as:

public class Response
{
    public string Foo { get; set; }
    public JsonElement Bar { get; set; }
}

The fact that JsonDocument implements IDisposable makes me wonder if by keeping a reference to an element (Bar) that can be contained in a JsonDocument, will create memory leaks?

Be advised that in general I avoid storing data as kind of "variant" type like this. Unfortunately the structure of the Bar property value is unknown at compile time.

My suspicion stems from System.Text.Json advertised strength's of lazy evaluation and I'm not sure if that involves deferred I/O.


Solution

  • From a brief investigation of sources (https://github.com/dotnet/corefx/blob/master/src/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs) it seems that JsonDocument Dispose returns "rented" bytes to shared array pool and does some general cleanup. Some instances of JsonDocument are marked as not disposable and in this case Dispose will not do anything. You can check this flag for your instance using reflection - if your instance doesn't have internal IsDisposable flag set to true there is no need to worry, because Dispose will not do anything anyway.

    I think in normal scenario, JsonDocument parser should clean after itself and there should be no rented bytes left or internal data after parser is done.

    It's always safe to not rely on specific implementation though as it may change and store only references to elements needed. You should probably remap JSON elements to your model, I think that's the whole purpose of JSON deserialization

    Quick test:

            var parentField = result.Bar.GetType().GetMember("_parent", MemberTypes.Field, BindingFlags.Instance | BindingFlags.NonPublic)[0] as FieldInfo;
            var parentDocument = parentField.GetValue(result.Bar);
    
            var isDisposableProperty = parentDocument.GetType().GetProperty("IsDisposable", BindingFlags.Instance | BindingFlags.NonPublic) as PropertyInfo;
            Console.WriteLine(isDisposableProperty.GetValue(parentDocument)); // false
    

    Proves that the instance of JsonDocument held by JsonElement is not disposable.