Can ASP.NET Core deserialization set null in property if all children properties are nulls?

70 Views Asked by At

In ASP.NET Core 8, I have the CreatePerson Minimal API which receives the JSON:

{
  "name": "Peter",
  "residence": {
    "country": null,
    "status": null
  },
}

This JSON is mapped to the class CreatePersonRequest:

public record CreatePersonRequest(
  string name,
  Residence? residence
);

public record Residence(
  string Country,
  string Status
);

When the JSON is sent to the CreatePerson API, ASP.NET Core deserializes it and instantiates a CreatePersonRequest with the Residence property instantiated, but with all its child properties (Country and Status) as nulls.

In this scenario, I want the JSON deserialization to return a CreatePersonRequest object with Residence property as null.

I tried setting JsonIgnoreCondition.WhenWritingNull, but it did not work.

services.Configure<JsonOptions>(options =>
{
    options.SerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
});

How could I achieve this?

Edit: My current JSON configuration is:

services.Configure<JsonOptions>(options =>
{
options.SerializerOptions.PropertyNameCaseInsensitive = true;
options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
options.SerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower;
});

Update 1: I attempted to use TypeInfoResolver to set a residence when Country and Status properties are not null. While this approach works, it necessitates creating a setter for the Residence property in the CreatePersonRequest class. However, I would prefer to keep CreatePersonRequest as an immutable class.

public static void IgnoreIfChildrenAreNulls(JsonTypeInfo typeInfo)
{
  if (typeInfo.Type != typeof(CreatePersonRequest))
    return;
  foreach (var propertyInfo in typeInfo.Properties)
  {
    if (propertyInfo.PropertyType == typeof(Residence))
    {
      propertyInfo.Set = (createPersonRequestObject, residenceObject) =>
      {
        var createPersonRequest = (CreatePersonRequest)createPersonRequestObject;
        var residence = (Residence?)residenceObject;
        if (residence is null || (residence.Country is null && residence.Status is null))        
           return;
        // It requires that Residence has a setter
        createPersonRequest.Residence = residence;
      };
    }
  } 
}
options.SerializerOptions.TypeInfoResolver = new DefaultJsonTypeInfoResolver()
{
 Modifiers = { CreatePersonEndpoint.IgnoreIfChildrenAreNulls }
};
1

There are 1 best solutions below

6
pwoltschk On

I mean, this is a quite specific requirement, .NET did not provide this out of the box. You have to create a Custom Converter for that.

public class NullToEmptyStringConverter : JsonConverter<Residence>
{
    public override Residence Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TokenType == JsonTokenType.StartObject)
        {
            options.Converters.Clear();
            var value = JsonSerializer.Deserialize<Residence>(ref reader, options);
            if (string.IsNullOrEmpty(value.Country) && string.IsNullOrEmpty(value.Status))
            {
                return null;
            }
            return value;
        }
        throw new JsonException();
    }

    public override void Write(Utf8JsonWriter writer, Residence value, JsonSerializerOptions options)
    {
        JsonSerializer.Serialize(writer, value, options);
    }
}

And then you have to add this to your JsonDeserializer via JsonSerializerOptions

options.JsonSerializerOptions.Converters.Add(new NullToEmptyStringConverter());

or

services.AddControllers().AddJsonOptions(options =>
{
    options.JsonSerializerOptions.Converters.Add(new NullToEmptyStringConverter());
});

Happy Coding and don't forget to upvote!

PS: The JsonIgnoreCondition.WhenWritingNull option you tried only affects the serialization process, not the deserialization. It tells the serializer to ignore properties with null values when writing JSON, but it doesn’t affect how the deserializer handles null values.