Home > Enterprise >  .NET 6 Blazor Server API Call - Deserialize Nested JSON Objects Into C# Objects
.NET 6 Blazor Server API Call - Deserialize Nested JSON Objects Into C# Objects

Time:11-07

This is my first time attempting to use APIs with Blazor, and I am struggling to save info from nested JSON objects into C# objects. I have been following these guides: https://learn.microsoft.com/en-us/aspnet/core/blazor/call-web-api?view=aspnetcore-6.0&pivots=server https://alexb72.medium.com/how-to-make-an-api-call-in-blazor-server-136e4154fca6

My API returns this data:

{
    "page": 1,
    "results": [
        {
            "adult": false,
            "backdrop_path": "/c6OLXfKAk5BKeR6broC8pYiCquX.jpg",
            "genre_ids": [
                18,
                53,
                35
            ],
            "id": 550,
            "original_language": "en",
            "original_title": "Fight Club",
            "overview": "A ticking-time-bomb insomniac and a slippery soap salesman channel primal male aggression into a shocking new form of therapy. Their concept catches on, with underground \"fight clubs\" forming in every town, until an eccentric gets in the way and ignites an out-of-control spiral toward oblivion.",
            "popularity": 100.28,
            "poster_path": "/pB8BM7pdSp6B6Ih7QZ4DrQ3PmJK.jpg",
            "release_date": "1999-10-15",
            "title": "Fight Club",
            "video": false,
            "vote_average": 8.4,
            "vote_count": 25110
        },
        {
            "adult": false,
            "backdrop_path": null,
            "genre_ids": [
                28
            ],
            "id": 347807,
            "original_language": "hi",
            "original_title": "Fight Club: Members Only",
            "overview": "Four friends head off to Bombay and get involved in the mother and father of all gang wars.",
            "popularity": 2.023,
            "poster_path": "/aXFmWfWYCCxQTkCn7K86RvDiMHZ.jpg",
            "release_date": "2006-02-17",
            "title": "Fight Club: Members Only",
            "video": false,
            "vote_average": 3.4,
            "vote_count": 9
        }
    ],
    "total_pages": 2,
    "total_results": 36
}

I have configured an API call that can correctly deserialze the page, total_pages, and total_results headers. If I try to display the results header on my page as a string, I get the error "Cannot get the value of a token type 'StartArray' as a string". I would like to save the results header into a list which I can iterate through on my page, but I am not sure how to handle this StartArray data type.

My C# code for this page (I have also tried List and IEnumerable for the results variable):

@code {
    public class MovieItem
    {
        public int id { get; set; }
        public string title { get; set; }
    }

    public class APIItem
    {
        public int page { get; set; }
        public IList<MovieItem> results { get; set; }
        public int total_pages { get; set; }
        public int total_results { get; set; }
    }

    public APIItem api_item = new APIItem();

    protected override async Task OnInitializedAsync()
    {
        var request = new HttpRequestMessage(HttpMethod.Get, "https://api.themoviedb.org/3/search/movie?api_key=HIDDEN&query=fight club");
        var client = ClientFactory.CreateClient();
        var response = await client.SendAsync(request);
        if (response.IsSuccessStatusCode)
        {
            await using var responseStream = await response.Content.ReadAsStreamAsync();
            api_item = await JsonSerializer.DeserializeAsync<APIItem>(responseStream);
        }
    }
}

Finally, when I try to display the API results on my page using the following code, I get this error: System.NullReferenceException: 'Object reference not set to an instance of an object'. When I comment out the @foreach section then the rest of the data loads correctly:

    <p>@api_item.page</p>
    <p>@api_item.total_pages</p>
    <p>@api_item.total_results</p>

    @foreach (var movie in api_item.results)
    {
        @movie.id
        @movie.title
    }

I am not sure what to try next from here. If there is a better way to handle this API call in general then I would appreciate any suggestions; I went with this method simply because it's how it was documented on Microsoft's Blazor API guide. Thanks for the help.

CodePudding user response:

When the first awaited task (var response = await client.SendAsync(request);) inside OnInitializedAsync is reached, Blazor renders the UI. At this point api_item.results is still null. So you need to check if the list is not null before iterating it.

@if (api_item.results != null)
{
    @foreach (var movie in api_item.results)
    {
        @movie.id
        @movie.title
    }
}
else
{
    <p>Loading...</p>
}

The other properties do not produce exception because you have already initialized api_item with public APIItem api_item = new APIItem();.

Another approach would be this:

@if (api_item != null)
{
    <p>@api_item.page</p>    
    <p>@api_item.total_pages</p>    
    <p>@api_item.total_results</p>
    
    @foreach (var movie in api_item.results)
    {
        @movie.id
        @movie.title
    }
}
else
{
    <p>Loading...</p>
}

@code {
    public APIItem api_item = null;

    protected override async Task OnInitializedAsync()
    {
        var request = new HttpRequestMessage(HttpMethod.Get, "https://api.themoviedb.org/3/search/movie?api_key=HIDDEN&query=fight club");
        var client = ClientFactory.CreateClient();
        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            await using var responseStream = await response.Content.ReadAsStreamAsync();
            api_item = await JsonSerializer.DeserializeAsync<APIItem>(responseStream);
        }
    }
}

You can find this pattern being used in the WeatherForecast page of a Blazor project template.

Razor component lifecycle

Razor component rendering

CodePudding user response:

I would not recommend to use a ReadAsStream in this case, it is sometimes tricky. Try this

     if (response.IsSuccessStatusCode)
    {
        var json = await response.Content.ReadAsStringAsync();
         api_item = JsonSerializer.Deserialize<APIItem>(json);
    } //check data in a debuger at this point

and fix the class. Dont use any interfaces to deserilize any data.For example for IEnumerable compiler doesn't what class to create - List, Array or something else.

    public class APIItem
    {
       ...
        public List<MovieItem> results { get; set; }
        .....
    }
  • Related