Updated on

To read a JSON file in C#, open it with File.OpenRead() and hand the stream to JsonSerializer.Deserialize<T>(). Two calls, no NuGet package, and the file never becomes a string we throw away, which is why it is the fastest of the six methods we benchmark below.

The other five are worth knowing because they suit different situations: two more System.Text.Json routes through File.ReadAllText() and StreamReader, and three Newtonsoft.Json routes for projects already using that library.

We will build all six against the same sample file, then measure them.

To download the source code for the video, visit our Patreon page (YouTube Patron tier).

At the end of the article, we will compare the performance of these methods using the BenchmarkDotNet library.


VIDEO: How to Read and Parse a JSON File in C# video.


Let’s dive in.

Prepare the Environment

Before we start discussing the various methods we can use to read and parse JSON files in .NET, we need to define a sample JSON file and populate it with data:

[
    {
        "teacherId":1,
        "firstName":"Clare",
        "lastName":"Anyanwu",
        "birthYear":1987,
        "level":8,
        "courses":
        [
            {
                "name":"Biology",
                "creditUnits":3,
                "numberOfStudents":42
            },
            {
                "name":"Basic Science",
                "creditUnits":4,
                "numberOfStudents":35
            }
        ]
    }
]

This JSON file will be the input data for all the methods in this article.

How Do We Read and Parse a JSON File With Newtonsoft.Json?

Newtonsoft.Json, also called JSON.NET, is a NuGet package rather than part of .NET. Treat it as the legacy path: the right choice on a project that already depends on it, the wrong one for new code. System.Text.Json ships in the box and is the direction .NET is heading.

JsonConvert.DeserializeObject<List<Teacher>>(json) is the direct equivalent of the System.Text.Json call: read the file into a string, pass it, get typed objects back.

JsonTextReader is the streaming form. We wrap a StreamReader in it and call Deserialize<T>() on a JsonSerializer, so it pulls tokens from the file instead of a string.

JArray.Parse() is the untyped route: it hands back a JArray of JToken objects we can walk, and ToObject<Teacher>() converts a token into a class. It is the slowest of the six and by far the hungriest, because it builds a full object model of the document first.

Property names match case-insensitively here with no configuration, the difference to remember when moving code between libraries.

Read a JSON File Into a .NET Object

To read and parse a JSON file into a .NET object with Newtonsoft.Json, we can use the JsonConvert.DeserializeObject() method, which is a part of the Newtonsoft.Json library.

First, we define the Teacher class:

public class Teacher
{
    public int TeacherId { get; set; }
    public string FirstName { get; set; } = string.Empty;
    public string LastName { get; set; } = string.Empty;
    public int BirthYear { get; set; }
    public int Level { get; set; }
    public List<Course> Courses { get; set; }
}

This class contains properties that correspond to the data in the JSON file. The Courses property is a list of the type Course:

public class Course
{
    public string Name { get; set; } = string.Empty;
    public int CreditUnits { get; set; }
    public int NumberOfStudents { get; set; }
}

Now, let’s create a ReadAndParseJsonFileWithNewtonsoftJson class and a _sampleJsonFilePath variable:

public class ReadAndParseJsonFileWithNewtonsoftJson
{
    private readonly string _sampleJsonFilePath;

    public ReadAndParseJsonFileWithNewtonsoftJson(string sampleJsonFilePath)
    {
        _sampleJsonFilePath = sampleJsonFilePath;
    }
}

Here, we define a class that will contain all our methods that will use the Newtonsoft.Json library to parse our JSON data. We also define a private readonly string that points to our sample JSON file path.

Then, we define a constructor that initializes _sampleJsonFilePath with the value of the sampleJsonFilePath parameter. Classes that call our JSON file reading and parsing methods will set the value of this parameter.

Now, let’s define a UseUserDefinedObjectWithNewtonsoftJson() method:

public List<Teacher> UseUserDefinedObjectWithNewtonsoftJson()
{
    using StreamReader reader = new(_sampleJsonFilePath);
    var json = reader.ReadToEnd();
    List<Teacher> teachers = JsonConvert.DeserializeObject<List<Teacher>>(json);

    return teachers;
}

Here, we create a new StreamReader object by passing the path of our JSON file to the constructor. The using statement ensures that the StreamReader is disposed of properly when we’re done with it.

After that, we call the JsonConvert.DeserializeObject() method, passing in the JSON string and the Teacher class as the generic type parameter.

Finally, we return a list of Teacher objects deserialized from the JSON data.

We can see that using the StreamReader class and the Newtonsoft.Json library to read and parse JSON data into .NET objects is quite simple.

Read a JSON File With JsonTextReader

Now, let’s see how we can read and parse a JSON file using the JsonTextReader with the Newtonsoft.Json library in C#.

First, let’s define a UseJsonTextReaderInNewtonsoftJson() method:

public List<Teacher> UseJsonTextReaderInNewtonsoftJson()
{
    var serializer = new JsonSerializer();
    List<Teacher> teachers = new();
    using (var streamReader = new StreamReader(_sampleJsonFilePath))
    using (var textReader = new JsonTextReader(streamReader))
    {
        teachers = serializer.Deserialize<List<Teacher>>(textReader);
    }

    return teachers;
}

Here, we create a JsonSerializer object to deserialize the JSON data. Then, we also create an empty List of type Teacher to store the deserialized data.

Next, we define a StreamReader object to read the contents of the JSON file, and then use a JsonTextReader to read the JSON data from the stream reader.

Finally, we call the Deserialize() method of the JsonSerializer object, passing in the JsonTextReader. We invoke this method to convert the JSON data into a List of Teacher objects and return the list.

Read a JSON File With JArray.Parse()

Let’s see how we can use the JArray.Parse() method in Newtonsoft.Json to read and parse a JSON file. To demonstrate this, let’s define a UseJArrayParseInNewtonsoftJson() method:

public List<Teacher> UseJArrayParseInNewtonsoftJson()
{
    using StreamReader reader = new(_sampleJsonFilePath);
    var json = reader.ReadToEnd();
    var jarray = JArray.Parse(json);
    List<Teacher> teachers = new();

    foreach (var item in jarray)
    {
        Teacher teacher = item.ToObject<Teacher>();
        teachers.Add(teacher);
    }

    return teachers;
}

First, we use a StreamReader object to read the contents of the JSON file into a string variable called json.

Next, we invoke the JArray.Parse() method and pass the JSON string to it. This method parses the string into a JArray object, which is a collection of JToken objects representing the data in the JSON file.

We then create an empty list of Teacher objects called teachers.

We use a foreach loop to iterate through each JToken in the JArray. For each JToken, we call the ToObject<Teacher>() method to create a Teacher object using the properties of the JToken.

After that, we add the newly created Teacher object to the teachers list.

Finally, we return the list of teachers.

How Do We Read and Parse a JSON File With System.Text.Json?

System.Text.Json is built into .NET, so reading a JSON file into objects needs no package reference.

Every route is the same two steps: get the file’s contents, then hand them to JsonSerializer.Deserialize<T>(). What changes between them is how we get the contents.

File.ReadAllText() returns the whole file as a string. StreamReader.ReadToEnd() does the same through a reader we open ourselves. File.OpenRead() returns a FileStream and gives the stream to the deserializer, which reads it in chunks instead of building the text first.

That third route is the one to prefer. It is the fastest of the six methods here and allocates a fraction of the memory, because the file’s characters never become a string we immediately discard.

One behaviour catches people out. System.Text.Json matches property names case-sensitively, so "firstName" in the file will not bind to FirstName in our class unless we set PropertyNameCaseInsensitive to true on a JsonSerializerOptions instance and pass it in.

If we know the shape of the data up front, deserializing straight into a POCO class is the clearest way to model it.

First, let’s define a ReadAndParseJsonFileWithSystemTextJson class and a _sampleJsonFilePath variable:

public class ReadAndParseJsonFileWithSystemTextJson
{
    private readonly string _sampleJsonFilePath;

    public ReadAndParseJsonFileWithSystemTextJson(string sampleJsonFilePath)
    {
        _sampleJsonFilePath = sampleJsonFilePath;
    }
}

Then, let’s add the JsonSerializerOptions instance that turns on case-insensitive property matching:

private readonly JsonSerializerOptions _options = new()
{
    PropertyNameCaseInsensitive = true
};

When we pass the _options parameter to the JsonSerializer.Deserialize() method, the deserializer will be able to match properties regardless of their casing.

This can be useful in scenarios where our JSON file may come from different sources with different casing conventions.

Now, let’s explore the various methods for reading and parsing a JSON file.

Read a JSON File With File.ReadAllText()

First, let’s learn how to read and parse a JSON file using the File.ReadAllText() method in conjunction with the System.Text.Json library.

Let’s take a look at the UseFileReadAllTextWithSystemTextJson() method:

public List<Teacher> UseFileReadAllTextWithSystemTextJson()
{
    var json = File.ReadAllText(_sampleJsonFilePath);
    List<Teacher> teachers = JsonSerializer.Deserialize<List<Teacher>>(json, _options);

    return teachers;
}

Here, we use the File.ReadAllText() method to read the contents of the specified file into a string variable called json. This method returns the entire contents of the file as a single string.

Next, we use the JsonSerializer class to deserialize the json string into a list of Teacher objects. We do this by invoking the JsonSerializer.Deserialize<List<Teacher>>() method, which takes in the JSON string and a type argument (in this case, List<Teacher>) and we return an object of that type.

Finally, we return the deserialized list of Teacher objects.

As we can see, using the File.ReadAllText() method in combination with the JsonSerializer class allows us to quickly and easily convert JSON data into strongly-typed objects.

Read a JSON File With File.OpenRead()

Alternatively, we can use the File.OpenRead() method with System.Text.Json to read and parse JSON files in our C# applications.

To start, let’s define a UseFileOpenReadTextWithSystemTextJson() method:

public List<Teacher> UseFileOpenReadTextWithSystemTextJson()
{
    using FileStream json = File.OpenRead(_sampleJsonFilePath);
    List<Teacher> teachers = JsonSerializer.Deserialize<List<Teacher>>(json, _options);

    return teachers;
}

Here, we create a new FileStream object by calling File.OpenRead() and pass it the path of our JSON file. This method opens the file in read-only mode and returns an object, that we store in a variable named json.

We then call the static method JsonSerializer.Deserialize() to convert the FileStream object into a list of Teacher instances.

Finally, we return the list of teachers.

Read a JSON File With StreamReader

We can also use the StreamReader class to read a JSON file, and then use the System.Text.Json library to parse the returned JSON string.

To see how this works, let’s create a UseStreamReaderWithSystemTextJson() method:

public List<Teacher> UseStreamReaderWithSystemTextJson()
{
    using StreamReader streamReader = new(_sampleJsonFilePath);
    var json = streamReader.ReadToEnd();
    List<Teacher> teachers = JsonSerializer.Deserialize<List<Teacher>>(json, _options);

    return teachers;
}

In this method, we create a new instance of StreamReader and pass it the path of our JSON file.

We then call the ReadToEnd() method of the StreamReader object to read the entire contents of the JSON file as a string, which we store in a variable called json.

Next, we invoke the static method JsonSerializer.Deserialize() to convert the JSON data in the string into a list of Teacher objects.

Finally, we return the list of teachers.

How Do We Read a JSON File Without a Matching C# Class?

Sometimes there is no class to deserialize into: the shape varies between files, or we want two fields out of a large document and modelling the rest is wasted work.

JsonNode.Parse() gives System.Text.Json the untyped model that JArray.Parse() gives Newtonsoft.Json. We index into the result with property names and array positions, and GetValue<T>() pulls a typed value out at the end.

