Skip to content

Conversation

techouse
Copy link
Owner

@techouse techouse commented Aug 13, 2025

This pull request updates the unit tests in QsNet.Tests/ExampleTests.cs to use Dictionary<string, object?> instead of Dictionary<object, object?> for all expected decoded results. This change ensures that all dictionary keys are consistently typed as strings, which better matches typical query string decoding output and improves type safety and clarity in the tests.

Test type consistency improvements:

List and map decoding behavior:

  • When lists are decoded as maps (e.g., due to high indices or disabled list parsing), the keys are now expected as strings (e.g., "100" instead of 100). [1] [2] [3] [4]

Nested and mixed structure handling:

  • Nested dictionaries within decoded results also use string keys, ensuring consistency even for deeply nested or mixed structures. [1] [2] [3] [4] [5] [6]

Duplicate key handling:

  • Tests for duplicate key handling (combine, first, last) now expect the results in dictionaries with string keys. [1] [2] [3] [4]

Charset and encoding options:

  • Tests for charset handling (Latin1, UTF8, numeric entities) have updated expected results to use Dictionary<string, object?>. [1] [2] [3] [4]

Summary by CodeRabbit

  • New Features

    • Decoded results now use string-keyed dictionaries consistently, including nested structures.
    • Numeric and non-string keys are normalized to strings (e.g., 100 → "100").
    • More predictable behavior for list-like segments when list parsing is disabled or limited.
  • Refactor

    • Public decode API now returns string-keyed maps, aligning all decoded outputs to string keys.
  • Tests

    • Updated test cases to expect string-keyed dictionaries across scenarios (nested maps, arrays, encodings, limits).
  • Chores

    • Minor project formatting cleanups.

@techouse techouse self-assigned this Aug 13, 2025
@techouse techouse added the enhancement New feature or request label Aug 13, 2025
Copy link

coderabbitai bot commented Aug 13, 2025

Walkthrough

The decode API now returns Dictionary<string, object?> throughout, replacing object-keyed maps. Decoder and utils were updated to produce and convert to string-keyed dictionaries, including deep conversion with cycle/identity handling. Tests were adjusted accordingly. Extensions.ToQueryMap and Qs.Decode signatures now reflect string keys. Minor csproj formatting change.

Changes

Cohort / File(s) Summary
Public API: Decode and Extensions
QsNet/Qs.cs, QsNet/Extensions.cs
Qs.Decode return type changed to Dictionary<string, object?>; final result now deep-converted to string-keyed via new utils. Extensions.ToQueryMap updated to return Dictionary<string, object?>.
Decoder internals
QsNet/Internal/Decoder.cs
Decoding paths updated to consistently produce string-keyed dictionaries, adjust list vs. map decisions (including ListLimit handling), and standardize bracketed-numeric key behavior. Minor comments/formatting.
Utilities: conversion and merge
QsNet/Internal/Utils.cs
Indexing into dictionaries now uses string indices; enumerable-to-dictionary merges produce string-keyed maps. Added cycle/identity-aware deep converter: ToStringKeyDeepNonRecursive. Updated nested dictionary conversions and list handling.
Tests and fixtures
QsNet.Tests/ExampleTests.cs, QsNet.Tests/UtilsTests.cs, QsNet.Tests/Fixtures/Data/EmptyTestCases.cs, QsNet.Tests/QsNet.Tests.csproj
Tests switched from object-keyed to string-keyed expectations and inputs across decode/merge scenarios; numeric keys now strings ("0", "1", "100"). Fixture inner maps updated similarly. csproj whitespace tweak (no semantic change).

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant Extensions
  participant Qs
  participant Decoder
  participant Utils

  Caller->>Extensions: ToQueryMap(query, options)
  Extensions->>Qs: Decode(query, options)
  Qs->>Decoder: ParseObject/Decode internals (object-keyed interim)
  Decoder-->>Qs: Object-keyed result
  Qs->>Utils: Compact(object-keyed)
  Utils-->>Qs: Compacted (object-keyed)
  Qs->>Utils: ToStringKeyDeepNonRecursive(compacted)
  Utils-->>Qs: String-keyed Dictionary<string, object?>
  Qs-->>Extensions: String-keyed map
  Extensions-->>Caller: Dictionary<string, object?>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Poem

