Skip to content

Support type-level JsonIgnoreCondition via [JsonIgnore] on classes, structs, and interfaces#124646

Draft
Copilot wants to merge 6 commits intomainfrom
copilot/add-jsonignorecondition-type-level
Draft

Support type-level JsonIgnoreCondition via [JsonIgnore] on classes, structs, and interfaces#124646
Copilot wants to merge 6 commits intomainfrom
copilot/add-jsonignorecondition-type-level

Conversation

Copy link
Contributor

Copilot AI commented Feb 20, 2026

Description

[JsonIgnore(Condition = ...)] can now be applied at the type level to set a default JsonIgnoreCondition for all properties and fields, eliminating per-property duplication:

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
class MyPoco
{
    public string? PropA { get; set; }
    public string? PropB { get; set; }
    public string? PropC { get; set; }
}

Property-level [JsonIgnore] overrides the type-level setting. WhenWritingNull is silently skipped for non-nullable value types, matching JsonSerializerOptions.DefaultIgnoreCondition semantics. JsonIgnoreCondition.Always is rejected at the type level as invalid configuration — the reflection path throws InvalidOperationException, and the source generator emits SYSLIB1226 warning diagnostic and ignores the attribute.

Changes

  • API surface: Expanded JsonIgnoreAttribute AttributeUsage to include Class | Struct | Interface, with API compat suppressions for the AttributeUsage change in both the shared framework baseline (ApiCompatBaseline.NetCoreAppLatestStable.xml) and the package-level baseline (CompatibilitySuppressions.xml for net10.0, net462, netstandard2.0)
  • Reflection resolver (DefaultJsonTypeInfoResolver.Helpers.cs): Falls back to type-level [JsonIgnore] when no member-level attribute is present. Uses typeInfo.Type (root serialized type) so type-level attributes on derived classes apply to inherited base-class properties. Throws InvalidOperationException for JsonIgnoreCondition.Always at the type level.
  • Source generator (JsonSourceGenerator.Parser.cs): Reads type-level [JsonIgnore] in ProcessTypeCustomAttributes using cached JsonIgnoreAttributeType symbol from KnownTypeSymbols.cs, threads it through to property parsing as a fallback. Emits SYSLIB1226 diagnostic for JsonIgnoreCondition.Always at the type level.
  • Diagnostic: Added SYSLIB1226 diagnostic descriptor, resource strings (including XLF translations), and updated list-of-diagnostics.md
  • Tests: Covers WhenWritingNull, WhenWritingDefault, Always (invalid config), property-level override, struct, inheritance, and deserialization scenarios across both reflection and source gen paths. Includes source generator unit test for the SYSLIB1226 diagnostic.
Original prompt

This section details on the original issue you should resolve

<issue_title>It should be possible to specify JsonIgnoreCondition's on the type level</issue_title>
<issue_description>## Motivation

There's a substantial amount of duplication in the following code:

class MyPoco
{
    [JsonIgnore(JsonIgnoreCondition.WhenWritingNull)]
    public string? PropA { get; set; }

    [JsonIgnore(JsonIgnoreCondition.WhenWritingNull)]
    public string? PropB { get; set; }

    [JsonIgnore(JsonIgnoreCondition.WhenWritingNull)]
    public string? PropC { get; set; }
}

that could be eliminated using a type-level declaration:

[JsonIgnore(JsonIgnoreCondition.WhenWritingNull)]
class MyPoco
{
    public string? PropA { get; set; }

    public string? PropB { get; set; }

    public string? PropC { get; set; }
}

API Proposal

namespace System.Text.Json.Serialization;

-[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface | AttributeTargets.AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public partial class JsonIgnoreAttribute;

cc @stephentoub</issue_description>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

@dotnet-policy-service
Copy link
Contributor

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

Copilot AI and others added 3 commits February 20, 2026 15:14
…n resolver, source generator)

Co-authored-by: eiriktsarpalis <[email protected]>
…dd inheritance and deserialization tests

Co-authored-by: eiriktsarpalis <[email protected]>
Copilot AI changed the title [WIP] Add JsonIgnoreCondition support at type level Support type-level JsonIgnoreCondition via [JsonIgnore] on classes, structs, and interfaces Feb 20, 2026
Copilot AI requested a review from eiriktsarpalis February 20, 2026 15:36
Copy link
Member

@eiriktsarpalis eiriktsarpalis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

if (attributeType?.ContainingAssembly.Name == SystemTextJsonNamespace &&
attributeType.ToDisplayString() == JsonIgnoreAttributeFullName)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot rather than allocating a full string every type the attribute gets compared, use the existing pattern in KnownSymbols.cs (or whatever it's called) where the attribute symbol is cached for the current compilation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched to using SymbolEqualityComparer.Default.Equals(attributeType, _knownSymbols.JsonIgnoreAttributeType) with a cached JsonIgnoreAttributeType property added to KnownTypeSymbols.cs, matching the pattern used by other attribute types like JsonDerivedTypeAttributeType. Commit 1f73332.

else if (namedArgs.Length == 1 &&
namedArgs[0].Value.Type?.ToDisplayString() == JsonIgnoreConditionFullName)
{
typeIgnoreCondition = (JsonIgnoreCondition)namedArgs[0].Value.Value!;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if the JsonIgnoreCondition is Always on the type level? Surely that would be invalid configuration? Add a diagnostic, a source generator unit test and a testing covering runtime behavior for both the generator and relection-based serializers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added handling for JsonIgnoreCondition.Always at the type level:

  • Reflection path: throws InvalidOperationException (reusing SR.DefaultIgnoreConditionInvalid), consistent with JsonSerializerOptions.DefaultIgnoreCondition behavior
  • Source generator: emits SYSLIB1226 warning diagnostic and ignores the attribute (resets typeIgnoreCondition to null)
  • Source gen unit test: JsonIgnoreConditionAlwaysOnTypeWarns in JsonSourceGeneratorDiagnosticsTests.cs
  • Runtime tests: JsonIgnoreCondition_TypeLevel_Always_ThrowsInvalidOperation (virtual in base, overridden in source gen tests to verify properties serialize normally when the attribute is ignored)

Commit 1f73332.

</Suppression>
<Suppression>
<DiagnosticId>CP0015</DiagnosticId>
<Target>T:System.Text.Json.Serialization.JsonIgnoreAttribute:[T:System.AttributeUsageAttribute]</Target>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot this is still causing API compatibility errors. Please address accordingly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added CompatibilitySuppressions.xml in src/libraries/System.Text.Json/src/ for package validation (CP0015 suppression for JsonIgnoreAttribute's AttributeUsageAttribute change across net10.0, net462, and netstandard2.0 TFMs). The existing suppression in ApiCompatBaseline.NetCoreAppLatestStable.xml covers the shared framework API compat check, but the package-level validation also needs its own suppression file since System.Text.Json ships as a multi-TFM NuGet package. Commit fc730c9.

Copy link
Contributor Author

Copilot AI commented Feb 24, 2026

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/graphql
    • Triggering command: /usr/bin/gh gh pr checks 114212 --json name,state (http block)
    • Triggering command: /usr/bin/gh /usr/bin/gh pr checks 114212 --repo dotnet/runtime (http block)
    • Triggering command: /usr/bin/gh gh pr view --json number,url,headRefName (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

It should be possible to specify JsonIgnoreCondition's on the type level

2 participants