JsonDocument.Parse() is the read-only, lower-allocation alternative. It exposes a RootElement with GetProperty() and EnumerateArray(), and it is disposable, so it belongs in a using statement, because the elements it hands back are only valid while the document is alive.

The rule of thumb is short. Reach for JsonNode when the document is small or we want to change it, and JsonDocument when it is large and we only need to read a few values out of it.

Neither replaces a class when we know the shape. Deserializing into a type stays the clearer and faster option.

If we would rather work with the data as if it were a normal object, deserializing into a dynamic object instead is the alternative to this section, and there is a separate guide to iterating over the JSON objects you parsed.

Let’s add a UseJsonNodeWithSystemTextJson() method to our System.Text.Json class:

public JsonNode UseJsonNodeWithSystemTextJson()
{
    var json = File.ReadAllText(_sampleJsonFilePath);
    JsonNode teachers = JsonNode.Parse(json);

    return teachers;
}

Here, we read the file into a string and hand it to JsonNode.Parse(), which returns a JsonNode we can index into. For our sample file, teachers[0]["firstName"].GetValue<string>() returns Clare, with no Teacher class involved.

The JsonDocument route reads the same values without building a mutable model:

using JsonDocument document = JsonDocument.Parse(json);
var firstName = document.RootElement[0].GetProperty("firstName").GetString();

The using statement matters here. Once the JsonDocument is disposed, reading any JsonElement it produced throws an ObjectDisposedException.

How Do We Read a JSON File Asynchronously in C#?

Reading a file is I/O, so the modern form of every method above is asynchronous.

await JsonSerializer.DeserializeAsync<List<Teacher>>(stream, _options) is the direct counterpart of the File.OpenRead() route and the form to write in new code. The stream still comes from File.OpenRead(), the await releases the thread while the disk works, and the result is the same typed list.

The other routes have counterparts too. await File.ReadAllTextAsync() replaces File.ReadAllText(), and Newtonsoft.Json gets there through await streamReader.ReadToEndAsync() before JsonConvert.DeserializeObject<T>().

Async does not make a single read faster, and it is worth being clear about that. It frees the thread that would otherwise sit waiting on the disk, which is what matters in a web application serving many requests at once and changes nothing measurable in a console application reading one file.

There is no synchronous-over-async shortcut worth taking. Calling .Result on the returned task blocks the thread we just freed.

Let’s write the asynchronous version of the fastest method:

public async Task<List<Teacher>> UseFileOpenReadAsyncWithSystemTextJson()
{
    using FileStream json = File.OpenRead(_sampleJsonFilePath);
    List<Teacher> teachers = await JsonSerializer.DeserializeAsync<List<Teacher>>(json, _options);

    return teachers;
}

The only differences from the synchronous method are the async keyword, the await, and DeserializeAsync() in place of Deserialize(). DeserializeAsync() returns a ValueTask<T>, which we await exactly as we would a Task<T>.

Which Way of Reading a JSON File Is Fastest?

File.OpenRead() with JsonSerializer.Deserialize<T>() is the fastest of the six methods and allocates the least memory.

We measure all six with BenchmarkDotNet against a large JSON file. All three System.Text.Json methods finish ahead of all three Newtonsoft.Json methods.

Reading through the stream is what separates the winner from its two System.Text.Json siblings. File.ReadAllText() and StreamReader.ReadToEnd() each build a string holding the entire file before parsing starts, and that string is the bulk of what they allocate.

JArray.Parse() sits at the other end by a wide margin. Building a JToken tree for a document we immediately convert into classes costs us both the tree and the classes.

The ordering matters far less for a small configuration file than for a large data file. On a few kilobytes every method here finishes in well under a millisecond, and the deciding factor is which library the project already uses rather than which one wins a benchmark.

The benchmark class that measures all six methods is included with the source code for this article.

Now, let’s run our benchmark tests and inspect the results on the console:

| Method                                 | Mean      | Error    | StdDev   | Allocated |
|--------------------------------------- |----------:|---------:|---------:|----------:|
| UseFileOpenReadWithSystemTextJson      |  19.34 ms | 0.378 ms | 0.421 ms |   4.22 MB |
| UseFileReadAllTextWithSystemTextJson   |  27.22 ms | 0.414 ms | 0.345 ms |  19.33 MB |
| UseStreamReaderWithSystemTextJson      |  27.96 ms | 0.546 ms | 0.561 ms |  19.33 MB |
| UseJsonTextReaderInNewtonsoftJson      |  35.72 ms | 0.519 ms | 0.460 ms |   9.95 MB |
| UseUserDefinedObjectWithNewtonsoftJson |  51.39 ms | 1.023 ms | 2.158 ms |  25.05 MB |
| UseJArrayParseInNewtonsoftJson         | 179.23 ms | 3.540 ms | 7.989 ms |  77.72 MB |

The fastest method is UseFileOpenReadWithSystemTextJson, at an average of 19.34 ms. It also allocates the least memory of the six, at 4.22 MB.

UseFileReadAllTextWithSystemTextJson and UseStreamReaderWithSystemTextJson land close together at 27.22 ms and 27.96 ms, and both allocate 19.33 MB. That is more than four times the winner’s total, and the difference is the string each of them builds before parsing starts.

All three Newtonsoft.Json methods sit behind them. UseJsonTextReaderInNewtonsoftJson is the quickest of the three at 35.72 ms and the leanest at 9.95 MB, because it streams rather than loading the file into a string first. UseUserDefinedObjectWithNewtonsoftJson takes 51.39 ms and allocates 25.05 MB.

The slowest method by a wide margin is UseJArrayParseInNewtonsoftJson, at 179.23 ms and 77.72 MB. It builds a complete JToken tree for a document we immediately convert into Teacher objects, so we pay for the tree and the objects.

Overall, opening the file with File.OpenRead() and handing the stream to JsonSerializer.Deserialize<T>() is the best default for reading and parsing a JSON file in C#.

Diagram: two paths from a JSON file to C# objects, one through an intermediate string and one straight through a stream.

Every call in this article takes one of those two paths, so here they are side by side with what each one is for:

CallLibraryReads the file asReach for it when
JsonSerializer.Deserialize<T>(File.OpenRead(path), options)System.Text.Jsona streamThe default: fastest of the six and the lightest
await JsonSerializer.DeserializeAsync<T>(stream, options)System.Text.Jsona streamThe same, in async code: the form to write today
JsonSerializer.Deserialize<T>(File.ReadAllText(path), options)System.Text.Jsonone stringThe file is small and we want the plainest code
JsonSerializer.Deserialize<T>(reader.ReadToEnd(), options)System.Text.Jsonone stringWe are already holding a StreamReader
JsonNode.Parse(json) / JsonDocument.Parse(json)System.Text.Jsonone stringThere is no C# class to bind to
JsonConvert.DeserializeObject<T>(json)Newtonsoft.Jsonone stringThe project already depends on Newtonsoft.Json
serializer.Deserialize<T>(new JsonTextReader(reader))Newtonsoft.Jsona streamNewtonsoft.Json, and the file is large
JArray.Parse(json)Newtonsoft.Jsonone stringNewtonsoft.Json, and there is no class to bind to

The benchmark above covers the six methods measured in this article; the async and JsonNode routes read the same bytes and are listed here for comparison.

Conclusion

In this article, we explored six different methods that we can use to read and parse a JSON file in C# with examples. Additionally, we utilized the BenchmarkDotNet library to compare the performance of these methods. This article provides a comprehensive guide for developers to understand how to read and parse JSON files in C# and choose the best approach for their use case.

To learn how to perform the reverse process, that is, writing a JSON string to a file, please visit How to Write JSON Into a File in C#, and for the list-shaped version of the same job, see turning a list of objects back into JSON.

Tested with .NET 10 and Newtonsoft.Json 13.0.4.