'When using record types to deserialize JSON, how can I avoid null values for missing members?

I am using Newtonsoft Json to deserialize JSON into C# record types. I'll provide examples below, but the theme of the challenge here is trying to get the object footprint and semantics in code that I like, while also getting the deserialization behavior I want. Essentially, I like to avoid null values inside deserialization code as much as possible. Mainly this applies to string and collection types: If a string property is missing, I don't want null, I want "". Similarly, for collection properties that are missing, I do not want null but an empty container (e.g. Array.Empty<MyComplexType>()).

The way I used to avoid this before record types is to explicitly specify property defaults, like this:

public class ReleaseProfileData
{
    IReadOnlyCollection<TermData> Required { get; set; } = Array.Empty<TermData>();
}

I cannot do this when I convert it to the short-form record type equivalent:

public record ReleaseProfileData(
    IReadOnlyCollection<TermData> Required = Array.Empty<TermData>()
);

So I resorted to this instead:

public record ReleaseProfileData(
    IReadOnlyCollection<TermData> Required
);

However, if in my JSON data I'm missing the "required": [] property, the Required parameter here will be null. I instead want it to be an empty container.

I've considered two objects and doing a conversion between, but this feels overkill. I also looked into settings in NewtonsoftJson, such as MissingMemberHandling, NullValueHandling, etc. But none of these seem to simply say, "If a property is missing, give me an empty string or container instead of null".

I realize I can do a long-form version of my record type using init properties, but again this starts to get really verbose: Initialization is now done via brace-init style, instead of constructor. I can add a constructor, but again this makes it more verbose. And in addition to that I now need to implement Destruction methods if I want to break out my object into var (first, second, etc). It just keeps getting worse.

What is a good approach here?



Solution 1:[1]

You can define your own property with the same name to "change" the generated positional property of the record. From the docs:

If the generated auto-implemented property definition isn't what you want, you can define your own property of the same name. For example, you may want to change accessibility or mutability, or provide an implementation for either the get or set accessor. If you declare the property in your source, you must initialize it from the positional parameter of the record. If your property is an auto-implemented property, you must initialize the property.

public record ReleaseProfileData(IReadOnlyCollection<object> Required)
{
    public IReadOnlyCollection<object> Required { get; init; } = Required ?? Array.Empty<object>();
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1