C# – How to ignore JSON deserialization errors

One error during deserialization can cause the whole process to fail. Consider the following JSON. The second object has invalid data (can’t convert string to int), which will result in deserialization failing:

[ { "Color":"Red", "Grams":70 }, { "Color":"Green", "Grams":"invalid" } ]
Code language: JSON / JSON with Comments (json)

With Newtonsoft, you can choose to ignore deserialization errors. To do that, pass in an error handling lambda in the settings:

using Newtonsoft.Json; var apples = JsonConvert.DeserializeObject<List<Apple>>(applesJson, new JsonSerializerSettings() { Error = (sender, error) => error.ErrorContext.Handled = true }); Console.WriteLine($"How many good apples? {apples?.Count()}");
Code language: C# (cs)

This outputs the following:

How many good apples? 1
Code language: plaintext (plaintext)

All deserialization errors are ignored, and objects with errors are excluded from the results. In other words, the “bad apples” are removed from the bunch.

System.Text.Json doesn’t have this functionality

System.Text.Json currently doesn’t have the ability to ignore all errors. If you need this functionality right now, I suggest using Newtonsoft. Otherwise you’d have to write a custom converter to attempt to do this.

It should be noted that there is a GitHub issue about adding this functionality to System.Text.Json, so it’s possible they’ll add it in the future.

What happens when an object has an error

When an object has a deserialization error:

  • It’s excluded from the results.
  • Its parent object is excluded from the results (and so on, recursively, all the way up to the root object).
  • Arrays containing the object aren’t excluded.

This functionality is useful when deserializing arrays of objects, because objects in an array are independent of each other, allowing you to filter out objects with errors while preserving the rest.

You can also use this to suppress exceptions when deserializing a single JSON object (in case you can’t or don’t want to wrap the deserialization call in a try/catch).

Note: Malformed JSON may result in it returning a null, even for arrays. It depends on where the malformation is located. So do a null check on the results.

Example – Child object with an error

Consider the following array with one Coder object. The Coder object has a Project object with an error in it (id is null). This will result in a deserialization error.

[ { "Id":1, "Project":{"Id":null, "Language":"C#"} } ]
Code language: JSON / JSON with Comments (json)

Deserialize and ignore errors:

var coders = JsonConvert.DeserializeObject<List<Coder>>(codersJson, new JsonSerializerSettings() { Error = (sender, error) => error.ErrorContext.Handled = true }); Console.WriteLine($"How many coders? {coders?.Count()}");
Code language: C# (cs)

This outputs the following:

How many coders? 0
Code language: plaintext (plaintext)

It returned an empty array. Errors cause objects to be excluded recursively. Hence, the child object (Code.Project) caused the parent object (Coder) to be excluded.

Example – Error object in an array

The second Movie object in the following array of movies will fail deserialization:

[ { "Title": "Terminator 2: Judgment Day", "Year": 1991 }, { "Title": "Jurassic Park", "Year": "invalid" } ]
Code language: JSON / JSON with Comments (json)

Deserialize and ignore errors:

var movies = JsonConvert.DeserializeObject<List<Movie>>(moviesJson, new JsonSerializerSettings() { Error = (sender, error) => error.ErrorContext.Handled = true }); foreach (var movie in movies ?? Enumerable.Empty<Movie>()) { Console.WriteLine($"{movie.Title} {movie.Year}"); }
Code language: C# (cs)

This outputs the following:

Terminator 2: Judgment Day was released in 1991
Code language: plaintext (plaintext)

The second Movie object had a deserialization error and it was excluded. This shows that objects in an array are independent, and only objects with errors are excluded from the results.

Example – Malformed JSON returns a null

Sometimes malformed JSON results in it returning a null. For example, consider the following malformed JSON with a # just sitting there:

[ { # "Id":1, "Project":{"Id":1, "Language":"C#"} } ]
Code language: JSON / JSON with Comments (json)

Now deserialize this while ignoring errors and check if the result is null:

var coders = JsonConvert.DeserializeObject<List<Coder>>(codersJson, new JsonSerializerSettings() { Error = (sender, error) => error.ErrorContext.Handled = true }); Console.WriteLine($"Coders is null? {coders is null}");
Code language: C# (cs)

This outputs the following:

Coders is null? True
Code language: plaintext (plaintext)

In this case, malformed JSON was detected while deserializing one of the objects in the array, and it affected the whole array and returned a null. Always null check the result.

Reporting errors

Besides being able to ignore errors, you can also use the error handler to collect errors for reporting. You can report the errors to the user, or log them, or return them in an API call. The error information is available in the ErrorContext object.

Here’s an example of reporting the errors to the user by writing them out to the console. First, take a look at the following JSON array. Both objects have errors.

[ { "Id":1, "Project":{"Id":null, "Language":"C#"} }, { "Id":"invalid", "Project":{"Id":1, "Language":"C#"} }, ]
Code language: JSON / JSON with Comments (json)

Deserialize, collecting the error information in a list, and then writing it out to the console at the end:

var errors = new List<string>(); var coders = JsonConvert.DeserializeObject<List<Coder>>(codersJson, new JsonSerializerSettings() { Error = (sender, error) => var errors = new List<string>(); var coders = JsonConvert.DeserializeObject<List<Coder>>(codersJson, new JsonSerializerSettings() { Error = (sender, error) => { errors.Add(error.ErrorContext.Error.Message); error.ErrorContext.Handled = true; } }); foreach (var error in errors) { Console.WriteLine(error); Console.WriteLine(); }
Code language: C# (cs)

This will output the two errors:

Error converting vError converting value {null} to type 'System.Int32'. Path '[0].Project.Id', line 4, position 26. Could not convert string to integer: invalid. Path '[1].Id', line 7, position 20.
Code language: plaintext (plaintext)

2 thoughts on “C# – How to ignore JSON deserialization errors”

  1. Instead of ignoring the record, is there a way to make fields which are in error to be set as their default values e.g. Grams = null for second apple in above case?

    Reply
    • Good question! That sounds like a very robust error handling approach. Neither Newtonsoft nor System.Text.Json handle that by default. However, you can write a custom converter to do that.

      Here’s a small example. First, let’s say you have JSON like this, where Id is an integer (hence, the second object here has an invalid value that can’t be deserialized):
      [
        {
          "Id": 1
        },
        {
          "Id": "invalid"
        }
      ]

      Here’s how you’d safely check if the Id property exists, if it’s a number, and try to get it as an integer. If all of those checks fail, set Coder.Id to whatever default value you want.

      using System.Text.Json;

      var coders = new List<Coder>();

      using (var jsonDoc = JsonDocument.Parse(codersJson))
      {
          foreach(var obj in jsonDoc.RootElement.EnumerateArray())
          {
              Coder newCoder = new Coder();

              if (obj.TryGetProperty("Id", out JsonElement property) 
                  && property.ValueKind == JsonValueKind.Number
                  && property.TryGetInt32(out int i))
              {
                  newCoder.Id = i;
              }
              else
              {
                  newCoder.Id = -1; //or "default", or whatever special value you want as the default
              }

              coders.Add(newCoder);
          }
      }

      foreach(var coder in coders)
      {
          Console.WriteLine(coder.Id);
      }

      This outputs:
      1
      -1 (because the second object had an invalid value and was set to the default of -1)

      Reply

Leave a Reply to Ashish Cancel reply