In this article, we delve into the different approaches to ensure a string is valid JSON in C# by providing detailed explanations and practical examples. JavaScript Object Notation, more commonly known as JSON, has become the de facto data format for interchange between web development, APIs, and cloud services. Its lightweight and human-readable format makes it popular for modern web applications and cross-platform scenarios. Consequently, ensuring the validity of JSON data is crucial to preventing errors or exceptions and maintaining data integrity.

To download the source code for this article, you can visit our GitHub repository.

Let’s start.

Why Validate JSON Data?

Invalid JSON data can lead to various issues, including:

  • Unpredictable Behavior: Invalid data can cause unexpected application behavior, leading to crashes, data corruption, and erroneous results.
  • Data loss: Invalid data can corrupt or render stored data unusable, leading to data loss and inconsistencies.

To address these concerns, validating JSON data before processing or storing it is essential. We can employ several approaches to validate JSON data in C#. Let’s explore them!

JSON Validation Interface and Testing Definitions

Let’s start by creating an interface that we will use throughout our examples:

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!
public interface IJsonValidator
{
    bool IsValid(string jsonString);
}

Our IJsonValidator interface has a single method IsValid(). It takes a JSON string as input and returns a boolean indicating whether the string is valid JSON or not.

To test each of our implementations, we will create a simple xUnit testing project. For our assertions, we are using the popular NuGet package FluentAssetsions to aid in readability. For more information on writing unit tests with xUnit, be sure to check out these articles: Unit Testing with xUnit in ASP.NET Core and Testing Exceptions in MSTest, NUnit, and xUnit

So, with our interface now defined, let’s create a simple base class to simplify the testing of each of the methods showcased in this article:

public abstract class JsonValidatorsTestBase(IJsonValidator sut)
{
    private const string SimpleValidJson = """{ "name": "Sample Name", "age": 18 }""";

    private const string SimpleInvalidJson = """{ "name": "Sample Name" "age": 18 }""";

    protected IJsonValidator _sut = sut;

    [Fact]
    public virtual void WhenGivenValidJson_ThenIsValidReturnsTrue()
    {
        // Act
        var result = _sut.IsValid(SimpleValidJson);

        // Assert
        result.Should().BeTrue();
    }

    [Fact]
    public virtual void WhenGivenInvalidJson_ThenIsValidReturnsFalse()
    {
        // Act
        var result = _sut.IsValid(SimpleInvalidJson);

        // Assert
        result.Should().BeFalse();
    }
}

We are employing the new Primary Constructors feature of .NET 8.0 to create an abstract base class which takes an IJsonValidator parameter sut (System Under Test), and uses it to execute a series of tests. By creating the base class, we can quickly create a set of common tests that each of our JSON validators should pass.

The two methods, WhenGivenValidJson_ThenIsValidReturnsTrue() and WhenGivenInvalidJson_ThenIsValidReturnsFalse(), marked with the [Fact] attribute, test the IsValid() method of the _sut instance. These methods adhere to the Arrange-Act-Assert pattern, with data preparation handled at the class level.

In the testing methods, the IsValid() method is called with specific JSON strings, and the results are asserted using FluentAssertions. A true result indicates valid JSON, while false indicates invalid JSON.

Newtonsoft.Json – A Popular Choice for JSON Parsing

JSON parsers like Newtonsoft.Json (aka Json.NET) offer built-in validation capabilities. These parsers can attempt to parse the JSON string and we use it to validate our JSON string. Let’s see an example of using this Newtonsoft.json NuGet package. But, first, we need to add it:

dotnet add package Newtonsoft.Json

Ensure Valid JSON String using JObject.Parse()

Now, let’s create a NewtonsoftUseCase class that implements our IJsonValidator interface:

public class NewtonsoftUseCase : IJsonValidator
{
    public bool IsValid(string jsonString)
    {
        try
        {
            JObject.Parse(jsonString);

            return true;
        }
        catch (JsonReaderException)
        {
            return false;
        }
    }
}

