System.Text.Json Cannot access a dispose jsonDocument

Viewed 752

could someone help me how to fix this error? I can't resolve this until now. I can't figure out where the problem is.

"Cannot access a disposed object. Object name: 'JsonDocument'"

I just started to use "Sytem.Text.Json" that's why I'm still learning and want to to know how to use it properly.

Thank you.

  public static async Task<JsonElement> ParseJsonData(string api, CancellationToken ct)
    {
        clientHandler = new HttpClientHandler()
        {
            UseProxy = Proxy.IsUseProxy ? true : false,
            Proxy = Proxy.IsUseProxy ? new WebProxy($"{Proxy.ProxyHost}:{Proxy.ProxyPort}") : null,
            //ServerCertificateCustomValidationCallback = (sender, certificate, chain, errors) => { return true; },
            // SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls
        };
        var uri = new Uri(api, UriKind.Absolute);
        utils.SetConnection(uri);
        client = new HttpClient(clientHandler);
        using (var request = new HttpRequestMessage(HttpMethod.Get, uri))
        {
            AddRequestHeaders(request, uri);
            return await ResponseMessage(request, ct);
        }
    }
    private static async Task<JsonElement> ResponseMessage(HttpRequestMessage request, CancellationToken ct)
    {
        using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, ct).ConfigureAwait(false))
        {
            ct.ThrowIfCancellationRequested();
 
            using (var content = response.Content)
            {
                var stream = await content.ReadAsStreamAsync().ConfigureAwait(false);
                var json = await ParseStream(stream, response);

                return json.RootElement;
            }
        }
    }

    private static async Task<JsonDocument> ParseStream(Stream stream, HttpResponseMessage response)
    {
        if (stream == null || stream.CanRead == false)
        {
            return default;
        }

        HttpStatusCode status = response.StatusCode;
        StatusCode.status = status.ToString();
        StatusCode.value = (int)status;

        using (var json = await JsonDocument.ParseAsync(stream).ConfigureAwait(false))
        {
            if (!response.IsSuccessStatusCode)
            {
                throw new ApiException()
                {
                    Content = json.RootElement.ToString(),
                    StatusCode = status.ToString(),
                    value = (int)status,
                };
            }
            return json;
        }
    }

UPDATE: (Here's what I've tried)

     public static async Task<JsonDocument> ParseJsonData(string api, CancellationToken ct)
        {
            clientHandler = new HttpClientHandler()
            {
                UseProxy = Proxy.IsUseProxy ? true : false,
                Proxy = Proxy.IsUseProxy ? new WebProxy($"{Proxy.ProxyHost}:{Proxy.ProxyPort}") : null,
                ServerCertificateCustomValidationCallback = (sender, certificate, chain, errors) => { return true; }
                // SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls
            };
            var uri = new Uri(api, UriKind.Absolute);
            utils.SetConnection(uri);
            client = new HttpClient(clientHandler);

            using (var request = new HttpRequestMessage(HttpMethod.Get, uri))
            {
                AddRequestHeaders(request, uri);
                return await ResponseMessage(request, ct);
            }
        }
        private static async Task<JsonDocument> ResponseMessage(HttpRequestMessage request, CancellationToken ct)
        {
            using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, ct).ConfigureAwait(false))
            {
                ct.ThrowIfCancellationRequested();
                HttpStatusCode status = response.StatusCode;
                
                using (var content = response.Content)
                {
                    var stream = await content.ReadAsStreamAsync().ConfigureAwait(false);

                    if (stream == null || stream.CanRead == false) { return default; }

                    var options = new JsonDocumentOptions { AllowTrailingCommas = true };
                    var json = await JsonDocument.ParseAsync(stream, options).ConfigureAwait(false);
                    
                    if (!response.IsSuccessStatusCode)
                    {
                        throw new ApiException()
                        {
                            Content = json.RootElement.ToString(),
                            StatusCode = status.ToString(),
                            value = (int)status,
                        };
                    }
                    return json;
                }
            }
        }

   public static async Task<test> GetData(string id, CancellationToken ct)
    {
        string API = $"https://www.test.com/api/videos/{id}";

        using (var root = await MyClientHelper.ParseJsonData(API, ct))
        {
            var json = root.RootElement;

            //here i can access the root and dispose after

            return new test()
            {
                /////
            }
        }
    }
2 Answers

It's the way using works. When you leave a using clause, the object is disposed. That's on purpose.

So consider your code:

using (var json = await JsonDocument.ParseAsync(stream).ConfigureAwait(false))
        {
            if (!response.IsSuccessStatusCode)
            {
                throw new ApiException()
                {
                    Content = json.RootElement.ToString(),
                    StatusCode = status.ToString(),
                    value = (int)status,
                };
            }
            return json; <------ the moment you return it you also dispose it
        }

So when you try to access it outside, you are getting the error:

    var json = await ParseStream(stream, response);
    // here your object is already disposed
    return json.RootElement;

Solution: before existing the parse function, return your json. The JsonDocument object should not be used outside the using clause.

You should NOT omit to dispose of the object as a workaround: https://docs.microsoft.com/en-us/dotnet/api/system.text.json.jsondocument?view=netcore-3.1

Failure to properly dispose this object will result in the memory not being returned to the pool, which will increase GC impact across various parts of the framework.

Luckily, there's the Clone() method. So instead of:

using JsonDocument doc = JsonDocument.Parse(jsonString);
return doc; // or return doc.RootElement;`

You can do this:

using JsonDocument doc = JsonDocument.Parse(jsonString);
var root = doc.RootElement.Clone();
return root;

"Gets a JsonElement that can be safely stored beyond the lifetime of the original JsonDocument." https://docs.microsoft.com/en-us/dotnet/api/system.text.json.jsonelement.clone?view=net-5.0

Related