Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
748 changes: 376 additions & 372 deletions QsNet.Tests/DecodeTests.cs

Large diffs are not rendered by default.

110 changes: 55 additions & 55 deletions QsNet.Tests/ExampleTests.cs

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions QsNet.Tests/Fixtures/Data/EmptyTestCases.cs
Original file line number Diff line number Diff line change
Expand Up @@ -330,10 +330,10 @@ internal static class EmptyTestCases
["indices"] = "[0]=a&[1]=b& [0]=1",
["repeat"] = "=a&=b& =1"
},
["noEmptyKeys"] = new Dictionary<object, object?>
["noEmptyKeys"] = new Dictionary<string, object?>
{
[0] = "a",
[1] = "b",
["0"] = "a",
["1"] = "b",
[" "] = new List<object?> { "1" }
}
},
Expand All @@ -345,10 +345,10 @@ internal static class EmptyTestCases
[""] = new List<object?> { "a", "b" },
["a"] = new List<object?> { "1", "2" }
},
["noEmptyKeys"] = new Dictionary<object, object?>
["noEmptyKeys"] = new Dictionary<string, object?>
{
[0] = "a",
[1] = "b",
["0"] = "a",
["1"] = "b",
["a"] = new List<object?> { "1", "2" }
},
["stringifyOutput"] = new Dictionary<string, object?>
Expand Down Expand Up @@ -392,7 +392,7 @@ internal static class EmptyTestCases
["indices"] = "[0]=a&[1]=b",
["repeat"] = "=a&=b"
},
["noEmptyKeys"] = new Dictionary<object, object?> { [0] = "a", [1] = "b" }
["noEmptyKeys"] = new Dictionary<string, object?> { ["0"] = "a", ["1"] = "b" }
}
];
}
2 changes: 1 addition & 1 deletion QsNet.Tests/QsNet.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="coverlet.collector" Version="6.0.4"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\QsNet\QsNet.csproj"/>
Expand Down
64 changes: 32 additions & 32 deletions QsNet.Tests/UtilsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -512,9 +512,9 @@ public void Unescape_HandlesBadHexAfterPercent()
[Fact]
public void Merge_MapWithList()
{
var target = new Dictionary<object, object?> { { 0, "a" } };
var target = new Dictionary<string, object?> { { "0", "a" } };
var source = new List<object?> { Undefined.Create(), "b" };
var expected = new Dictionary<object, object?> { { 0, "a" }, { 1, "b" } };
var expected = new Dictionary<string, object?> { { "0", "a" }, { "1", "b" } };

Utils.Merge(target, source).Should().BeEquivalentTo(expected);
}
Expand Down Expand Up @@ -691,7 +691,7 @@ public void Merge_TwoObjectsWithSameKey()
new Dictionary<string, object?> { { "a", "b" } },
new Dictionary<string, object?> { { "a", "c" } }
);
var expected = new Dictionary<object, object?>
var expected = new Dictionary<string, object?>
{
{
"a",
Expand Down Expand Up @@ -719,7 +719,7 @@ public void Merge_StandaloneAndObjectIntoList()
}
}
);
var expected = new Dictionary<object, object?>
var expected = new Dictionary<string, object?>
{
{
"foo",
Expand Down Expand Up @@ -757,15 +757,15 @@ public void Merge_StandaloneAndTwoObjectsIntoList()
}
}
);
var expected = new Dictionary<object, object?>
var expected = new Dictionary<string, object?>
{
{
"foo",
new Dictionary<object, object?>
new Dictionary<string, object?>
{
{ 0, "bar" },
{ "0", "bar" },
{
1,
"1",
new Dictionary<string, object?> { { "first", "123" } }
},
{ "second", "456" }
Expand Down Expand Up @@ -793,7 +793,7 @@ public void Merge_ObjectSandwichedByTwoStandalonesIntoList()
},
new Dictionary<string, object?> { { "foo", "baz" } }
);
var expected = new Dictionary<object, object?>
var expected = new Dictionary<string, object?>
{
{
"foo",
Expand Down Expand Up @@ -837,7 +837,7 @@ public void Merge_TwoListsIntoList()
}
}
);
var expected2 = new Dictionary<object, object?>
var expected2 = new Dictionary<string, object?>
{
{
"foo",
Expand Down Expand Up @@ -880,7 +880,7 @@ public void Merge_TwoSetsIntoSet()
}
}
);
var expected2 = new Dictionary<object, object?>
var expected2 = new Dictionary<string, object?>
{
{
"foo",
Expand Down Expand Up @@ -914,7 +914,7 @@ public void Merge_SetIntoList()
}
}
);
var expected = new Dictionary<object, object?>
var expected = new Dictionary<string, object?>
{
{
"foo",
Expand Down Expand Up @@ -948,7 +948,7 @@ public void Merge_ListIntoSet()
}
}
);
var expected = new Dictionary<object, object?>
var expected = new Dictionary<string, object?>
{
{
"foo",
Expand Down Expand Up @@ -982,7 +982,7 @@ public void Merge_SetIntoListWithMultipleElements()
}
}
);
var expected = new Dictionary<object, object?>
var expected = new Dictionary<string, object?>
{
{
"foo",
Expand Down Expand Up @@ -1016,11 +1016,11 @@ public void Merge_ObjectIntoList()
}
}
);
var expected = new Dictionary<object, object?>
var expected = new Dictionary<string, object?>
{
{
"foo",
new Dictionary<object, object?> { { 0, "bar" }, { "baz", "xyzzy" } }
new Dictionary<string, object?> { { "0", "bar" }, { "baz", "xyzzy" } }
}
};

Expand Down Expand Up @@ -1050,11 +1050,11 @@ public void Merge_ListIntoObject()
}
}
);
var expected = new Dictionary<object, object?>
var expected = new Dictionary<string, object?>
{
{
"foo",
new Dictionary<object, object?> { { "bar", "baz" }, { 0, "xyzzy" } }
new Dictionary<string, object?> { { "bar", "baz" }, { "0", "xyzzy" } }
}
};

Expand Down Expand Up @@ -1086,7 +1086,7 @@ public void Merge_SetWithUndefinedWithAnotherSet()
}
}
);
var expected1 = new Dictionary<object, object?>
var expected1 = new Dictionary<string, object?>
{
{
"foo",
Expand Down Expand Up @@ -1116,7 +1116,7 @@ public void Merge_SetWithUndefinedWithAnotherSet()
}
}
);
var expected2 = new Dictionary<object, object?>
var expected2 = new Dictionary<string, object?>
{
{
"foo",
Expand Down Expand Up @@ -1162,7 +1162,7 @@ public void Merge_SetOfMapsWithAnotherSetOfMaps()
}
}
);
var expected2 = new Dictionary<object, object?>
var expected2 = new Dictionary<string, object?>
{
{
"foo",
Expand Down Expand Up @@ -1330,13 +1330,13 @@ public void IsEmpty_EmptyCollectionsAndMaps()
public void ToObjectKeyedDictionary_Converts_NonGeneric_IDictionary()
{
// arrange
IDictionary src = new Hashtable { ["a"] = 1, [2] = "b" };
IDictionary src = new Hashtable { ["a"] = 1, ["2"] = "b" };

// act
var result = Utils.ToObjectKeyedDictionary(src);

// assert – same contents, but a different instance
result.Should().Equal(new Dictionary<object, object?> { ["a"] = 1, [2] = "b" });
result.Should().Equal(new Dictionary<object, object?> { ["a"] = 1, ["2"] = "b" });

// cast to object so the types match the generic constraint
Assert.NotSame(src, result);
Expand Down Expand Up @@ -1400,7 +1400,7 @@ public void ConvertNestedValues_Deep_Walks_And_Preserves_Cycles()
var inner = new Dictionary<string, object?>();
inner["self"] = inner; // cycle

var root = new Dictionary<object, object?> { ["k"] = inner };
var root = new Dictionary<string, object?> { ["k"] = inner };

Utils.ConvertNestedValues(root);

Expand Down Expand Up @@ -1468,7 +1468,7 @@ public void Merge_MapsAndArrays()
.Merge(dict1, dict2)
.Should()
.BeEquivalentTo(
new Dictionary<object, object?>
new Dictionary<string, object?>
{
{
"a",
Expand All @@ -1480,7 +1480,7 @@ public void Merge_MapsAndArrays()
.Merge(dict1, dict3)
.Should()
.BeEquivalentTo(
new Dictionary<object, object?>
new Dictionary<string, object?>
{
{
"a",
Expand Down Expand Up @@ -1512,15 +1512,15 @@ public void Merge_MapsAndArrays()
}
};

var expected1 = new Dictionary<object, object?>
var expected1 = new Dictionary<string, object?>
{
{
"foo",
new Dictionary<object, object?>
new Dictionary<string, object?>
{
{ 0, "bar" },
{ "0", "bar" },
{
1,
"1",
new Dictionary<string, object?> { { "first", "123" } }
},
{ "second", "456" }
Expand All @@ -1547,7 +1547,7 @@ public void Merge_MapsAndArrays()
.Merge(a, b)
.Should()
.BeEquivalentTo(
new Dictionary<object, object?>
new Dictionary<string, object?>
{
{
"foo",
Expand All @@ -1560,6 +1560,6 @@ public void Merge_MapsAndArrays()
Utils
.Merge(x, "bar")
.Should()
.BeEquivalentTo(new Dictionary<object, object?> { { "foo", "baz" }, { "bar", true } });
.BeEquivalentTo(new Dictionary<string, object?> { { "foo", "baz" }, { "bar", true } });
}
}
2 changes: 1 addition & 1 deletion QsNet/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public static class Extensions
/// <param name="queryString">The query string to decode</param>
/// <param name="options">Optional decoder settings</param>
/// <returns>A Dictionary containing the decoded key-value pairs</returns>
public static Dictionary<object, object?> ToQueryMap(
public static Dictionary<string, object?> ToQueryMap(
this string queryString,
DecodeOptions? options = null
)
Expand Down
38 changes: 19 additions & 19 deletions QsNet/Internal/Decoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,8 @@ int currentListLength
{
key = options.GetDecoder(part[..pos], charset)?.ToString() ?? string.Empty;
var currentLength =
obj.TryGetValue(key, out var val) && val is IList<object?> list
? list.Count
: 0;
obj.TryGetValue(key, out var val) && val is IList<object?> list ? list.Count : 0;

value = Utils.Apply<object?>(
ParseListValue(part[(pos + 1)..], options, currentLength),
v => options.GetDecoder(v?.ToString(), charset)
Expand Down Expand Up @@ -206,14 +205,13 @@ bool valuesParsed

if (leaf is IDictionary id and not Dictionary<object, object?>)
{
// Preserve identity for self-referencing maps (and avoid an
// unnecessary copy when the caller doesn’t care about key type).
// Preserve identity for self-referencing maps
var selfRef =
id is Dictionary<string, object?> strDict
&& strDict.Keys.Any(k => ReferenceEquals(strDict[k], strDict));

if (!selfRef)
leaf = Utils.ToObjectKeyedDictionary(id); // only when safe
leaf = Utils.ToObjectKeyedDictionary(id);
}

for (var i = chain.Count - 1; i >= 0; i--)
Expand All @@ -239,37 +237,40 @@ bool valuesParsed
? cleanRoot.Replace("%2E", ".")
: cleanRoot;

// Was this segment a *bracketed* numeric like "[1]"?
// Bracketed numeric like "[1]"?
var isPureNumeric =
int.TryParse(decodedRoot, out var idx) && !string.IsNullOrEmpty(decodedRoot);
var isBracketedNumeric =
isPureNumeric && root != decodedRoot && idx.ToString() == decodedRoot;

if (!options.ParseLists && decodedRoot == "")
// "[]" when arrays are disabled → treat as object with key 0
obj = new Dictionary<object, object?> { [0] = leaf };
if (!options.ParseLists || options.ListLimit < 0)
{
var keyForMap = decodedRoot == "" ? "0" : decodedRoot;
obj = new Dictionary<object, object?> { [keyForMap] = leaf };
}
else
{
switch (isBracketedNumeric)
{
case true when options.ParseLists && idx >= 0 && idx <= options.ListLimit:
case true when idx >= 0 && idx <= options.ListLimit:
{
// Within list limit → build a list up to idx
// Build a list up to idx (0 is allowed when ListLimit == 0)
var list = new List<object?>();
for (var j = 0; j <= idx; j++)
list.Add(j == idx ? leaf : Undefined.Instance);
obj = list;
break;
}
case true:
// Bracketed numeric but *not* a list (limit 0/too small or arrays disabled) → object-keyed map
obj = new Dictionary<object, object?> { [idx] = leaf };
// Not a list (e.g., idx > ListLimit) → map with the string key (e.g., "2", "99999999")
obj = new Dictionary<object, object?> { [decodedRoot] = leaf };
break;
default:
// Pure digits? use int key; otherwise keep string
object dictKey = isPureNumeric ? idx : decodedRoot;
obj = new Dictionary<object, object?> { [dictKey] = leaf };
// Non-numeric or non-bracketed → map with string key
obj = new Dictionary<object, object?> { [decodedRoot] = leaf };
break;
}
}
}

leaf = obj;
Expand Down Expand Up @@ -350,8 +351,7 @@ bool strictDepth
open = key.IndexOf('[', close + 1);
}

if (open < 0)
return segments;
if (open < 0) return segments;
// When depth > 0, strictDepth can apply to the remainder.
if (strictDepth)
throw new IndexOutOfRangeException(
Expand Down
Loading
Loading