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.
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:
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.