Skip to content

Sanitize IDictionary query parameters to skip null values - #755

Open
Bart Koelman (bkoelman) wants to merge 2 commits into
microsoft:mainfrom
bkoelman:feat/sanitize-dictionary-null-entries
Open

Sanitize IDictionary query parameters to skip null values#755
Bart Koelman (bkoelman) wants to merge 2 commits into
microsoft:mainfrom
bkoelman:feat/sanitize-dictionary-null-entries

Conversation

@bkoelman

Copy link
Copy Markdown

Contributes to microsoft/kiota#3800.

Copilot AI review requested due to automatic review settings July 23, 2026 00:09
@bkoelman
Bart Koelman (bkoelman) force-pushed the feat/sanitize-dictionary-null-entries branch from 01bb318 to 2ba7a12 Compare July 23, 2026 00:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR updates RequestInformation URL template expansion to properly handle IDictionary query parameter values by dropping entries with null values, enabling “free-form” query parameters (e.g., JSON:API-style dictionaries) and aligning behavior with RFC 6570 undefined-value semantics.

Changes:

  • Add IDictionary handling to RequestInformation sanitization so dictionary entries with null values are omitted during Std.UriTemplate expansion.
  • Add unit tests covering exploded dictionary query parameters, null skipping, empty-string inclusion, and AddQueryParameters integration.
  • Extend the test query-parameter model to include a Query dictionary property.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/abstractions/RequestInformation.cs Adds dictionary sanitization to drop null entries before URI template expansion.
tests/abstractions/RequestInformationTests.cs Adds coverage for dictionary-based query parameter expansion and null-handling behavior.

Comment on lines +129 to +135
foreach(DictionaryEntry entry in dict)
{
if(entry.Value is not null)
{
result[(string)entry.Key] = GetSanitizedValue(entry.Value).ToString()!;
}
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

According to the plan (section PR 2), this is beyond the scope of this change. The dictionary signature of the client code generated by kiota should be Dictionary<string, string>, which can't contain null or non-string keys, so taking this change would basically introduce dead code.


// Assert
var url = requestInfo.URI.OriginalString;
Assert.DoesNotContain("include", url);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This is intentional; the include parameter must not appear, whether or not it ends with an = sign.

Comment on lines +949 to +951
// page[size] key is percent-encoded by RFC 6570 {?} operator
Assert.Contains("page", url);
Assert.Contains("10", url);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The goal of these tests is not to verify encoding/formatting, so if that part of the project ever changes, they should not break these tests.

Copilot AI review requested due to automatic review settings July 23, 2026 00:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

src/abstractions/RequestInformation.cs:134

  • SanitizeDictionary casts entry.Key to string, which will throw an InvalidCastException (and with a fairly opaque message) if a consumer passes an IDictionary with non-string keys. Also, forcing .ToString() here is inconsistent with ExpandArray/scalar sanitization (which keep non-string primitives as objects) and can subtly change formatting (e.g., culture-sensitive decimal/double). Consider validating keys and keeping sanitized values as object so Std.UriTemplate can handle formatting consistently.
        private static Dictionary<string, string> SanitizeDictionary(IDictionary dict)
        {
            var result = new Dictionary<string, string>(dict.Count);
            foreach(DictionaryEntry entry in dict)
            {
                if(entry.Value is not null)
                {
                    result[(string)entry.Key] = GetSanitizedValue(entry.Value).ToString()!;
                }

tests/abstractions/RequestInformationTests.cs:951

  • The assertions for the page[size] query parameter are very permissive (Contains("page") / Contains("10")) and don't actually verify the percent-encoding behavior described in the comment. This test can pass even if the key is not encoded correctly or if 10 appears elsewhere in the URL.
            var url = requestInfo.URI.OriginalString;
            Assert.Contains("include=author", url);
            // page[size] key is percent-encoded by RFC 6570 {?} operator
            Assert.Contains("page", url);
            Assert.Contains("10", url);

@baywet Vincent Biret (baywet) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the contribution!

Comment thread src/abstractions/RequestInformation.cs Outdated
/// </summary>
private static Dictionary<string, string> SanitizeDictionary(IDictionary dict)
{
var result = new Dictionary<string, string>(dict.Count);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can't we use Dictionary<string, object> so we can conform with the rest of the sanitization approach and we don't have to call ToString() below (which could potentially cause issues with number formatting etc... down the road)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Pushed, but that doesn't make sense to me at all. It's something entirely different from what's in the plan:

{648C4C98-5FC4-4C1C-90FE-3E5E90B7E5C8}

I've always assumed that dictionary values must be constrained to strings, which is what they are in an HTTP query string. Are you suggesting we deviate from the plan and switch to object values in the generated APIs?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

sorry if I wasn't clear. I was only suggesting that we update the return type of the private method to align with the rest.

In terms of usage, the users should able to use dictionary<string, string | int | float |.... > (any primitive type) on their query parameters class if that makes sense?

@github-project-automation github-project-automation Bot moved this to In Progress 🚧 in Kiota Jul 23, 2026
Bart Koelman (bkoelman) and others added 2 commits July 24, 2026 03:24
… values

Co-authored-by: Cursor <cursoragent@cursor.com>
…nary query parameters

Update SanitizeDictionary return type from Dictionary<string, string> to Dictionary<string, object> and route dictionary entry values through GetSanitizedValues instead of calling .ToString()!.

This avoids forcing string conversions on numbers, booleans, and dates in dictionary query parameters, allowing Std.UriTemplate to handle formatting natively according to RFC 6570.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copilot AI review requested due to automatic review settings July 24, 2026 01:39
@bkoelman
Bart Koelman (bkoelman) force-pushed the feat/sanitize-dictionary-null-entries branch from 2ba7a12 to e147dd9 Compare July 24, 2026 01:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment on lines +129 to +135
foreach(DictionaryEntry entry in dict)
{
if(entry.Value is not null)
{
result[entry.Key.ToString()!] = GetSanitizedValues(entry.Value);
}
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This matches the local formatting style of this specific file. Adding spaces introduces inconsistency.

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

Labels

None yet

Projects

Status: In Progress 🚧

Development

Successfully merging this pull request may close these issues.

3 participants