In our IsValid() method, we use the JObject.Parse() method from the JsonConvert class provided by the Newtonsoft.Json library. By attempting to parse the JSON string, we can determine its validity. If a JsonReaderException is thrown, then the string is not valid JSON.

Now, let’s test it out:

public class NewtonsoftJsonTest() : JsonValidatorsTestBase(new NewtonsoftUseCase())
{
}

To test the NewtonsoftUseCase class, we define a class in the test project and inherit from our abstract JsonValidatorsTestBase. Then we pass an instance of the class we wish to test, which in this case is NewtonsoftUseCase. That’s it! The base class will handle running the tests against our NewtonsoftUseCase object.

Using JToken.Parse() and JArray.Parse() to Ensure a Valid JSON String

The Newtonsoft.Json library provides several methods to parse JSON strings and validate their structure. JArray.Parse() and JToken.Parse() are two such methods that can be used as alternatives to JObject.Parse(). Both are located in the Newtonsoft.Json.Linq namespace.

The JArray.Parse() parses a string as a JSON array. It attempts to convert the input string into a JArray object. If the string is not a valid JSON array, the method will throw a JsonReaderException. It’s a useful method to ensure that the string defines JSON array and not just a part of it.

JToken.Parse() allows us to parse JToken objects. JToken is a generic representation of any JSON tokens, and encapsulates objects, arrays, values, properties, etc. It provides a unified interface for working with different JSON data types. Same as the JArray, if the string does not have a valid JSON format, the method will throw a JsonReaderException.

Let’s implement a simple use case for both JToken.Parse() and JArray.Parse() to validate a JSON string:

public class JTokenJArrayUseCase : IJsonValidator
{
    public bool IsValid(string jsonString)
    {
        try
        {
            if (jsonString.StartsWith('{') && jsonString.EndsWith('}'))
            {
                JToken.Parse(jsonString);
            }
            else if (jsonString.StartsWith('[') && jsonString.EndsWith(']'))
            {
                JArray.Parse(jsonString);
            }
            else
            {
                return false;
            }

            return true;
        }
        catch (JsonReaderException e)
        {
            return false;
        }
    }
}

We check whether the provided jsonString starts and ends with curly braces {} or square brackets []. Then apply the appropriate parsing methods depending on whether the JSON represents an object or array.

Let’s test the functionality of the JTokenJArrayUseCase by using the test base class that we have defined:

public class JTokenJArrayTests() : JsonValidatorsTestBase(new JTokenJArrayUseCase())
{
}

Same as before, for testing the JTokenJArrayUseCase, we define a class named JTokenJArrayTests, inheriting it from the JsonValidatorsTestBase class. Finally, we pass an instance of the class we intend to test, JTokenJArrayUseCase, to the base class.

Ensure a Valid JSON String Using System.Text.Json

Starting with .NET Core 3.0, the System.Text.Json library has become the default JSON processing framework in C#. It provides a simpler and more lightweight alternative to Newtonsoft.Json, especially for basic JSON parsing and validation tasks. Being the default framework library it also means we do not need to install any additional NuGet packages.

Let’s use it to validate a JSON string following our previous pattern:

public class SystemTextJsonUseCase : IJsonValidator
{
    public bool IsValid(string jsonString)
    {
        try
        {
            JsonDocument.Parse(jsonString);

            return true;
        }
        catch (JsonException)
        {
            return false;
        }
    }
}

In the try block, we attempt to parse the JSON string using JsonDocument.Parse(). If the parsing is successful, true is returned, otherwise, because of raising an exception, false is returned. The JsonException type comes from the System.Text.Json.

We are familiar with the testing process for this class. Once again, we create a class named SystemTextJsonTests, inheriting from JsonValidatorsTestBase, and supply an instance of SystemTextJsonUseCase to it.

Schema-Driven JSON Validation

Schema-driven JSON validation refers to a process of validating JSON data against a predefined schema. In this context, a schema is a formalized description or blueprint that defines the structure, data types, and constraints that JSON documents must adhere to. The validation process ensures that the provided JSON data complies with the desired schema.

Schema-Driven Validation Using JSchema.Parse() and JObject.IsValid()

This scenario provides a well-structured plan for verifying JSON data using Newtonsoft.Json, guaranteeing compliance with the defined schema definition. Let’s add the needed Nuget package:

dotnet add package Newtonsoft.Json.Schema

First, we need to define our desired JSON structure:

string desiredJsonSchema = @"{
    'type': 'object',
    'properties': {
        'username': {'type':'string'},
        'addresses': {'type': 'array'}
    },
    'additionalProperties': false
}";

We have a desired JSON schema with specific structural requirements. The schema dictates that the JSON object type should be an object and should have two properties: username and addresses. The username should be a string and addresses should be an array. Furthermore, we add an instruction stating that it should not contain any additional properties beyond those explicitly defined.

Now, let’s use it to validate our JSON:

public bool IsValid(string jsonString)
{
    try
    {
        var schema = JSchema.Parse(desiredJsonSchema);
        var jObject = JObject.Parse(jsonString);

        return jObject.IsValid(schema);
    }
    catch (JsonReaderException)
    {
        return false;
    }
}

We create a method to evaluate the correctness of a JSON string against a specified JSON schema. The procedure entails parsing the desiredJsonSchema to construct a new schema object. Next, we parse the jsonString and invoke an extension method called IsValid()on the result, passing in out schema object. This method verifies whether the jObject adheres to the provided schema.

If the validation succeeds, the method returns true; otherwise, it returns false. Also, if any issues arise during the parsing process, we catch the exception and return false, indicating that the provided jsonString is not valid JSON.

Testing JSON Schema-Driven Validation

The testing approach for this method differs slightly. Firstly, we declare a _sut variable of the type IJsonValidator. We assign an instance of the class we intend to test, which includes the implementation of the IsValid() method. Subsequently, we define two methods annotated with [Fact], one for testing valid JSON and another for testing behavior with an invalid JSON string:

private readonly IJsonValidator _sut = new NewtonsoftJsonSchemaUseCase();

[Fact]
public void WhenGivenValidJson_ThenIsValidReturnsTrue()
{
    // Assign
    const string jsonString = "{'username': 'Sample Username', 'addresses': ['Street 1', 'Street 2']}";
    
    // Act
    var result = _sut.IsValid(jsonString);

    // Assert
    result.Should().BeTrue();
}

[Fact]
public void WhenGivenInValidJson_ThenIsValidReturnsFalse()
{
    // Assign
    const string jsonString = "{'username': 'Sample Username', 'password': 'Sample Password'}";
    
    // Act
    var result = _sut.IsValid(jsonString);

    // Assert
    result.Should().BeFalse();
}

In the WhenGivenValidJson_ThenIsValidReturnsTrue() method, we declare a valid JSON where the schema is crucial, consisting of two parts. The first part is the username, which must be of string type, and the second part is an array of objects.

If a JSON is defined with a different schema, it will be rejected. This scenario can be tested in the WhenGivenInValidJson_ThenIsValidReturnsFalse() method by defining an invalid JSON schema.

So far, we have attempted to determine whether the given string is a valid JSON by utilizing libraries and handling exceptions. In the next section, we aim to perform JSON validation without triggering exceptions.

Ensure Valid JSON String With JSchemaValidatingReader

Another approach to JSON validation is available to us through the use Newtonsoft.Json.Schema library. Unlike basic parsing methods, with this library, we use an approach to ensure both syntactic correctness and adherence to the structure. This method involves a series of steps, from creating readers and a validating class to deserializing the JSON string. In this way, we ensure that the input string is valid JSON. So, let’s define a desired JSON schema:

string desiredJsonSchema = @"{
    'type': 'object',
    'properties': {
        'name': {'type':'string'},
        'age': {'type': 'integer'}
    },
    'additionalProperties': false
}";

The desiredJsonSchema is a blueprint for a JSON object, comprising two properties that are called name and age. The name property is expected to be of type string while age is expected to be an integer. These types dictate the kind of data that an object can have. Let’s use it in the code:

public class DeserializeUseCase : IJsonValidator
{
    public bool IsValid(string jsonString)
    {
        try
        {
            using var stringReader = new StringReader(jsonString);
            using var jsonTextReader = new JsonTextReader(stringReader);
            using var validatingReader = new JSchemaValidatingReader(jsonTextReader);

            validatingReader.Schema = JSchema.Parse(desiredJsonSchema);
            var serializer = new JsonSerializer();
            serializer.Deserialize<object>(validatingReader);

            return true;
        }
        catch (JsonReaderException e)
        {
            return false;
        }
    }
}

Our IsValid() method takes a JSON string as input and attempts to deserialize it. Instead of relying solely on JObject.Parse(), it uses a combination of classes from the Newtonsoft.Json library, specifically JsonTextReader, JSchemaValidatingReader, and JsonSerializer.

Now, how does it work? The method initializes a JsonTextReader to read the JSON string and then sets up a JSchemaValidatingReader for additional validation. The JSchemaValidatingReader is configured with a JSON Schema, created by parsing the desired JSON schema using JSchema.Parse().

Subsequently, a JsonSerializer is employed to attempt the deserialization of the JSON data using the validation reader. If the deserialization process is completed without throwing any exceptions, the method concludes that the JSON is valid and returns true. Conversely, if an exception occurs during this process, the method catches it, and in such cases, it returns false.

Testing this scenario is straightforward. Same as before, we define a test class that inherits from JsonValidatorsTestBase and provide the desired class as a parameter. The JsonValidatorsTestBase has two fields; One for valid JSON and another for invalid JSON. The valid JSON aligns with the schema defined for the desiredJsonSchema.

Schema-Driven Validation With Json.Schema

Instead of manually defining a JSON schema, which could be quite complex and prone to errors, we can leverage the power of the Fluent API to create a JSON schema in a more straightforward and maintainable way. This approach is particularly useful when dealing with complex schemas. Before using Json.Schema, we need to install it by running the NuGet package command:

dotnet add package JsonSchema.Net

First, we start by creating an instance of the JsonSchemaBuilder class, which we use to build our JSON schema:

var desiredJsonSchema = new JsonSchemaBuilder()
    .Properties(
        ("name", new JsonSchemaBuilder()
            .Type(SchemaValueType.String)
            .MinLength(10)
        ),
        ("age", new JsonSchemaBuilder()
            .Type(SchemaValueType.Integer)
        )
    )
    .Required("name")
    .Required("age");

We call the Properties method to add properties to our JSON schema. Each property is defined with a key and a value, where the value is also an instance of JsonSchemaBuilder. This allows us to specify the characteristics of each property clearly and concisely.

For the name property, we set its type to SchemaValueType.String, meaning it should contain a string value. We also enforce a minimum length of 10 characters for the name property using the MinLength method.

We follow a similar process for the age property, setting its type to SchemaValueType.Integer. No additional constraints are imposed on the age property. Now, let’s use it:

public class JsonSchemaUseCase : IJsonValidator
{
    public bool IsValid(string jsonString)
    {
        try
        {
            var jsonNode = JsonNode.Parse(jsonString);
            var evaluationResults = _desiredJsonSchema.Evaluate(jsonNode);

            return evaluationResults.IsValid;
        }
        catch (JsonException)
        {
            return false;
        }
    }
}

Inside the try block, we implement the process of validation. We use JsonNode.Parse(jsonString) to parse a JSON from a text string. The  desiredJsonSchema.Evaluate(parsedObject) line evaluates the JSON schema against a desired JSON schema. If in any way, the specified schema is incorrect, the evaluationResults.IsValid yields a false result. Additionally, encountering any parsing issues with the JSON results in an exception, leading to a false outcome for the method.

