1

I'm currently (de-)serializing JSON objects via System.Text.Json (.NET 6), but failing to easily verify if a some of the optional classes are present and if they contain values.

My structure looks like this:

class Request
{
    public string RequestID { get; set; }
    public User User { get; set; }
    ...
}
class User
{
    public string UserID { get; set; }
    public string Username { get; set; }
    public bool? SomeOptionalBoolean { get; set; }
}

The JSON object (string) is deserialized to an object that contains the Request object. However, Request may not contain a User object which would raise a NPE when accessing it anyway. Therefore, I'm currently checking each class and its properties for null values when accessing the object's properties (like request.User.UserID):

internal static bool IsUserValid(Request request)
{
    if (request.User == null || request.User.UserID == null || request.User.Username == null)
    {
        return false;
    }
    return true;
}

static void Main(string[] args)
{
    if (IsUserValid(request))
    {
        Console.WriteLine(request.User.UserID);
    }
}

This enables me to safely access the required properties, but it seems tedious and there's probably something I'm missing. How can I simplify this approach without writing validations for each and every class?

I've tried to search for solutions (like JsonRequired attributes) in Google and Stack Overflow, but couldn't find a pattern that matches my use case.

roccne
  • 25
  • 6
  • 1
    *I've tried to search for solutions (like **JsonRequired** attributes) ... but **couldn't find a pattern that matches my use case.*** -- why did [`[JsonRequired]`](https://learn.microsoft.com/en-us/dotnet/api/system.text.json.serialization.jsonrequiredattribute?view=net-7.0) (or any of the other options from MSFT's documentation page [Required properties](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/required-properties)) not meet your needs? – dbc Mar 21 '23 at 17:09
  • 2
    Also, if you just want to avoid null reference exceptions, you can use the [null conditional operator](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/member-access-operators#null-conditional-operators--and-): `request?.User?.UserID`. – dbc Mar 21 '23 at 17:12
  • 1
    @dbc OP states that .NET 6 is used and docs say that `JsonRequired` is part of .NET 7 – Guru Stron Mar 21 '23 at 17:16
  • Thank you, I think you answered all my questions! Guess I need to upgrade to .NET 7 now and just use the null conditional operator to access the class. – roccne Mar 21 '23 at 17:22
  • @roccne JFYI null-conditional does not require .NET update. – Guru Stron Mar 21 '23 at 17:25

2 Answers2

1

You can simplify this a bit by using null propagation (via null conditional operators):

static bool IsUserValid(Request request)
{
    var user = request.User;
    if (user?.UserID is null // if user is null will short-circuit the rest, so NREin following statements
        || user.Username is null
        || user.SomeOptionalBoolean is null)
    {
        return false;
    }

    return true;
}

If you are willing to sacrifice performance a bit (no short-circuiting, some additional allocations including potential boxing) you can collect all properties into a single collection and use Any:

static bool IsUserValid(Request request)
{
    var user = request.User;
    var objects = new object?[]{user?.UserID, user?.Username, user?.SomeOptionalBoolean};
    if (objects.Any(o => o is null))
    {
        return false;
    }
    
    return true;
}

Also if you can migrate to .NET 7 you can look into marking properties as required - check out the docs (exception will be thrown on deserialization if any of required properties is missing).

Another approach is using JSON Schema to validate the json, for example via packages from json-everything.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
1

I don't see a collection of Request or User but as you said this is tedious to writing validations for each and every class. So going with the example you provided you can write and add Extension Method for Request which will tie the validation method to the class:

public static class Extension
{
    public static bool IsUserValid(this Request request)
    {
        return request.User != null ? (!string.IsNullOrEmpty(request.User.UserID) && !string.IsNullOrEmpty(request.User.Username)): false;
    }
    
    public static void PrintValidUser(this Request request)
    {
        if(IsUserValid(request))
            Console.WriteLine(request.User.UserID);
    }
}

I have modified the above code to check for null/empty since it is a string also it will check for both the fields with &&.

Then use it the main the way you like:

void Main()
{
    //request
    var request = new Request() { 
        User = new User(){ UserID = "roccne12345",Username = "roccne" } 
    };

    if (request.IsUserValid())
    {
        Console.WriteLine(request.User.UserID);
    }
    
   //or
    request.PrintValidUser();
}

Vinod Srivastav
  • 3,644
  • 1
  • 27
  • 40