Thursday, February 16, 2023

System.Text.Json: Enforce Required Properties for Deserialization

I had a requirement where I want to enforce some property values to be present before deserializing the JSON string. And with .NET 7, you can do that pretty easily (another reason to move to .NET 7).

In this post, let's see how we can enforce the required properties for deserialization. There are three ways,

Failing to provide a required property will throw a JsonException.

Let's have a look.

By adding the required modifier

Consider the following POCO class. Here I have applied required modifier to FirstName and LastName.

public record Person
{
    public required string FirstName { getset; }

    public string? MiddleName { getset; }

    public required string LastName { getset; }
}

And let's try to deserialize the following string.

string @string = """
    {
        "FirstName": "John",
        "LastName": "Doe"
    }
    """
;

try
{
    Person? person = JsonSerializer.Deserialize<Person>(@string);
}
catch (JsonException jsonException)
{
    Console.WriteLine(jsonException.Message);
}

The above code is successfully deserializing the string into a person. Because the string has required properties set.

Now let's try the below. I am removing the LastName from JSON string.

string @string = """
    {
        "FirstName": "John"
    }
    """
;

try
{
    Person? person = JsonSerializer.Deserialize<Person>(@string);
}
catch (JsonException jsonException)
{
    Console.WriteLine(jsonException.Message);
    // JSON deserialization for type 'Person' was missing required properties, including the following: LastName
}

This will fail because our string doesn't contain the LastName property.

By annotating it with JsonRequiredAttribute

We can achieve the same result as above by using JsonRequiredAttribute. So instead of adding required modifier, we can decorate the property with JsonRequiredAttribute.

public record Person
{
    [JsonRequired]
    public string FirstName { getset; }

    public string? MiddleName { getset; }

    [JsonRequired]
    public string LastName { getset; }
}

By modifying the JsonPropertyInfo.IsRequired property of the contract model

This is kind of a more advanced approach.

string @string = """
    {
        "FirstName": "John"
    }
    """
;

var jsonSerializerOptions = new JsonSerializerOptions
{
    TypeInfoResolver = new DefaultJsonTypeInfoResolver
    {
        Modifiers =
        {
            static typeInfo =>
            {
                if (typeInfo.Kind != JsonTypeInfoKind.Object)
                {
                    return;
                }

                foreach (JsonPropertyInfo propertyInfo in typeInfo.Properties)
                {
                    propertyInfo.IsRequired = propertyInfo.Name switch
                    {
                        nameof(Person.FirstName) or nameof(Person.LastName) => true,
                        _ => false
                    };
                }
            }
        }
    }
};

try
{
   // This time we are passing the jsonSerializerOptions
    Person? person = JsonSerializer.Deserialize<Person>(@string, jsonSerializerOptions);
}
catch (JsonException jsonException)
{
    Console.WriteLine(jsonException.Message);
    // JSON deserialization for type 'Person' was missing required properties, including the following: LastName
}

The above will also throw a JsonException with a message saying Required properties aren't set.

Hope this helps. 

Happy Coding.

Regards,
Jaliya

No comments:

Post a Comment