Once again we can test this class using our testing base class JsonValidatorsTestBase. Since our desiredJsonSchema lines up with the default JSON string in our base test class, we simply need to derive from the base class and pass an instance of our JsonSchemaUseCase to the base class constructor, and the job is complete.

Using Regular Expressions To Ensure a String Is Valid JSON

Although less reliable than dedicated JSON libraries, regular expressions can be useful in certain scenarios to validate JSON. We define a class similar to before, inheriting from the interface IJsonValidator, and implementing the IsValid() method:

public class RegularExpressionsUseCase : IJsonValidator
{
    private const string JsonPattern = "(?<json>{(?:[^{}]|(?<Nested>{)|(?<-Nested>}))*(?(Nested)(?!))})";

    public bool IsValid(string jsonString)
    {
        return Regex.IsMatch(jsonString, JsonPattern );
    }
}

We’ve adopted an alternative approach for JSON string validation, moving away from exception handling. Our objective is to assess the accuracy of the JSON structure without relying on exceptions. To accomplish this, we’ve implemented a more sophisticated regular expression designed specifically for checking open and closed curly braces, especially in scenarios involving multiple JSON objects, including nested ones.

This refined regular expression utilizes a balancing group mechanism, keeping track of the count of open and closing braces within the JSON object. However, it’s important to note that while this approach can identify unmatched braces, it cannot guarantee that the input string is valid JSON. If the input string aligns with the regular expression, it suggests the JSON’s structure is in accordance. Conversely, if there’s no match, the input is considered invalid JSON, and the function returns false.

Testing this use case poses a slight challenge. As previously mentioned, we establish a test class and inherit it from JsonValidatorsTestBase, passing the RegularExpressionsUseCase instance as a parameter. The valid JSON within JsonValidatorsTestBase can be effectively tested and successfully pass the defined regex. To assess invalid JSON, we need to overide the invalid JSON test case:

public class RegularExpressionsTests() : JsonValidatorsTestBase(new RegularExpressionsUseCase())
{
    public override void WhenGivenInvalidJson_ThenIsValidReturnsFalse()
    {
        // Arrange
        const string invalidJson = """{ "name": "Sample Name", "age": 18 """;

        // Act
        var result = _sut.IsValid(invalidJson);

        // Assert
        result.Should().BeFalse();
    }
}

Here in our override of WhenGivenInvalidJson_ThenIsValidReturnsFalse(), we declare an invalid JSON string lacking a closed curly brace. Subsequently, we invoke the IsValid() method and ensure a false result.

Comparison of JSON Validation Alternatives

In the realm of JSON validation, various approaches cater to distinct needs. The lightweight and customizable option of regular expressions proves beneficial for handling simple JSON structures. Newtonsoft.Json, a widely adopted library, showcases versatility by excelling in robust deserialization and effectively managing complex JSON scenarios. System.Text.Json, integrated into the .NET framework, stands out as a performant and modern solution for JSON processing.

For those inclined towards a more formalized validation process, Json.Schema introduces a schema-based method that ensures not only structural validity but also adherence to a predefined schema.

Ultimately, the choice of a validation method hinges on the specific project requirements, taking into account factors such as performance, flexibility, and the necessity for schema validation. By delving into these diverse approaches within the realm of JSON validation, one can strike a balance between simplicity and the depth of validation essential for handling JSON data intricacies.

Conclusion

In conclusion, we’ve explored various approaches to ensure the validity of a string as JSON in C#. From employing regular expressions to leveraging well-established libraries like Newtonsoft.Json and System.Text.Json, and even delving into the structured validation offered by Json.Schema, each method brings its own set of advantages and considerations. As the landscape of JSON processing in C# continues to evolve, we are empowered with a diverse toolkit, allowing us to select the most fitting solution that aligns with our unique requirements and preferences.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!