Read json file from disc
Introduction
To make sure I understand how json files are read and interpreted I decided to place a json file on the server and make sure I could read it and represent the data in a useful way.
Procedure
Add the json file to be used as a guinea pig.
- Right-click on wwwroot
- select Add
- New Item
- Json File
- save as sample-01.json
- Insert the following - note the opening and closing square brackets: (obtained from JsonEditor Online - but without the square brackets.)
[
{
"name": "Chris",
"age": 23,
"city": "New York"
},
{
"name": "Emily",
"age": 19,
"city": "Atlanta"
},
{
"name": "Joe",
"age": 32,
"city": "New York"
},
{
"name": "Kevin",
"age": 19,
"city": "Atlanta"
},
{
"name": "Michelle",
"age": 27,
"city": "Los Angeles"
},
{
"name": "Robert",
"age": 45,
"city": "Manhattan"
},
{
"name": "Sarah",
"age": 31,
"city": "New York"
}
]Now add a razor component/page to read and display the data.
- Create a new razor component in the Pages folder in the server project
- Call it 'ServerReadFromFile.razor'
- Replace all code with the following:
@page "/server-read-from-file"
@using System.Text.Json
@using System.Text.Json.Serialization
<h3>Read json file saved on disc</h3>
<h4>People</h4>
@if (people == null)
{
<p>Loading...</p>
}
else
{
@foreach (var Person in people)
{
<p>Name: @Person.Name</p>
<p>Age: @Person.Age</p>
<p>City: @Person.City</p>
}
}
@code {
protected async override Task OnInitializedAsync()
{
string pathAndFileName = @"C:\Users\chris\source\repos\BlazorAviation\BlazorAviation\wwwroot\sample-01.json";
string peopleJson = System.IO.File.ReadAllText(pathAndFileName);
people = JsonSerializer.Deserialize<Person[]>(peopleJson).ToList();
}
public partial class Person
{
[JsonPropertyName("name")]
public string Name { get; set; } = string.Empty;
[JsonPropertyName("age")]
public long Age { get; set; }
[JsonPropertyName("city")]
public string City { get; set; } = string.Empty;
}
}
The things to note about the above are:
- The full path and file name of the file to be read
- The use of "System.IO.File.ReadAllText" (instead of "Http.GetFromJsonAsync")
- The "JsonPropertyName" attribute for the model class. Json generally uses lowercase initial letters and this allows the json name to be 'converted' into the more usual C# type names.
Add the above page/component to the menu, by inserting the following in NavMenu.razor (found in the main project in the Components > Layout folder.
<div class="nav-item px-3">
<NavLink class="nav-link" href="server-read-from-file">
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Read json from file
</NavLink>
</div>Save all files and run the application. Select 'Read json from file'. The application should look like this:
Presentation
The above demonstrates that I can get the application to read a simple json file, but the presentation leaves a little to be desired. We can use a Syncfusion grid to make this better.
At the top of the file add:
@using Syncfusion.Blazor.GridsAnd replace the foreach loop with this:
<SfGrid DataSource="@people">
<GridColumns>
<GridColumn Field="@nameof(Person.Name)"
HeaderText="Name"
TextAlign="@TextAlign.Left"
Width="40">
</GridColumn>
<GridColumn Field="@nameof(Person.City)"
HeaderText="City"
TextAlign="@TextAlign.Left"
Width="40">
</GridColumn>
<GridColumn Field="@nameof(Person.Age)"
HeaderText="Age"
TextAlign="@TextAlign.Right"
Width="20">
</GridColumn>
</GridColumns>
</SfGrid>And just to satisfy my obsessive nature, I also added a horizonal line above the grid. (<hr />)
Using Http
The above method of reading the json file used 'System.IO'. This won't be possible when using a remote API data source. To test the basics we can convert the above to use Http.
At the top of the file insert:
@inject HttpClient HttpWe can now replace the three lines of code to read the data with this:
string pathAndFileName = @"C:\Users\chris\source\repos\BlazorAviation\BlazorAviation\wwwroot\sample-01.json";
people = await Http.GetFromJsonAsync<Person[]>(pathAndFileName);However, if we try running this we will get an error:
To resolve this problem we need to add the following to Program.cs. Insert before var app = builder.Build();
//Points to wwwroot for server, so static file can be read
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri("https://localhost:7277/") });Note that the localhost number will vary from system to system, so adjust accordingly (either get the port from launchSettings.json or run the application and note the port number from the browser address.)
We also need to revise the path and file name variable to remove the path. Note also that the file must now be in wwwroot. The method now becomes:
protected async override Task OnInitializedAsync()
{
string FileName = @"sample-01.json";
people = await Http.GetFromJsonAsync<Person[]>(FileName);
}Save all the files and run the app. With the Syncfusion grid control, the page should look like this:
Move model class into separate folder & file
The 'Person' class (defining the fields in the class) has been included in the code section of the razor page. This doesn't have to be so, in fact it is more normal for classes for objects to be defined separately. We will now move the 'Person' class to a separate file.
- Right-click on the server project name and add a new folder. Call it 'Models'.
- Right-click on the Models folder and add a new class item. Call it 'Person.cs'
- Copy and paste the code for the Person class from the razor page and paste it into the new Person.cs file
- There is no need for the class to be defined as 'partial', so that part can be removed.
@using System.Text.Json.Serializationshould have been inserted at the top of the file, if it hasn't, add it- Delete the Person class from the razor page
- At the top of the razor page insert the following using statement
@using BlazorAviation.Models- There is no longer any need for
@using System.Text.Json.Serializationat the top of the razor page, so this can be deleted.
Person.cs should now look like this:
using System.Text.Json.Serialization;
namespace BlazorAviation.Models
{
public class Person
{
[JsonPropertyName("name")]
public string Name { get; set; } = string.Empty;
[JsonPropertyName("age")]
public long Age { get; set; }
[JsonPropertyName("city")]
public string City { get; set; } = string.Empty;
}
}And the razor page should look like this:
@page "/server-read-from-file"
@using System.Text.Json
@using Syncfusion.Blazor.Grids
@using BlazorAviation.Models
@inject HttpClient Http
<h3>Read json file saved on disc</h3>
<h4>People</h4>
<hr />
@if (people == null)
{
<p>Loading...</p>
}
else
{
<SfGrid DataSource="@people">
<GridColumns>
<GridColumn Field="@nameof(Person.Name)"
HeaderText="Name"
TextAlign="@TextAlign.Left"
Width="40">
</GridColumn>
<GridColumn Field="@nameof(Person.City)"
HeaderText="City"
TextAlign="@TextAlign.Left"
Width="40">
</GridColumn>
<GridColumn Field="@nameof(Person.Age)"
HeaderText="Age"
TextAlign="@TextAlign.Right"
Width="20">
</GridColumn>
</GridColumns>
</SfGrid>
}
@code {
private IEnumerable<Person>? people = [];
protected async override Task OnInitializedAsync()
{
string FileName = @"sample-01.json";
people = await Http.GetFromJsonAsync<Person[]>(FileName);
}
}
Save all the files and run the application to verify that it still works.
More Complex json file
The above example uses a very simple json file; how should a more complex, nested format be handled?
Create a new json file
- Right-click on wwwroot
- select Add
- New Item
- Json File
- save as sample-02.json
- Insert the following - note the opening and closing square brackets: (obtained from JsonEditor Online - but without the square brackets.)
[
{
"name": "Chris",
"age": 23,
"address": {
"city": "New York",
"country": "America"
},
"friends": [
{
"name": "Emily",
"hobbies": [ "biking", "music", "gaming" ]
},
{
"name": "John",
"hobbies": [ "soccer", "gaming" ]
}
]
},
{
"name": "Joanna",
"age": 22,
"address": {
"city": "Oxford",
"country": "UK"
},
"friends": [
{
"name": "Sarah",
"hobbies": [ "birds", "flowers", "trees" ]
},
{
"name": "Chris",
"hobbies": [ "Coding", "Cars" ]
}
]
}
]
Model Class
To create the model classes, browse to https://app.quicktype.io and paste the above code into the left-hand pane. In the right-hand options box, select:
- Language: C#
- Serialization Framework: System Text Json
- Generated namespace: change to 'PersonComplex'
- Use T[] or List<T>: List<T> (probably doesn't matter)
- Output features: Attributes only
- Highlight the code for the classes (i.e. ignore the using statements at the top of the screen) and copy to the clipboard
In Visual Studio, right-click on the Models folder and select
- Add
- New Item
- Class
- Name it 'PersonComplex'
- Paste in the code from the clipboard, replacing the 'PersonComplex' class
Save the file.
Create a new page
- Create a new razor component in the Pages folder in the server project
- Call it 'ServerReadFromFileComplex.razor'
- Replace all code with the following:
@page "/server-read-from-file-complex"
@using System.Text.Json
@using System.Text.Json.Serialization
@using BlazorAviation.Models
@using Syncfusion.Blazor.Grids
@inject HttpClient Http
<h3>Read nested json file saved on disc</h3>
<h4>People with attributes!</h4>
<hr />
@if (people == null)
{
<p>Loading...</p>
}
else
{
<SfGrid DataSource="@people">
<GridColumns>
<GridColumn Field="@nameof(PersonComplex.Name)"
HeaderText="Name"
TextAlign="@TextAlign.Left"
Width="40">
</GridColumn>
<GridColumn Field="Address.City"
HeaderText="City"
TextAlign="@TextAlign.Left"
Width="40">
</GridColumn>
<GridColumn Field="@nameof(PersonComplex.Age)"
HeaderText="Age"
TextAlign="@TextAlign.Right"
Width="20">
</GridColumn>
</GridColumns>
</SfGrid>
}
@code {
private IEnumerable<PersonComplex>? people = [];
protected async override Task OnInitializedAsync()
{
string FileName = @"sample-02.json";
people = await Http.GetFromJsonAsync<PersonComplex[]>(FileName);
}
}We can use exactly the same technique to read the json data and populate the 'people' object, but the challenge that arises is presentation.
The change required to handle 'City' now that it is in the Address class is to change the 'GridColumn Field' so that it no longer has '@nameof' and the property is simply "Address.City".
Save the file and run the application.
The 'City' field from the Address class is a 'simple complication', in that there is a one-to-one relationship between person and address. The 'Friends' class presents more of a problem as a person can have a variable number of friends. We may return to this later.
Amadeus Hotel Data
We have probably now got sufficient knowledge to try the Amadeus Hotel data.
To obtain sample hotel data from Amadeus, open a browser,
- browse to https://developers.amadeus.com/
- Sign in
- Select 'Products'
- Select 'Hotels'
- Select 'Hotel List'
- Scroll down and select 'Go to API reference and examples'
- Select 'Search hotels in a city' by clicking the 'GET' button
- Click 'Try it out'
- Change 'Radius' to 1 (to avoid too many records)
- Scroll down and click 'Execute'
- Copy all the data returned. (ctrl-A, ctrl-C)
In Visual Studio
- Right-click on wwwroot
- select Add
- New Item
- Json File
- save as amadeus-hotel.json
Open the file and paste in all the data previously copied to the clipboard. (It will be about 2,000 lines.)
Amadeus Hotel Class
To create the model classes, browse to https://app.quicktype.io and paste the above code into the left-hand pane. In the right-hand options box, select:
- Language
- Language: C#
- Serialization Framework: System Text Json
- Generated namespace: change to 'AmadeusHotel'
- Use T[] or List
: List (probably doesn't matter) - Output features: Attributes only
- Other
- Set 'Detect enums' to 'off'
- Highlight the code for the classes (i.e. ignore the using statements at the top of the screen) and copy to the clipboard
In Visual Studio, right-click on the Models folder and select
- Add
- New Item
- Class
- Name it 'AmadeusHotel'
- Paste in the code from the clipboard, replacing the 'AmadeusHotel' class
Edit the file to make anything that looks as if it could be null into a nullable property.
Create a new page
- Create a new razor component in the Pages folder in the server project
- Call it 'ServerReadFromFileHotel.razor'
- Replace all code with the following:
References
Sample data from https://jsoneditoronline.org/indepth/datasets/json-file-example/






