dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
14.7k stars 4.59k forks source link

JsonIgnoreCondition.WhenWritingNull appears to be ignored when deserializing data #104614

Open nickquednow opened 3 weeks ago

nickquednow commented 3 weeks ago

Description

I was doing some testing with default values from JSON to Class conversion, and I found out that when I put a null value in the JSON data, there was an exception that was thrown due to the data type not being nullable (found in #30795, but possibly a separate documentation bug). When looking at the Microsoft documentation, I got to the following link: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/ignore-properties#ignore-all-null-value-properties

I tried that value, and my default value was being overridden with null (which I was hoping would not be the case). Then finding very old articles about the issue, people recommended the now-obsolete (in dotnet 8) option of IgnoreNullValues=true, which did work (had the intended effect of keeping the default value of 100).

The code provided is not in an array, but was originally found to be broken when trying to deserialize to a list<HighLowTemps>.

TLDR; the JsonSerializerOptions.IgnoreNullValues=true kept a default value, while JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull did not in dotnet 8.

Reproduction Steps

using System.Text.Json;
using System.Text.Json.Nodes;

public class HighLowTemps
{
    public int? High = 100;
    public int? Low = 0;
}

public class Mains
{
    public static void Main()
    {
        // modified from: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/use-dom
        string jsonString = @"
{
  ""TemperatureRanges"": 
      {
          ""High"": null,
          ""Low"": 20
      }
}
";

        JsonNode forecastNode = JsonNode.Parse(jsonString)!;

        JsonSerializerOptions options = new JsonSerializerOptions();
        options.IncludeFields = true;
        options.DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull;
        //options.IgnoreNullValues = true;

        var test = JsonSerializer.Deserialize<HighLowTemps>(forecastNode["TemperatureRanges"]!, options)!;
        // breakpoint here. test shows null for High. switch to IgnoreNullValues, High is 100
    }
}

Expected behavior

For IgnoreNullValues

public class HighLowTemps
{
    public int? High = 100;
    public int? Low = 20;
}

For DefaultIgnoreCondition

public class HighLowTemps
{
    public int? High = 100;
    public int? Low = 20;
}

Actual behavior

For IgnoreNullValues

public class HighLowTemps
{
    public int? High = 100;
    public int? Low = 20;
}

For DefaultIgnoreCondition

public class HighLowTemps
{
    public int? High = null; <--------------- Unexpected behavior
    public int? Low = 20;
}

Regression?

No response

Known Workarounds

Use the deprecated IgnoreNullValues property

Configuration

Tested on the following (all versions from the visual studio installer running VS 2022 pro v17.10.1):

Dotnet 5.0 and all Dotnet Core versions do not have JsonNodes, so not tested.

Windows 11 feature pack 23H2. Running on x86_64

This is probably configuration agnostic.

Other information

No response

dotnet-policy-service[bot] commented 3 weeks ago

Tagging subscribers to this area: @dotnet/area-system-text-json, @gregsdennis See info in area-owners.md if you want to be subscribed.

eiriktsarpalis commented 3 weeks ago

This is expected behavior. WhenWritingNull only governs writing and not reading of null values. DefaultIgnoreCondition never ignores values. What you might be looking for is adding a JsonIgnoreCondition.WhenNull case which hasn't been implemented yet.

As a temporary workaround, you could try wrapping the JsonPropertyInfo.Set delegate so that it only sets the property when the value is not null.

nickquednow commented 3 weeks ago

Since it seems like I would have to replace the JsonSerializerOptions.TypeInfoResolver to a custom class in order to use the JsonPropertyInfo setter method, is there a different work around that would be closer to the options?

If not then I think I am just going to have to roll the dice on the obsolete property in hopes of JsonIgnoreCondition being updated in the future to have the JsonIgnoreCondition.WhenNull case.