Skip to content

Report a dictionary value by its key - #92

Open
tgharold wants to merge 3 commits into
tgh-20261002-enumerate-collections-oncefrom
tgh-20261002-dictionary-paths-by-key
Open

tgharold wants to merge 3 commits into
tgh-20261002-enumerate-collections-oncefrom
tgh-20261002-dictionary-paths-by-key

Conversation

@tgharold

@tgharold tgharold commented Oct 2, 2026

Copy link
Copy Markdown
Owner

Stacked on #91. Retarget to master after #91 merges; CI runs only for PRs into master.

Motivation

A dictionary value was reported by its position in enumeration order, through the KeyValuePair the dictionary yields: Map[0].Value.Name. A caller can't map that back to a key without enumerating the dictionary the same way. For 3.0 the ruling is to report by key, in the form MVC model binding uses for a dictionary (selectedCourses[1050]=Chemistry): Map[Primary].Name. Keys stay unquoted and unescaped. An object key keeps the index form for its entry, so the key is still validated.

Summary

  • EnqueueItems unwraps the entries of a dictionary, meaning any type that implements IDictionary, IDictionary<,> or IReadOnlyDictionary<,>. When the key is simple, the value is queued at [key]. A simple key is a string, primitive, enum or self-formatting struct that is a leaf type, formatted with the invariant culture. Any other entry keeps [index].Key / [index].Value. A List<KeyValuePair<,>> is not a dictionary.
  • The value is one level below the dictionary, like a list item, so it can come earlier in the results and counts one level less for the depth limit.
  • The struct-skip from Report a struct once when a collection repeats it #91 now checks what is queued. A dictionary of class values re-enumerated by a second route no longer skips an entry that a Validate method added, which Report a struct once when a collection repeats it #91 alone would have done, because KeyValuePair is a struct.
  • PathStep gains a key step. Enqueue takes a ready-made step. The old NOTE about dictionaries is resolved.
  • BREAKING. Existing tests move to the key form. The 2.x dictionary test leaves UpgradeCompatibilityTests. README gets a Dictionaries section, and the changelog gets a BREAKING entry.

Test plan

  • Specs first: 27 specs fail on the Report a struct once when a collection repeats it #91 branch, and the guards pass (object key, key with an attribute, null value, list of pairs)
  • With the fix: 637 passed, 7 skipped on net8.0 and net10.0
  • Test project builds for net481 with -p:IncludeNetFramework=true
  • CI after retargeting to master

tgharold and others added 3 commits October 2, 2026 18:58
A dictionary value is to be reported by its key, in the form MVC
model binding uses: Map[Primary].Name, not Map[0].Value.Name. Specs:
string, number, enum, Guid and decimal keys (invariant culture), a key
with path characters (not escaped), each entry choosing its form,
struct values, every kind of dictionary, the value one level below
the dictionary, a shared dictionary of structs, and an entry that a
Validate method adds, found by a second route.

Guards: an object key and a key with something to validate keep the
index form, a null value is skipped, and a list of KeyValuePair items
is not a dictionary.

Existing tests move to the key form. The 2.x dictionary test leaves
UpgradeCompatibilityTests, because 3.0 differs there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A value of a dictionary (IDictionary, IDictionary<,> or
IReadOnlyDictionary<,>) is now reported by its key, as MVC model
binding names it: Map[Primary].Name instead of Map[0].Value.Name. The
KeyValuePair is no longer a path step, so the value is one level
below the dictionary.

A key names its value when it is a string, primitive, enum or a
self-formatting struct with nothing to validate. It is formatted with
the invariant culture and not escaped. Any other key keeps the index
form for that entry, so the key object is still validated.

On a second enumeration, the struct check looks at what is queued: the
value for a key-form entry, and the key and value for an index-form
entry. A dictionary of class values no longer skips an entry that was
added in the meantime.

PathStep gains a key step, and Enqueue takes a ready-made step.
Removes the NOTE in EnqueueItems.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
README: a Dictionaries section with the key form, which keys name a
value, the invariant culture, the unescaped key, the index form for
other keys, and the level of a value.

Changelog: a BREAKING entry for the key form, and the dictionary
item path in the earlier collection entry now shows the 3.0 form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant