4

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()
            {
                /////
            }
        }
    }
zacky
  • 89
  • 9
  • That's correct, you cannot access the `JsonDocument` once it is disposed. If you need to use the `RootElement` after the lifetime of the document you must [clone](https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonelement.clone?view=netcore-3.1#System_Text_Json_JsonElement_Clone) it. See: [Converting newtonsoft code to System.Text.Json in .net core 3. what's equivalent of JObject.Parse and JsonProperty](https://stackoverflow.com/a/58273914/3744182). – dbc Aug 26 '20 at 06:09
  • Ahm this is interesting about Cloning. I'm just currently reading some documentation. Thanks for this idea – zacky Aug 26 '20 at 06:28

2 Answers2

5

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://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonelement.clone?view=net-5.0

Baked Inhalf
  • 3,375
  • 1
  • 31
  • 45
4

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://learn.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.

Athanasios Kataras
  • 25,191
  • 4
  • 32
  • 61
  • Yeah I already accepted it as answer. Now I'm just trying to re-code my class. By the way so, I can't be able to create my Parse function? or do you suggest to move that code inside responsemessage? – zacky Aug 26 '20 at 06:30
  • I'm not 100% sure it'll work but I'd try returning the RootElement in the ParseStream and the just have the ResponseMessage return. – Athanasios Kataras Aug 26 '20 at 06:38
  • is it ok if you can give me example of what you said about "Solution"? I still can't figure out. because I want to separate my Parse function. – zacky Aug 26 '20 at 06:38
  • Sorry I didn't know you have replied already. I'll try now your suggestion – zacky Aug 26 '20 at 06:39
  • I still can't fix. but I'm still trying. But if I don't use "using" on my JsonDocument.ParseAsync, there is not error. Do you think this is safe? – zacky Aug 26 '20 at 07:13
  • It's not, check the microsoft documentation. It might cause serious problems with garbage collection. – Athanasios Kataras Aug 26 '20 at 07:15
  • I finally solved :) should I still post my solution? – zacky Aug 26 '20 at 07:50
  • Absolutely! You can edit the question too and add it as an update. – Athanasios Kataras Aug 26 '20 at 08:26
  • I've updated my question already with the solution. Could you check it if I'm doing right? – zacky Aug 26 '20 at 08:39