I nibbled keys till strings appeared,
From 0 to "0", the path is cleared.
Deep in the burrow, maps align,
Lists or nests—now all stringify.
With twitching nose, I press “decode”—
A carrot-colored, cleaner load.
Thump! The query’s now in ode.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fbebb51 and e6c2ed8.

📒 Files selected for processing (8)
  • QsNet.Tests/ExampleTests.cs (36 hunks)
  • QsNet.Tests/Fixtures/Data/EmptyTestCases.cs (3 hunks)
  • QsNet.Tests/QsNet.Tests.csproj (1 hunks)
  • QsNet.Tests/UtilsTests.cs (22 hunks)
  • QsNet/Extensions.cs (1 hunks)
  • QsNet/Internal/Decoder.cs (4 hunks)
  • QsNet/Internal/Utils.cs (6 hunks)
  • QsNet/Qs.cs (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (6)
QsNet/Extensions.cs (3)
QsNet/Internal/Utils.cs (8)
  • Dictionary (455-596)
  • Dictionary (753-759)
  • Dictionary (766-775)
  • Dictionary (782-791)
  • Dictionary (798-810)
  • Dictionary (859-862)
  • Dictionary (864-930)
  • Dictionary (958-1055)
QsNet/Qs.cs (1)
  • Dictionary (29-89)
QsNet/Internal/Decoder.cs (1)
  • Dictionary (65-175)
QsNet/Qs.cs (4)
QsNet/Extensions.cs (1)
  • Dictionary (21-27)
QsNet/Internal/Utils.cs (10)
  • Dictionary (455-596)
  • Dictionary (753-759)
  • Dictionary (766-775)
  • Dictionary (782-791)
  • Dictionary (798-810)
  • Dictionary (859-862)
  • Dictionary (864-930)
  • Dictionary (958-1055)
  • Decode (419-447)
  • Utils (18-1056)
QsNet/Internal/Decoder.cs (1)
  • Dictionary (65-175)
QsNet/Models/DecodeOptions.cs (3)
  • DecodeOptions (20-245)
  • DecodeOptions (29-40)
  • DecodeOptions (200-244)
QsNet/Internal/Decoder.cs (4)
QsNet/Extensions.cs (1)
  • Dictionary (21-27)
QsNet/Internal/Utils.cs (10)
  • Dictionary (455-596)
  • Dictionary (753-759)
  • Dictionary (766-775)
  • Dictionary (782-791)
  • Dictionary (798-810)
  • Dictionary (859-862)
  • Dictionary (864-930)
  • Dictionary (958-1055)
  • Utils (18-1056)
  • List (604-632)
QsNet/Qs.cs (1)
  • Dictionary (29-89)
QsNet/Models/Undefined.cs (4)
  • ToString (24-27)
  • Undefined (6-37)
  • Undefined (11-13)
  • Undefined (33-36)
QsNet.Tests/UtilsTests.cs (5)
QsNet/Extensions.cs (1)
  • Dictionary (21-27)
QsNet/Internal/Utils.cs (10)
  • Dictionary (455-596)
  • Dictionary (753-759)
  • Dictionary (766-775)
  • Dictionary (782-791)
  • Dictionary (798-810)
  • Dictionary (859-862)
  • Dictionary (864-930)
  • Dictionary (958-1055)
  • List (604-632)
  • Utils (18-1056)
QsNet/Qs.cs (1)
  • Dictionary (29-89)
QsNet/Internal/Decoder.cs (2)
  • Dictionary (65-175)
  • List (319-364)
QsNet/Models/Undefined.cs (3)
  • Undefined (6-37)
  • Undefined (11-13)
  • Undefined (33-36)
QsNet.Tests/Fixtures/Data/EmptyTestCases.cs (4)
QsNet/Extensions.cs (1)
  • Dictionary (21-27)
QsNet/Internal/Utils.cs (8)
  • Dictionary (455-596)
  • Dictionary (753-759)
  • Dictionary (766-775)
  • Dictionary (782-791)
  • Dictionary (798-810)
  • Dictionary (859-862)
  • Dictionary (864-930)
  • Dictionary (958-1055)
QsNet/Qs.cs (1)
  • Dictionary (29-89)
QsNet/Internal/Decoder.cs (1)
  • Dictionary (65-175)
QsNet/Internal/Utils.cs (5)
QsNet/Enums/Sentinel.cs (1)
  • ToString (65-68)
QsNet/Models/Undefined.cs (1)
  • ToString (24-27)
QsNet/Extensions.cs (1)
  • Dictionary (21-27)
QsNet/Qs.cs (1)
  • Dictionary (29-89)
QsNet/Internal/Decoder.cs (2)
  • Dictionary (65-175)
  • List (319-364)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Coverage (merged)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: Codacy Security Scan
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (33)
QsNet/Internal/Utils.cs (5)

145-145: LGTM! String-keyed dictionary conversion aligns with API changes.

The change from integer keys to string keys (ii.ToString()) when merging IEnumerable sources into dictionary targets is consistent with the PR's goal of returning string-keyed dictionaries from the decode API.


188-188: LGTM! Consistent string key conversion.

The conversion to string keys when merging enumerables into dictionaries maintains consistency with the API changes.


859-930: Well-designed recursive conversion with cycle handling.

The implementation properly handles:

  • Cycle detection using a visited set
  • Identity preservation for already string-keyed dictionaries
  • Self-reference preservation
  • Recursive conversion of nested structures

The approach ensures no infinite loops while maintaining object identity where appropriate.


951-1055: Excellent non-recursive implementation with proper identity management.

The ToStringKeyDeepNonRecursive method is well-implemented:

  • Stack-based traversal avoids stack overflow for deeply nested structures
  • Proper tracking of visited objects prevents infinite loops
  • Identity preservation for already string-keyed dictionaries
  • Comprehensive handling of nested dictionaries and lists

This is a robust approach for converting complex nested structures to string-keyed representations.


204-204: No change needed: intentional use of ToObjectKeyedDictionary
We’ve verified that ToObjectKeyedDictionary((IDictionary)source) is consistently used in merge operations across the file—specifically at lines 204, 499, 540, and 582 in QsNet/Internal/Utils.cs. This matches the existing pattern for handling dictionary sources in internal processing, so no replacement with a string-keyed variant is required.

QsNet.Tests/QsNet.Tests.csproj (1)

20-20: Minor formatting change only.

The removal of the space before the closing /> tag is a trivial formatting change with no functional impact.

QsNet/Extensions.cs (1)

21-21: API signature updated correctly.

The return type change from Dictionary<object, object?> to Dictionary<string, object?> aligns with the PR's objective to expose string-keyed dictionaries in the public API. The implementation correctly delegates to Qs.Decode which now returns the string-keyed result.

QsNet.Tests/Fixtures/Data/EmptyTestCases.cs (1)

333-337: Test fixtures correctly updated to expect string keys.

The changes from numeric keys ([0], [1]) to string keys (["0"], ["1"]) in the test fixtures properly reflect the API change where decoded dictionaries now use string keys throughout. This ensures tests validate the expected string-keyed structure.

Also applies to: 348-351, 395-395

QsNet.Tests/UtilsTests.cs (4)

515-518: Test expectations correctly updated for string-keyed dictionaries.

The test now properly expects string keys ("0", "1") instead of numeric keys, aligning with the API changes.


694-700: Merge operation tests properly reflect string-keyed results.

All merge-related tests have been updated to expect Dictionary<string, object?> with string keys, correctly validating the new behavior.

Also applies to: 722-732, 760-774


1333-1339: Test correctly validates object-keyed dictionary conversion.

The test properly validates that ToObjectKeyedDictionary maintains object keys, which is important for internal processing even though the public API uses string keys.


1403-1403: All dictionary assertions correctly updated to string keys.

The test expectations throughout the file have been consistently updated to use Dictionary<string, object?> with string keys, properly validating the new API behavior.

Also applies to: 1471-1493, 1515-1529, 1550-1556, 1563-1563

QsNet/Qs.cs (5)

29-29: Return type change aligns with string-keyed dictionary conversion

The change from Dictionary<object, object?> to Dictionary<string, object?> is properly implemented and consistent with the final conversion step that transforms the internal object-keyed representation to string-keyed output.


37-37: Early return type matches the new signature

The early return for empty inputs correctly uses the new Dictionary<string, object?> type, maintaining consistency with the method signature.


58-59: Clear separation between internal and public representation

The comment "keep internal work in object-keyed maps" effectively documents the design decision to use object-keyed dictionaries internally while exposing string-keyed dictionaries publicly. This separation allows for flexibility in internal processing while providing a consistent public API.


62-62: Consistent return type for empty results

The return type for empty results after parsing is correctly updated to Dictionary<string, object?>.


86-88: Well-documented conversion pipeline

The comment clearly explains the two-step process: first compacting the object-keyed structure, then converting to string-keyed. The use of ToStringKeyDeepNonRecursive ensures the entire nested structure is converted while preserving shared references.

QsNet.Tests/ExampleTests.cs (7)

17-17: Test correctly expects string-keyed dictionary

The test expectation is properly updated to use Dictionary<string, object?> with string key "a".


34-37: Nested dictionary types are consistently string-keyed

The nested dictionary structure correctly uses Dictionary<string, object?> at both levels, maintaining type consistency throughout the hierarchy.


367-369: High indices correctly converted to string keys

The test properly expects numeric index "100" to be represented as a string key in the dictionary when the index exceeds the list limit. This aligns with the new string-keyed design.


380-383: List limit behavior correctly produces string-keyed dictionaries

When list parsing is disabled (ListLimit = 0), the numeric index "1" is correctly expected as a string key in the resulting dictionary.


394-397: Disabled list parsing produces string-keyed results

With ParseLists = false, the test correctly expects the array index to be represented as the string key "0" in the dictionary.


408-411: Mixed notation correctly uses string keys for indices

The test properly expects both numeric ("0") and non-numeric ("b") keys as strings in the resulting dictionary when mixed notations are used.


423-425: Nested structures within lists maintain string-keyed dictionaries

Lists containing dictionaries correctly use Dictionary<string, object?> for the nested map structure.

QsNet/Internal/Decoder.cs (9)

137-138: LGTM! Type-safe list count retrieval

The change to check for IList<object?> specifically is appropriate and maintains type safety when determining the current list length.


208-209: Simplified comment maintains clarity

The comment simplification from "Preserve identity" to just "Preserve identity for self-referencing maps" is clear and concise.


213-214: Identity preservation logic remains intact

The self-reference check and conversion logic correctly preserves identity for self-referencing maps while converting others to object-keyed dictionaries.


240-244: Bracketed numeric detection logic is clear

The variable naming and logic for detecting bracketed numeric keys (e.g., "[1]") is well-structured and easy to follow.


246-250: String key handling for non-list scenarios

When list parsing is disabled or ListLimit is negative, the code correctly creates a dictionary with string keys. The handling of empty keys as "0" is consistent with the overall string-keyed design.


255-256: List creation condition properly handles edge cases

The condition idx >= 0 && idx <= options.ListLimit correctly allows creating lists for valid indices within the limit, including the boundary case where idx equals ListLimit.


265-266: Out-of-bounds indices correctly use string keys

When the index exceeds ListLimit, the code correctly creates a dictionary with the string representation of the index as the key, maintaining consistency with the string-keyed design.


269-270: Non-numeric keys consistently use string representation

The default case correctly handles non-numeric or non-bracketed keys by using the decoded root as a string key in the dictionary.


354-354: Minor style improvement: single-line return

The single-line return statement is a minor style improvement that enhances readability.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/decode-to-dictionary-string-object

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@techouse techouse changed the title 🚸 change Decode return from Dictionary<object, object?> to Dictionary<string, object?> 🚸 change Qs.Decode return from Dictionary<object, object?> to Dictionary<string, object?> Aug 13, 2025
Copy link

codecov bot commented Aug 13, 2025

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

Thanks for integrating Codecov - We've got you covered ☂️

@techouse techouse merged commit 8ee9b0b into main Aug 13, 2025
11 of 15 checks passed
@techouse techouse deleted the fix/decode-to-dictionary-string-object branch August 13, 2025 10:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant