Skip to content

CA1869: "Cache and reuse 'JsonSerializerOptions' instances" - false positives in top-level programs #112247

@spydacarnage

Description

@spydacarnage

Description

CA1869 is caused by a local instance of JsonSerializerOptions being created and used once as the options argument of a Serialize or Deserialize call as this can cause performance degradation if the code is executed multiple times.

I initially found this bug in a top-level program where there was only a single Deserialize call in the whole file, so I thought it was a little strange, but upon further investigation found that this bug extends to other situations.

Reproduction Steps

There are 3 versions, all very similar, but each with potentially different solutions.

Version 1 - Top-level program

JsonSerializerOptions options = new()
{
    PropertyNameCaseInsensitive = true,
    ReadCommentHandling = JsonCommentHandling.Skip
};

var output = JsonSerializer.Deserialize<int[]>("[1,2,3]", options);

Image

Version 2 - In a class constructor:

    public Test()
    {
        JsonSerializerOptions options = new()
        {
            PropertyNameCaseInsensitive = true,
            ReadCommentHandling = JsonCommentHandling.Skip
        };

        var output = JsonSerializer.Deserialize<int[]>("[1,2,3]", options);
    }

Version 3 - In a loop inside a standard method:

    public void M()
    {
        for (int i = 0; i < 10; i++)
        {
            JsonSerializerOptions options = new()
            {
                PropertyNameCaseInsensitive = true,
                ReadCommentHandling = JsonCommentHandling.Skip
            };

            var output = JsonSerializer.Deserialize<int[]>("[1,2,3]", options);
        }
    }

Expected behavior

For version 1, I'm honestly not sure what should happen - maybe nothing, as the fix appears to make the code worse, not better.
For version 2, I could see the fixer creating a static variable to hold the options for use in each construction.
For version 3, I would hope the fixer would, at the very least, move the instance creation outside the loop, as duplicating it inside the loop doesn't improve the performance at all.

Actual behavior

After using the provided fixer, the following was produced:

Version 1 - Top-level program

JsonSerializerOptions jsonSerializerOptions = new()
{
    PropertyNameCaseInsensitive = true,
    ReadCommentHandling = JsonCommentHandling.Skip
};
JsonSerializerOptions options = jsonSerializerOptions;

var output = JsonSerializer.Deserialize<int[]>("[1,2,3]", options);

Version 2 - In a class constructor:

    public Test()
    {
        JsonSerializerOptions jsonSerializerOptions = new()
        {
            PropertyNameCaseInsensitive = true,
            ReadCommentHandling = JsonCommentHandling.Skip
        };
        JsonSerializerOptions options = jsonSerializerOptions;

        var output = JsonSerializer.Deserialize<int[]>("[1,2,3]", options);

Version 3 - In a loop inside a standard method:

    public void M()
    {
        for (int i = 0; i < 10; i++)
        {
            JsonSerializerOptions jsonSerializerOptions = new()
            {
                PropertyNameCaseInsensitive = true,
                ReadCommentHandling = JsonCommentHandling.Skip
            };
            JsonSerializerOptions options = jsonSerializerOptions;

            var output = JsonSerializer.Deserialize<int[]>("[1,2,3]", options);
        }
    }

Version 3 does also have an extra option, although after the one above, which alters the method to allow a JsonSerializerOptions instance to be passed in as a parameter, which is much better.

Regression?

The problem seems to also be in NET8, but not in NET7.

Known Workarounds

Ignore or suppress the rule.

Configuration

Using VS 17.13.0 Preview 4 in a NET9 console application, running on Windows 11 x64.

Other information

No response

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions