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
3 changes: 3 additions & 0 deletions src/qs_codec/decode.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ def decode(
_parse_query_string_values(value, options) if isinstance(value, str) else value
)

if temp_obj is not None and options.parse_lists and 0 < options.list_limit < len(temp_obj):
options.parse_lists = False

# Iterate over the keys and setup the new object
if temp_obj:
for key, val in temp_obj.items():
Expand Down
2 changes: 1 addition & 1 deletion src/qs_codec/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def merge(
else:
target_[len(target_)] = source

if any(isinstance(value, Undefined) for value in target_.values()):
if not options.parse_lists and any(isinstance(value, Undefined) for value in target_.values()):
target = {str(i): target_[i] for i in target_ if not isinstance(target_[i], Undefined)}
else:
target = list(filter(lambda el: not isinstance(el, Undefined), target_.values()))
Expand Down
44 changes: 31 additions & 13 deletions tests/unit/decode_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,9 +252,7 @@ def test_parses_an_explicit_list(self, query: str, expected: t.Dict) -> None:
pytest.param("a[]=b&a=c", None, {"a": ["b", "c"]}, id="explicit-first-mix-simple-second"),
pytest.param("a[0]=b&a=c", None, {"a": ["b", "c"]}, id="indexed-list-first"),
pytest.param("a=b&a[0]=c", None, {"a": ["b", "c"]}, id="simple-first-indexed-list-second"),
pytest.param(
"a[1]=b&a=c", DecodeOptions(list_limit=20), {"a": {"1": "b", "2": "c"}}, id="indexed-list-with-limit"
),
pytest.param("a[1]=b&a=c", DecodeOptions(list_limit=20), {"a": ["b", "c"]}, id="indexed-list-with-limit"),
pytest.param(
"a[]=b&a=c", DecodeOptions(list_limit=0), {"a": ["b", "c"]}, id="explicit-list-with-zero-limit"
),
Expand Down Expand Up @@ -292,6 +290,30 @@ def test_parses_a_nested_list(self, query: str, expected: t.Mapping[str, t.Any])
pytest.param("a[1]=c", DecodeOptions(list_limit=20), {"a": ["c"]}, id="list-limit-20"),
pytest.param("a[1]=c", DecodeOptions(list_limit=0), {"a": {"1": "c"}}, id="list-limit-0"),
pytest.param("a[1]=c", None, {"a": ["c"]}, id="default-behavior"),
pytest.param(
"a[0]=b&a[2]=c",
DecodeOptions(parse_lists=True),
{"a": ["b", "c"]},
id="list-starting-with-0-with-missing-index-parse-lists-true",
),
pytest.param(
"a[0]=b&a[2]=c",
DecodeOptions(parse_lists=False),
{"a": {"0": "b", "2": "c"}},
id="list-starting-with-0-with-missing-index-parse-lists-false",
),
pytest.param(
"a[1]=b&a[15]=c",
DecodeOptions(parse_lists=False),
{"a": {"1": "b", "15": "c"}},
id="list-starting-with-non-0-with-missing-index-parse-lists-false",
),
pytest.param(
"a[1]=b&a[15]=c",
DecodeOptions(parse_lists=True),
{"a": ["b", "c"]},
id="list-starting-with-non-0-with-missing-index-parse-lists-false",
),
],
)
def test_allows_to_specify_list_indices(
Expand Down Expand Up @@ -513,22 +535,18 @@ def test_allows_for_empty_strings_in_lists(
assert result == expected

@pytest.mark.parametrize(
"query, expected, not_expected",
"query, expected",
[
pytest.param("a[10]=1&a[2]=2", {"a": {"10": "1", "2": "2"}}, {"a": ["2", "1"]}, id="sparse-list"),
pytest.param("a[1][b][2][c]=1", {"a": [{"b": [{"c": "1"}]}]}, None, id="nested-list-of-dicts"),
pytest.param("a[1][2][3][c]=1", {"a": [[[{"c": "1"}]]]}, None, id="deeper-nested-list"),
pytest.param("a[1][2][3][c][1]=1", {"a": [[[{"c": ["1"]}]]]}, None, id="deepest-nested-list"),
pytest.param("a[10]=1&a[2]=2", {"a": ["2", "1"]}, id="sparse-list"),
pytest.param("a[1][b][2][c]=1", {"a": [{"b": [{"c": "1"}]}]}, id="nested-list-of-dicts"),
pytest.param("a[1][2][3][c]=1", {"a": [[[{"c": "1"}]]]}, id="deeper-nested-list"),
pytest.param("a[1][2][3][c][1]=1", {"a": [[[{"c": ["1"]}]]]}, id="deepest-nested-list"),
],
)
def test_compacts_sparse_lists(
self, query: str, expected: t.Mapping[str, t.Any], not_expected: t.Optional[t.Mapping[str, t.Any]]
) -> None:
def test_compacts_sparse_lists(self, query: str, expected: t.Mapping[str, t.Any]) -> None:
opts = DecodeOptions(list_limit=20)
result = decode(query, opts)
assert result == expected
if not_expected is not None:
assert result != not_expected

@pytest.mark.parametrize(
"query, expected",
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/example_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def test_lists(self):
# Note that the only difference between an index in a `list` and a key in a `dict` is that the value between the
# brackets must be a number to create a `list`. When creating `list`s with specific indices, **qs_codec** will compact
# a sparse `list` to only the existing values preserving their order:
assert qs_codec.decode("a[1]=b&a[15]=c") == {"a": {"1": "b", "15": "c"}}
assert qs_codec.decode("a[1]=b&a[15]=c") == {"a": ["b", "c"]}

# Note that an empty string is also a value, and will be preserved:
assert qs_codec.decode("a[]=&a[]=b") == {"a": ["", "b"]}
Expand Down