Skip to content

Add JsonDictionaryKeyFilter to support filtering property names on dictionary deserialization#124648

Draft
Copilot wants to merge 10 commits intomainfrom
copilot/support-filtering-property-names
Draft

Add JsonDictionaryKeyFilter to support filtering property names on dictionary deserialization#124648
Copilot wants to merge 10 commits intomainfrom
copilot/support-filtering-property-names

Conversation

Copy link
Contributor

Copilot AI commented Feb 20, 2026

JSON documents containing schema metadata (e.g. $schema, $id) fail to deserialize into generic dictionary types because all JSON property values must conform to TValue. This adds a JsonDictionaryKeyFilter API that lets callers skip arbitrary keys during dictionary deserialization.

Description

New API:

  • System.Text.Json.Serialization.JsonDictionaryKeyFilter — abstract filter class:
    • IgnoreMetadataNames static property: built-in filter that skips any key starting with $
    • abstract bool IgnoreKey(ReadOnlySpan<byte> utf8JsonPropertyName) — override to implement custom filtering
  • JsonSerializerOptions.DictionaryKeyFilter — wires the filter into deserialization

Usage:

// Deserialize a JSON Schema document into a dictionary, ignoring "$schema" etc.
var options = new JsonSerializerOptions
{
    DictionaryKeyFilter = JsonDictionaryKeyFilter.IgnoreMetadataNames
};

var result = JsonSerializer.Deserialize<Dictionary<string, MyType>>(json, options);
// "$schema", "$id", "$ref", etc. are silently skipped

Implementation details:

  • Fast path (!SupportContinuation && !CanContainMetadata): filter check + SkipWithVerify — safe since the entire JSON is buffered.
  • Slow path (stream/async deserialization): filter check uses TrySkipPartial with a new ReadStackFrame.SkipCurrentDictionaryKey bool for resumable continuation across buffer boundaries. Filtered keys bypass ReadDictionaryKey, so unconvertible key strings (e.g. "$schema" with TKey = int) never cause conversion errors.
  • JsonSerializerOptions equality comparer and hash code updated to include _dictionaryKeyFilter.

Existing test updates:

Two existing tests that enumerate all public settable properties of JsonSerializerOptions via reflection were updated to account for the new DictionaryKeyFilter property:

  • CacheTests.JsonSerializerOptions_EqualityComparer_ChangingAnySettingShouldReturnFalse — added DictionaryKeyFilter entry to GetPropertiesWithSettersAndNonDefaultValues.
  • OptionsTests.CopyConstructor_CopiesAllPublicProperties — added an explicit JsonDictionaryKeyFilter handler in GetFullyPopulatedOptionsInstance (the method uses Activator.CreateInstance for unknown types, which fails for abstract classes).

Merge conflict resolutions:

Merge conflicts with main were resolved twice as main introduced new features in the System.Text.Json area:

  • ref/System.Text.Json.cs — preserved JsonDictionaryKeyFilter alongside new entries from main (JsonIgnoreAttribute attribute usage expansion, JsonNamingPolicyAttribute).
  • gen/JsonSourceGenerator.Parser.cs — accepted main's version (our PR does not modify this file; conflicts arose from a new typeNamingPolicy parameter and pre-existing helper methods).
  • src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs — accepted main's version (our PR does not modify this file; conflicts arose from a new typeNamingPolicy parameter threading).

Testing

  • New tests added in CollectionTests.Dictionary.KeyFilter.cs covering IgnoreMetadataNames, custom filters, null filter, all-keys-filtered, IReadOnlyDictionary, copy constructor independence, and small-buffer async stream deserialization.
  • All new and existing related tests pass locally.
Original prompt

This section details on the original issue you should resolve

<issue_title>Support filtering property names on dictionary deserialization.</issue_title>
<issue_description>EDIT See #79059 (comment) for API proposal.

It seems there is currently no way to make JSON documents that reference a schema just deserializa.

Repro

{
    "$schema": "http://example.org/myschema.json",
    "Key1": {
        "V1": 10,
        "V2": "Ten"
    },
    "Key2": {
        "V1": 20,
        "V2": "Twenty"
    }
}
var content = "<json above>";
return JsonSerializer.Deserialize<Dictionary<string, CustomType>>(content);

class CustomType {
    public int V1 { get; set; }
    public string V2 { get; set; }
}

Expected Behavior

It just works. Or, based on JsonSerializerOptions, it could be configured to just work.

Actual Behavior

It causes an exception:

The JSON value could not be converted to CustomType. Path: $.$schema

In order to make it deserializable, the easiest option is to manually remove the schema node from the document:

if (JsonNode.Parse(content) is JsonObject jsonObject)
{
    if (jsonObject.Remove("$schema"))
        content = jsonObject.ToJsonString();
}

The more performant alternative would be to write a custom converter, but that seems like a lot of work. If the root type of the document weren't dictionary but a type I control, then presumably I could work this around by adding a property to my class and decorate it with [JsonPropertyName("$schema")] but that feels equally hacky.

Customer Evidence

It’s not entirely clear to me if this is worth prioritizing. On the one hand, it seems a decent amount of our customers might use JSON schemas (based on this super scientific poll below).

On the other hand, it would only impact customers who use dictionaries as the document type. That combination might be quite rare and may not be be worth handling directly. And based on James’s comments below, JSON.NET never handled that either. I suggest we leave it open for a bit and see what responses we get but my gut feel says cutting seems fine.

A5B403CE-A45D-49BD-9861-9C3D65E3281F
</issue_description>

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

@ Tagging subscribers to this area: @dotnet/area-system-text-json, @gregsdennis See info in [area-owners.md](https://github.com/dotnet/runtime/blob/main/docs/area-owners.md) if you want to be subscribed.
Issue Details

It seems there is currently no way to make JSON documents that reference a schema just deserializa.

Repro

{
    "$schema": "http://example.org/myschema.json",
    "Key1": "Value1",
    "Key2": "Value2"
}
var content = "<json above>";
return JsonSerializer.Deserialize<Dictionary<string, string>>(content);

Expected Behavior

It just works. Or, based on JsonSerializerOptions, it could be configured to just work.

Actual Behavior

It causes an exception:

The JSON value could not be converted to System.String. Path: $.$schema

In order to make it deserializable, the easiest option is to manually remove the schema node from the document:

if (JsonNode.Parse(content) is JsonObject jsonObject)
{
    if (jsonObject.Remove("$schema"))
        content = jsonObject.ToJsonString();
}

The more performant alternative would be to write a custom converter, but that seems like a lot of work. If the root type of the document weren't dictionary but a type I control, then presumably I could work this around by adding a property to my class and decorate it with [JsonPropertyName("$schema")] but that feels equally hacky.

Author: terrajobst
Assignees: -
Labels:

area-System.Text.Json

Milestone: -
@JamesNK Newtonsoft.Json errors:

Newtonsoft.Json.JsonSerializationException: 'Error converting value "http://example.org/myschema.json" to type 'CustomType'. Path '$schema', line 2, position 49.'

I'd recommend people load the JSON into a JObject and remove the $schema property and then deserialize it.</comment_new>
<comment_new>@eiriktsarpalis
A few observations:

  • POCO deserialization will generally skip an...

🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

Copilot AI and others added 2 commits February 20, 2026 15:49
…keys

Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
Copilot AI changed the title [WIP] Support filtering property names on dictionary deserialization Add JsonDictionaryKeyFilter to support filtering property names on dictionary deserialization Feb 20, 2026
Copilot AI requested a review from eiriktsarpalis February 20, 2026 16:27
@eiriktsarpalis
Copy link
Member

@copilot please address the failing tests.

Copy link
Contributor Author

Copilot AI commented Mar 18, 2026

@copilot please address the failing tests.

Fixed in 0f1bab6. The two failing tests were:

  1. CacheTests.JsonSerializerOptions_EqualityComparer_ChangingAnySettingShouldReturnFalse — uses reflection to enumerate all settable public properties on JsonSerializerOptions and asserts each one affects equality. Fixed by adding DictionaryKeyFilter to GetPropertiesWithSettersAndNonDefaultValues.

  2. OptionsTests.CopyConstructor_CopiesAllPublicProperties — calls Activator.CreateInstance on each property type to populate an options instance; since JsonDictionaryKeyFilter is abstract this threw. Fixed by adding an explicit handler in GetFullyPopulatedOptionsInstance to assign JsonDictionaryKeyFilter.IgnoreMetadataNames.

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:

  • helixr1107v0xdcypoyl9e7f.blob.core.windows.net
    • Triggering command: /usr/bin/pwsh pwsh -File scripts/Get-CIStatus.ps1 -HelixJob 1ec011e7-354c-4419-99dc-62dd20eeeda9 -WorkItem System.Text.Json.Tests a0dec3c7 (dns block)

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

@eiriktsarpalis
Copy link
Member

@copilot please address the merge conflicts

Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 19, 2026

@copilot please address the merge conflicts

Resolved in f236d93. The only conflict was in ref/System.Text.Json.cs where our JsonDictionaryKeyFilter class insertion overlapped with main's change to expand JsonIgnoreAttribute's AttributeUsage to include Class | Struct | Interface. Both changes are preserved.

@eiriktsarpalis
Copy link
Member

@copilot please address the most recent merge conflicts

Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 20, 2026

@copilot please address the most recent merge conflicts

Resolved in 0597bb2. Three files had conflicts from the JsonNamingPolicyAttribute PR (#124645):

  • ref/System.Text.Json.cs — kept our JsonDictionaryKeyFilter class alongside the new JsonNamingPolicyAttribute class from main
  • gen/JsonSourceGenerator.Parser.cs — accepted main's version entirely (our PR doesn't modify this file; the conflict was from duplicated helper methods that were already present in main)
  • src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs — accepted main's version entirely (our PR doesn't modify this file)

@dotnet-policy-service dotnet-policy-service bot added the linkable-framework Issues associated with delivering a linker friendly framework label Mar 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-System.Text.Json linkable-framework Issues associated with delivering a linker friendly framework

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support filtering property names on dictionary deserialization.

2 participants