Skip to content

Report every failing element of a root list, array or dictionary [patch] - #87

Merged
matt-edmondson merged 1 commit into
mainfrom
fix/50-claim-root-collections
Oct 30, 2026
Merged

matt-edmondson merged 1 commit into
mainfrom
fix/50-claim-root-collections

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #50

What changed

  • RequirementRuleCompiler.HasRules now claims a list, array or dictionary (anything assignable to IList or IDictionary) whose element or value type reaches a decorated member. Nested lists and dictionaries count too, so Dictionary<string, List<T>> and List<List<T>> are claimed. A collection that holds itself ends the search.
  • Sets, stacks, queues and bare IEnumerable stay unclaimed, for the positional-pairing reasons given on GraphValidator.DescendList. If one were claimed, its elements would be materialized factory-free and then never walked. Unclaimed, each element is still validated on its own, as before.
  • GraphValidator.Validate starts from Descend rather than Walk, so a root array or object-shaped dictionary is walked with [i] and key paths.

Claiming a collection only matters at the payload root. Below a claimed holder, the subtree is already materialized through factory-free options, so holders behave as before.

Call Before After
Deserialize<List<SimpleConfig>> on [A, B, A] [Tuning] [0].Tuning, [2].Tuning
Deserialize<SimpleConfig[]> [Tuning] [0].Tuning, [2].Tuning
Deserialize<Dictionary<string,SimpleConfig>> [Tuning] a.Tuning, b.Tuning

Side effect on System.Text.Json's own errors at a root collection

  • Type errors ({"Mode":"oops"} in element 1) now get exactly the path and line System.Text.Json reports without the factory, $[1].Mode. Before this change the path was $[1], with the inner path kept in the inner exception. The three ErrorLocationTests that pinned the old shape now assert the paths match exactly.
  • MaxDepth exceeded inside an element is now reported at $ on the same line, rather than at $[0]. A claimed root buffers its whole value through the caller's reader, and the depth limit fails during that buffering. A claimed root object already behaves this way.
    • I tried reading the root straight from the caller's reader to keep $[0].x. It doesn't help: System.Text.Json's own read-ahead (TrySkip) hits the limit first.
    • The test for the nested-element case now uses a root HashSet<T>, which still goes through a per-element converter, and a new test pins the $ root behaviour.

Tests

  • New RootCollectionTests covers:
    • a root List<T>, T[], Dictionary<string,T> and List<List<T>>, each reporting every violation in one exception;
    • valid payloads, and a round trip;
    • which collections are claimed: sets, stacks, IEnumerable<T>, List<HashSet<T>>, collections of undecorated types and a self-listing type are not.
  • Updated the two tests that pinned the exclusion: EligibilityTests.BareCollectionsAreStillNotClaimed, now BareCollectionsOfNotEmptyTypesAreClaimed, and RequirementRuleCompilerTests.HasRulesRejectsPrimitivesStringsAndEnums, which now uses List<int>.
  • With the library change reverted, the 10 new or updated tests fail. With it applied, all 296 tests pass on net10.0.
  • net8.0 and net9.0 were not run locally because those runtimes aren't installed here. CI runs them.
  • The library builds cleanly for every target framework.

README "How it works" and CLAUDE.md "Reachability" now describe root collections.


Generated by Claude Code

A collection at the payload root was never claimed, so System.Text.Json's own
collection converter validated each element on its own. Only the first failing
element was reported, and without its index or key.

HasRules now claims a list, array or dictionary whose elements reach a decorated
member, and GraphValidator.Validate starts from Descend so the root collection
is walked with [i] and key paths. Sets, stacks and bare IEnumerable stay
unclaimed, because the walk cannot pair them positionally.

Fixes #50

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vfJW23J6RZjbfVi3bACn8
@sonarqubecloud

Copy link
Copy Markdown

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

Labels

None yet

Projects

None yet

1 participant