qs-codec 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. {qs_codec-0.2.0 → qs_codec-0.2.2}/CHANGELOG.md +9 -0
  2. {qs_codec-0.2.0 → qs_codec-0.2.2}/PKG-INFO +8 -8
  3. {qs_codec-0.2.0 → qs_codec-0.2.2}/README.rst +5 -5
  4. {qs_codec-0.2.0 → qs_codec-0.2.2}/docs/README.rst +5 -5
  5. {qs_codec-0.2.0 → qs_codec-0.2.2}/pyproject.toml +2 -2
  6. {qs_codec-0.2.0 → qs_codec-0.2.2}/requirements_dev.txt +2 -2
  7. {qs_codec-0.2.0 → qs_codec-0.2.2}/src/qs_codec/__init__.py +1 -1
  8. {qs_codec-0.2.0 → qs_codec-0.2.2}/src/qs_codec/decode.py +16 -10
  9. {qs_codec-0.2.0 → qs_codec-0.2.2}/src/qs_codec/encode.py +1 -1
  10. {qs_codec-0.2.0 → qs_codec-0.2.2}/src/qs_codec/models/encode_options.py +1 -1
  11. {qs_codec-0.2.0 → qs_codec-0.2.2}/src/qs_codec/utils/utils.py +10 -10
  12. qs_codec-0.2.2/tests/comparison/test_cases.json +306 -0
  13. qs_codec-0.2.2/tests/e2e/e2e_test.py +204 -0
  14. {qs_codec-0.2.0 → qs_codec-0.2.2}/tests/unit/decode_test.py +27 -27
  15. {qs_codec-0.2.0 → qs_codec-0.2.2}/tests/unit/encode_test.py +12 -0
  16. {qs_codec-0.2.0 → qs_codec-0.2.2}/tests/unit/example_test.py +4 -4
  17. {qs_codec-0.2.0 → qs_codec-0.2.2}/tests/unit/utils_test.py +4 -4
  18. qs_codec-0.2.0/tests/comparison/test_cases.json +0 -79
  19. qs_codec-0.2.0/tests/e2e/e2e_test.py +0 -49
  20. {qs_codec-0.2.0 → qs_codec-0.2.2}/.gitignore +0 -0
  21. {qs_codec-0.2.0 → qs_codec-0.2.2}/CODE-OF-CONDUCT.md +0 -0
  22. {qs_codec-0.2.0 → qs_codec-0.2.2}/LICENSE +0 -0
  23. {qs_codec-0.2.0 → qs_codec-0.2.2}/src/qs_codec/enums/__init__.py +0 -0
  24. {qs_codec-0.2.0 → qs_codec-0.2.2}/src/qs_codec/enums/charset.py +0 -0
  25. {qs_codec-0.2.0 → qs_codec-0.2.2}/src/qs_codec/enums/duplicates.py +0 -0
  26. {qs_codec-0.2.0 → qs_codec-0.2.2}/src/qs_codec/enums/format.py +0 -0
  27. {qs_codec-0.2.0 → qs_codec-0.2.2}/src/qs_codec/enums/list_format.py +0 -0
  28. {qs_codec-0.2.0 → qs_codec-0.2.2}/src/qs_codec/enums/sentinel.py +0 -0
  29. {qs_codec-0.2.0 → qs_codec-0.2.2}/src/qs_codec/models/__init__.py +0 -0
  30. {qs_codec-0.2.0 → qs_codec-0.2.2}/src/qs_codec/models/decode_options.py +0 -0
  31. {qs_codec-0.2.0 → qs_codec-0.2.2}/src/qs_codec/models/undefined.py +0 -0
  32. {qs_codec-0.2.0 → qs_codec-0.2.2}/src/qs_codec/models/weak_wrapper.py +0 -0
  33. {qs_codec-0.2.0 → qs_codec-0.2.2}/src/qs_codec/py.typed +0 -0
  34. {qs_codec-0.2.0 → qs_codec-0.2.2}/src/qs_codec/utils/__init__.py +0 -0
  35. {qs_codec-0.2.0 → qs_codec-0.2.2}/src/qs_codec/utils/decode_utils.py +0 -0
  36. {qs_codec-0.2.0 → qs_codec-0.2.2}/src/qs_codec/utils/encode_utils.py +0 -0
  37. {qs_codec-0.2.0 → qs_codec-0.2.2}/src/qs_codec/utils/str_utils.py +0 -0
  38. {qs_codec-0.2.0 → qs_codec-0.2.2}/tests/comparison/.gitignore +0 -0
  39. {qs_codec-0.2.0 → qs_codec-0.2.2}/tests/comparison/__init__.py +0 -0
  40. {qs_codec-0.2.0 → qs_codec-0.2.2}/tests/comparison/compare_outputs.sh +0 -0
  41. {qs_codec-0.2.0 → qs_codec-0.2.2}/tests/comparison/package.json +0 -0
  42. {qs_codec-0.2.0 → qs_codec-0.2.2}/tests/comparison/qs.js +0 -0
  43. {qs_codec-0.2.0 → qs_codec-0.2.2}/tests/comparison/qs.py +0 -0
  44. {qs_codec-0.2.0 → qs_codec-0.2.2}/tests/e2e/__init__.py +0 -0
  45. {qs_codec-0.2.0 → qs_codec-0.2.2}/tests/unit/__init__.py +0 -0
  46. {qs_codec-0.2.0 → qs_codec-0.2.2}/tests/unit/fixed_qs_issues_test.py +0 -0
  47. {qs_codec-0.2.0 → qs_codec-0.2.2}/tests/unit/weakref_test.py +0 -0
@@ -1,3 +1,12 @@
1
+ ## 0.2.2
2
+
3
+ * [FEAT] `decode` returns `dict[str, Any]` instead of `dict` ([#4](https://github.com/techouse/qs_codec/pull/4))
4
+ * [FIX] fix decoding encoded square brackets in key names
5
+
6
+ ## 0.2.1
7
+
8
+ * [CHORE] update dependencies
9
+
1
10
  ## 0.2.0
2
11
 
3
12
  * [CHORE] update dependencies
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: qs-codec
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: A query string encoding and decoding library for Python. Ported from qs for JavaScript.
5
5
  Project-URL: Homepage, https://techouse.github.io/qs_codec/
6
6
  Project-URL: Documentation, https://techouse.github.io/qs_codec/
@@ -29,8 +29,8 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
29
29
  Classifier: Topic :: Internet :: WWW/HTTP
30
30
  Classifier: Topic :: Software Development :: Libraries
31
31
  Requires-Python: >=3.8
32
- Requires-Dist: regex==2024.4.28
33
- Requires-Dist: types-regex==2024.4.28.20240430
32
+ Requires-Dist: regex>=2024.4.28
33
+ Requires-Dist: types-regex>=2024.4.28.20240506
34
34
  Description-Content-Type: text/x-rst
35
35
 
36
36
  qs-codec
@@ -72,7 +72,7 @@ dictionaries
72
72
  def decode(
73
73
  value: t.Optional[t.Union[str, t.Mapping]],
74
74
  options: qs_codec.DecodeOptions = qs_codec.DecodeOptions(),
75
- ) -> dict:
75
+ ) -> t.Dict[str, t.Any]:
76
76
  """Decodes a str or Mapping into a Dict.
77
77
 
78
78
  Providing custom DecodeOptions will override the default behavior."""
@@ -363,7 +363,7 @@ over this huge ``list``.
363
363
 
364
364
  import qs_codec
365
365
 
366
- assert qs_codec.decode('a[100]=b') == {'a': {100: 'b'}}
366
+ assert qs_codec.decode('a[100]=b') == {'a': {'100': 'b'}}
367
367
 
368
368
  This limit can be overridden by passing an `list_limit <https://techouse.github.io/qs_codec/qs_codec.models.html#qs_codec.models.decode_options.DecodeOptions.list_limit>`__
369
369
  option:
@@ -375,7 +375,7 @@ option:
375
375
  assert qs_codec.decode(
376
376
  'a[1]=b',
377
377
  qs_codec.DecodeOptions(list_limit=0),
378
- ) == {'a': {1: 'b'}}
378
+ ) == {'a': {'1': 'b'}}
379
379
 
380
380
  To disable ``list`` parsing entirely, set `parse_lists <https://techouse.github.io/qs_codec/qs_codec.models.html#qs_codec.models.decode_options.DecodeOptions.parse_lists>`__
381
381
  to ``False``.
@@ -387,7 +387,7 @@ to ``False``.
387
387
  assert qs_codec.decode(
388
388
  'a[]=b',
389
389
  qs_codec.DecodeOptions(parse_lists=False),
390
- ) == {'a': {0: 'b'}}
390
+ ) == {'a': {'0': 'b'}}
391
391
 
392
392
  If you mix notations, `decode <https://techouse.github.io/qs_codec/qs_codec.models.html#qs_codec.decode>`__ will merge the two items into a ``dict``:
393
393
 
@@ -395,7 +395,7 @@ If you mix notations, `decode <https://techouse.github.io/qs_codec/qs_codec.mode
395
395
 
396
396
  import qs_codec
397
397
 
398
- assert qs_codec.decode('a[0]=b&a[b]=c') == {'a': {0: 'b', 'b': 'c'}}
398
+ assert qs_codec.decode('a[0]=b&a[b]=c') == {'a': {'0': 'b', 'b': 'c'}}
399
399
 
400
400
  You can also create ``list``\ s of ``dict``\ s:
401
401
 
@@ -37,7 +37,7 @@ dictionaries
37
37
  def decode(
38
38
  value: t.Optional[t.Union[str, t.Mapping]],
39
39
  options: qs_codec.DecodeOptions = qs_codec.DecodeOptions(),
40
- ) -> dict:
40
+ ) -> t.Dict[str, t.Any]:
41
41
  """Decodes a str or Mapping into a Dict.
42
42
 
43
43
  Providing custom DecodeOptions will override the default behavior."""
@@ -328,7 +328,7 @@ over this huge ``list``.
328
328
 
329
329
  import qs_codec
330
330
 
331
- assert qs_codec.decode('a[100]=b') == {'a': {100: 'b'}}
331
+ assert qs_codec.decode('a[100]=b') == {'a': {'100': 'b'}}
332
332
 
333
333
  This limit can be overridden by passing an `list_limit <https://techouse.github.io/qs_codec/qs_codec.models.html#qs_codec.models.decode_options.DecodeOptions.list_limit>`__
334
334
  option:
@@ -340,7 +340,7 @@ option:
340
340
  assert qs_codec.decode(
341
341
  'a[1]=b',
342
342
  qs_codec.DecodeOptions(list_limit=0),
343
- ) == {'a': {1: 'b'}}
343
+ ) == {'a': {'1': 'b'}}
344
344
 
345
345
  To disable ``list`` parsing entirely, set `parse_lists <https://techouse.github.io/qs_codec/qs_codec.models.html#qs_codec.models.decode_options.DecodeOptions.parse_lists>`__
346
346
  to ``False``.
@@ -352,7 +352,7 @@ to ``False``.
352
352
  assert qs_codec.decode(
353
353
  'a[]=b',
354
354
  qs_codec.DecodeOptions(parse_lists=False),
355
- ) == {'a': {0: 'b'}}
355
+ ) == {'a': {'0': 'b'}}
356
356
 
357
357
  If you mix notations, `decode <https://techouse.github.io/qs_codec/qs_codec.models.html#qs_codec.decode>`__ will merge the two items into a ``dict``:
358
358
 
@@ -360,7 +360,7 @@ If you mix notations, `decode <https://techouse.github.io/qs_codec/qs_codec.mode
360
360
 
361
361
  import qs_codec
362
362
 
363
- assert qs_codec.decode('a[0]=b&a[b]=c') == {'a': {0: 'b', 'b': 'c'}}
363
+ assert qs_codec.decode('a[0]=b&a[b]=c') == {'a': {'0': 'b', 'b': 'c'}}
364
364
 
365
365
  You can also create ``list``\ s of ``dict``\ s:
366
366
 
@@ -11,7 +11,7 @@ dictionaries
11
11
  def decode(
12
12
  value: t.Optional[t.Union[str, t.Mapping]],
13
13
  options: qs_codec.DecodeOptions = qs_codec.DecodeOptions(),
14
- ) -> dict:
14
+ ) -> t.Dict[str, t.Any]:
15
15
  """Decodes a str or Mapping into a Dict.
16
16
 
17
17
  Providing custom DecodeOptions will override the default behavior."""
@@ -303,7 +303,7 @@ over this huge ``list``.
303
303
 
304
304
  import qs_codec
305
305
 
306
- assert qs_codec.decode('a[100]=b') == {'a': {100: 'b'}}
306
+ assert qs_codec.decode('a[100]=b') == {'a': {'100': 'b'}}
307
307
 
308
308
  This limit can be overridden by passing an :py:attr:`list_limit <qs_codec.models.decode_options.DecodeOptions.list_limit>`
309
309
  option:
@@ -315,7 +315,7 @@ option:
315
315
  assert qs_codec.decode(
316
316
  'a[1]=b',
317
317
  qs_codec.DecodeOptions(list_limit=0),
318
- ) == {'a': {1: 'b'}}
318
+ ) == {'a': {'1': 'b'}}
319
319
 
320
320
  To disable ``list`` parsing entirely, set :py:attr:`parse_lists <qs_codec.models.decode_options.DecodeOptions.parse_lists>`
321
321
  to ``False``.
@@ -327,7 +327,7 @@ to ``False``.
327
327
  assert qs_codec.decode(
328
328
  'a[]=b',
329
329
  qs_codec.DecodeOptions(parse_lists=False),
330
- ) == {'a': {0: 'b'}}
330
+ ) == {'a': {'0': 'b'}}
331
331
 
332
332
  If you mix notations, :py:attr:`decode <qs_codec.decode>` will merge the two items into a ``dict``:
333
333
 
@@ -335,7 +335,7 @@ If you mix notations, :py:attr:`decode <qs_codec.decode>` will merge the two ite
335
335
 
336
336
  import qs_codec
337
337
 
338
- assert qs_codec.decode('a[0]=b&a[b]=c') == {'a': {0: 'b', 'b': 'c'}}
338
+ assert qs_codec.decode('a[0]=b&a[b]=c') == {'a': {'0': 'b', 'b': 'c'}}
339
339
 
340
340
  You can also create ``list``\ s of ``dict``\ s:
341
341
 
@@ -38,8 +38,8 @@ classifiers = [
38
38
  "Topic :: Software Development :: Libraries",
39
39
  ]
40
40
  dependencies = [
41
- "regex==2024.4.28",
42
- "types-regex==2024.4.28.20240430",
41
+ "regex>=2024.4.28",
42
+ "types-regex>=2024.4.28.20240506",
43
43
  ]
44
44
  dynamic = ["version"]
45
45
 
@@ -1,5 +1,5 @@
1
- regex==2024.4.28
2
- types-regex==2024.4.28.20240430
1
+ regex>=2024.4.28
2
+ types-regex>=2024.4.28.20240506
3
3
  pytest>=8.1.2
4
4
  pytest-cov>=5.0.0
5
5
  mypy>=1.10.0
@@ -1,6 +1,6 @@
1
1
  """A query string encoding and decoding library for Python. Ported from qs_codec for JavaScript."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.2.2"
4
4
 
5
5
  from .decode import decode
6
6
  from .encode import encode
@@ -14,20 +14,25 @@ from .models.undefined import Undefined
14
14
  from .utils.utils import Utils
15
15
 
16
16
 
17
- def decode(value: t.Optional[t.Union[str, t.Mapping]], options: DecodeOptions = DecodeOptions()) -> dict:
17
+ def decode(
18
+ value: t.Optional[t.Union[str, t.Dict[str, t.Any]]], options: DecodeOptions = DecodeOptions()
19
+ ) -> t.Dict[str, t.Any]:
18
20
  """
19
21
  Decodes a ``str`` or ``Mapping`` into a ``dict``.
20
22
 
21
23
  Providing custom ``DecodeOptions`` will override the default behavior.
22
24
  """
25
+ obj: t.Dict[str, t.Any] = {}
26
+
23
27
  if not value:
24
- return dict()
28
+ return obj
25
29
 
26
- if not isinstance(value, (str, t.Mapping)):
30
+ if not isinstance(value, (str, dict)):
27
31
  raise ValueError("The input must be a String or a Dict")
28
32
 
29
- temp_obj: t.Optional[t.Mapping] = _parse_query_string_values(value, options) if isinstance(value, str) else value
30
- obj: t.Dict = dict()
33
+ temp_obj: t.Optional[t.Dict[str, t.Any]] = (
34
+ _parse_query_string_values(value, options) if isinstance(value, str) else value
35
+ )
31
36
 
32
37
  # Iterate over the keys and setup the new object
33
38
  if temp_obj:
@@ -49,10 +54,11 @@ def _parse_array_value(value: t.Any, options: DecodeOptions) -> t.Any:
49
54
  return value
50
55
 
51
56
 
52
- def _parse_query_string_values(value: str, options: DecodeOptions) -> t.Dict:
53
- obj: t.Dict = dict()
57
+ def _parse_query_string_values(value: str, options: DecodeOptions) -> t.Dict[str, t.Any]:
58
+ obj: t.Dict[str, t.Any] = {}
54
59
 
55
60
  clean_str: str = value.replace("?", "", 1) if options.ignore_query_prefix else value
61
+ clean_str = clean_str.replace("%5B", "[").replace("%5b", "[").replace("%5D", "]").replace("%5d", "]")
56
62
  limit: t.Optional[int] = None if isinf(options.parameter_limit) else options.parameter_limit # type: ignore [assignment]
57
63
  parts: t.List[str]
58
64
  if isinstance(options.delimiter, re.Pattern):
@@ -118,7 +124,7 @@ def _parse_object(
118
124
 
119
125
  i: int
120
126
  for i in reversed(range(len(chain))):
121
- obj: t.Optional[t.Any]
127
+ obj: t.Optional[t.Union[t.Dict[str, t.Any], t.List[t.Any]]]
122
128
  root: str = chain[i]
123
129
 
124
130
  if root == "[]" and options.parse_lists:
@@ -140,7 +146,7 @@ def _parse_object(
140
146
  index = None
141
147
 
142
148
  if not options.parse_lists and decoded_root == "":
143
- obj = {0: leaf}
149
+ obj = {"0": leaf}
144
150
  elif (
145
151
  index is not None
146
152
  and index >= 0
@@ -152,7 +158,7 @@ def _parse_object(
152
158
  obj = [Undefined() for _ in range(index + 1)]
153
159
  obj[index] = leaf
154
160
  else:
155
- obj[index if index is not None else decoded_root] = leaf
161
+ obj[str(index) if index is not None else decoded_root] = leaf
156
162
 
157
163
  leaf = obj
158
164
 
@@ -114,7 +114,7 @@ def _encode(
114
114
  prefix: t.Optional[str],
115
115
  comma_round_trip: t.Optional[bool],
116
116
  encoder: t.Optional[t.Callable[[t.Any, t.Optional[Charset], t.Optional[Format]], str]],
117
- serialize_date: t.Callable[[datetime], str],
117
+ serialize_date: t.Callable[[datetime], t.Optional[str]],
118
118
  sort: t.Optional[t.Callable[[t.Any, t.Any], int]],
119
119
  filter: t.Optional[t.Union[t.Callable, t.List[t.Union[str, int]]]],
120
120
  formatter: t.Optional[t.Callable[[str], str]],
@@ -63,7 +63,7 @@ class EncodeOptions:
63
63
  skip_nulls: bool = False
64
64
  """Set to ``True`` to completely skip encoding keys with ``None`` values."""
65
65
 
66
- serialize_date: t.Callable[[datetime], str] = EncodeUtils.serialize_date
66
+ serialize_date: t.Callable[[datetime], t.Optional[str]] = EncodeUtils.serialize_date
67
67
  """If you only want to override the serialization of ``datetime`` objects, you can provide a ``Callable``."""
68
68
 
69
69
  encoder: t.Callable[[t.Any, t.Optional[Charset], t.Optional[Format]], str] = field( # type: ignore [assignment]
@@ -15,10 +15,10 @@ class Utils:
15
15
 
16
16
  @staticmethod
17
17
  def merge(
18
- target: t.Optional[t.Union[t.Mapping, t.List, t.Tuple]],
19
- source: t.Optional[t.Union[t.Mapping, t.List, t.Tuple, t.Any]],
18
+ target: t.Optional[t.Union[t.Mapping[str, t.Any], t.List[t.Any], t.Tuple]],
19
+ source: t.Optional[t.Union[t.Mapping[str, t.Any], t.List[t.Any], t.Tuple, t.Any]],
20
20
  options: DecodeOptions = DecodeOptions(),
21
- ) -> t.Union[t.Dict, t.List, t.Tuple, t.Any]:
21
+ ) -> t.Union[t.Dict[str, t.Any], t.List, t.Tuple, t.Any]:
22
22
  """Merge two objects together."""
23
23
  if source is None:
24
24
  return target
@@ -60,7 +60,7 @@ class Utils:
60
60
  if isinstance(source, (list, tuple)):
61
61
  target = {
62
62
  **target,
63
- **{i: item for i, item in enumerate(source) if not isinstance(item, Undefined)},
63
+ **{str(i): item for i, item in enumerate(source) if not isinstance(item, Undefined)},
64
64
  }
65
65
  elif source is not None:
66
66
  if not isinstance(target, (list, tuple)) and isinstance(source, (list, tuple)):
@@ -72,7 +72,7 @@ class Utils:
72
72
  if target is None or not isinstance(target, t.Mapping):
73
73
  if isinstance(target, (list, tuple)):
74
74
  return {
75
- **{i: item for i, item in enumerate(target) if not isinstance(item, Undefined)},
75
+ **{str(i): item for i, item in enumerate(target) if not isinstance(item, Undefined)},
76
76
  **source,
77
77
  }
78
78
 
@@ -86,8 +86,8 @@ class Utils:
86
86
  if not isinstance(el, Undefined)
87
87
  ]
88
88
 
89
- merge_target: t.Dict = (
90
- dict(enumerate(el for el in source if not isinstance(el, Undefined)))
89
+ merge_target: t.Dict[str, t.Any] = (
90
+ {str(i): el for i, el in enumerate(source) if not isinstance(el, Undefined)}
91
91
  if isinstance(target, (list, tuple)) and not isinstance(source, (list, tuple))
92
92
  else copy.deepcopy(dict(target) if not isinstance(target, dict) else target)
93
93
  )
@@ -95,15 +95,15 @@ class Utils:
95
95
  return {
96
96
  **merge_target,
97
97
  **{
98
- key: Utils.merge(merge_target[key], value, options) if key in merge_target else value
98
+ str(key): Utils.merge(merge_target[key], value, options) if key in merge_target else value
99
99
  for key, value in source.items()
100
100
  },
101
101
  }
102
102
 
103
103
  @staticmethod
104
- def compact(value: t.Dict) -> t.Dict:
104
+ def compact(value: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]:
105
105
  """Remove all `Undefined` values from a dictionary."""
106
- queue: t.List[t.Dict] = [{"obj": {"o": value}, "prop": "o"}]
106
+ queue: t.List[t.Dict[str, t.Any]] = [{"obj": {"o": value}, "prop": "o"}]
107
107
  refs: t.List = []
108
108
 
109
109
  for i in range(len(queue)): # pylint: disable=C0200
@@ -0,0 +1,306 @@
1
+ [
2
+ {
3
+ "data": {},
4
+ "encoded": ""
5
+ },
6
+ {
7
+ "data": {
8
+ "a": "b"
9
+ },
10
+ "encoded": "a=b"
11
+ },
12
+ {
13
+ "data": {
14
+ "a": "b",
15
+ "c": "d"
16
+ },
17
+ "encoded": "a=b&c=d"
18
+ },
19
+ {
20
+ "data": {
21
+ "a": "b",
22
+ "c": "d",
23
+ "e": "f"
24
+ },
25
+ "encoded": "a=b&c=d&e=f"
26
+ },
27
+ {
28
+ "data": {
29
+ "a": "b",
30
+ "c": "d",
31
+ "e": [
32
+ "f",
33
+ "g",
34
+ "h"
35
+ ]
36
+ },
37
+ "encoded": "a=b&c=d&e[0]=f&e[1]=g&e[2]=h"
38
+ },
39
+ {
40
+ "data": {
41
+ "a": "b",
42
+ "c": "d",
43
+ "e": [
44
+ "f",
45
+ "g",
46
+ "h"
47
+ ],
48
+ "i": {
49
+ "j": "k",
50
+ "l": "m"
51
+ }
52
+ },
53
+ "encoded": "a=b&c=d&e[0]=f&e[1]=g&e[2]=h&i[j]=k&i[l]=m"
54
+ },
55
+ {
56
+ "data": {
57
+ "filters": {
58
+ "$or": [
59
+ {
60
+ "date": {
61
+ "$eq": "2020-01-01"
62
+ }
63
+ },
64
+ {
65
+ "date": {
66
+ "$eq": "2020-01-02"
67
+ }
68
+ }
69
+ ],
70
+ "author": {
71
+ "name": {
72
+ "$eq": "John Doe"
73
+ }
74
+ }
75
+ }
76
+ },
77
+ "encoded": "filters[$or][0][date][$eq]=2020-01-01&filters[$or][1][date][$eq]=2020-01-02&filters[author][name][$eq]=John Doe"
78
+ },
79
+ {
80
+ "data": {
81
+ "commentsEmbedResponse": [
82
+ {
83
+ "id": "1",
84
+ "post_id": "1",
85
+ "someId": "ma018-9ha12",
86
+ "text": "Hello",
87
+ "replies": [
88
+ {
89
+ "id": "3",
90
+ "comment_id": "1",
91
+ "someId": "ma020-9ha15",
92
+ "text": "Hello"
93
+ }
94
+ ]
95
+ },
96
+ {
97
+ "id": "2",
98
+ "post_id": "1",
99
+ "someId": "mw012-7ha19",
100
+ "text": "How are you?",
101
+ "replies": [
102
+ {
103
+ "id": "4",
104
+ "comment_id": "2",
105
+ "someId": "mw023-9ha18",
106
+ "text": "Hello"
107
+ },
108
+ {
109
+ "id": "5",
110
+ "comment_id": "2",
111
+ "someId": "mw035-0ha22",
112
+ "text": "Hello"
113
+ }
114
+ ]
115
+ }
116
+ ]
117
+ },
118
+ "encoded": "commentsEmbedResponse[0][id]=1&commentsEmbedResponse[0][post_id]=1&commentsEmbedResponse[0][someId]=ma018-9ha12&commentsEmbedResponse[0][text]=Hello&commentsEmbedResponse[0][replies][0][id]=3&commentsEmbedResponse[0][replies][0][comment_id]=1&commentsEmbedResponse[0][replies][0][someId]=ma020-9ha15&commentsEmbedResponse[0][replies][0][text]=Hello&commentsEmbedResponse[1][id]=2&commentsEmbedResponse[1][post_id]=1&commentsEmbedResponse[1][someId]=mw012-7ha19&commentsEmbedResponse[1][text]=How are you?&commentsEmbedResponse[1][replies][0][id]=4&commentsEmbedResponse[1][replies][0][comment_id]=2&commentsEmbedResponse[1][replies][0][someId]=mw023-9ha18&commentsEmbedResponse[1][replies][0][text]=Hello&commentsEmbedResponse[1][replies][1][id]=5&commentsEmbedResponse[1][replies][1][comment_id]=2&commentsEmbedResponse[1][replies][1][someId]=mw035-0ha22&commentsEmbedResponse[1][replies][1][text]=Hello"
119
+ },
120
+ {
121
+ "data": {
122
+ "commentsResponse": [
123
+ {
124
+ "id": "1",
125
+ "post_id": "1",
126
+ "someId": "ma018-9ha12",
127
+ "text": "Hello",
128
+ "replies": [
129
+ {
130
+ "id": "3",
131
+ "comment_id": "1",
132
+ "someId": "ma020-9ha15",
133
+ "text": "Hello"
134
+ }
135
+ ]
136
+ },
137
+ {
138
+ "id": "2",
139
+ "post_id": "1",
140
+ "someId": "mw012-7ha19",
141
+ "text": "How are you?",
142
+ "replies": [
143
+ {
144
+ "id": "4",
145
+ "comment_id": "2",
146
+ "someId": "mw023-9ha18",
147
+ "text": "Hello"
148
+ },
149
+ {
150
+ "id": "5",
151
+ "comment_id": "2",
152
+ "someId": "mw035-0ha22",
153
+ "text": "Hello"
154
+ }
155
+ ]
156
+ }
157
+ ]
158
+ },
159
+ "encoded": "commentsResponse[0][id]=1&commentsResponse[0][post_id]=1&commentsResponse[0][someId]=ma018-9ha12&commentsResponse[0][text]=Hello&commentsResponse[0][replies][0][id]=3&commentsResponse[0][replies][0][comment_id]=1&commentsResponse[0][replies][0][someId]=ma020-9ha15&commentsResponse[0][replies][0][text]=Hello&commentsResponse[1][id]=2&commentsResponse[1][post_id]=1&commentsResponse[1][someId]=mw012-7ha19&commentsResponse[1][text]=How are you?&commentsResponse[1][replies][0][id]=4&commentsResponse[1][replies][0][comment_id]=2&commentsResponse[1][replies][0][someId]=mw023-9ha18&commentsResponse[1][replies][0][text]=Hello&commentsResponse[1][replies][1][id]=5&commentsResponse[1][replies][1][comment_id]=2&commentsResponse[1][replies][1][someId]=mw035-0ha22&commentsResponse[1][replies][1][text]=Hello"
160
+ },
161
+ {
162
+ "data": {
163
+ "data": {
164
+ "id": "1",
165
+ "someId": "af621-4aa41",
166
+ "text": "Lorem Ipsum Dolor",
167
+ "user": {
168
+ "firstname": "John",
169
+ "lastname": "Doe",
170
+ "age": "25"
171
+ },
172
+ "relationships": {
173
+ "tags": {
174
+ "data": [
175
+ {
176
+ "name": "super"
177
+ },
178
+ {
179
+ "name": "awesome"
180
+ }
181
+ ]
182
+ }
183
+ }
184
+ }
185
+ },
186
+ "encoded": "data[id]=1&data[someId]=af621-4aa41&data[text]=Lorem Ipsum Dolor&data[user][firstname]=John&data[user][lastname]=Doe&data[user][age]=25&data[relationships][tags][data][0][name]=super&data[relationships][tags][data][1][name]=awesome"
187
+ },
188
+ {
189
+ "data": {
190
+ "id": "1",
191
+ "someId": "af621-4aa41",
192
+ "text": "Lorem Ipsum Dolor",
193
+ "user": {
194
+ "firstname": "John",
195
+ "lastname": "Doe",
196
+ "age": "25"
197
+ },
198
+ "relationships": {
199
+ "tags": [
200
+ {
201
+ "name": "super"
202
+ },
203
+ {
204
+ "name": "awesome"
205
+ }
206
+ ]
207
+ }
208
+ },
209
+ "encoded": "id=1&someId=af621-4aa41&text=Lorem Ipsum Dolor&user[firstname]=John&user[lastname]=Doe&user[age]=25&relationships[tags][0][name]=super&relationships[tags][1][name]=awesome"
210
+ },
211
+ {
212
+ "data": {
213
+ "postsResponse": [
214
+ {
215
+ "id": "1",
216
+ "someId": "du761-8bc98",
217
+ "text": "Lorem Ipsum Dolor",
218
+ "user": {
219
+ "firstname": "John",
220
+ "lastname": "Doe",
221
+ "age": "25"
222
+ },
223
+ "relationships": {
224
+ "tags": [
225
+ {
226
+ "name": "super"
227
+ },
228
+ {
229
+ "name": "awesome"
230
+ }
231
+ ]
232
+ }
233
+ },
234
+ {
235
+ "id": "1",
236
+ "someId": "pa813-7jx02",
237
+ "text": "Lorem Ipsum Dolor",
238
+ "user": {
239
+ "firstname": "Mary",
240
+ "lastname": "Doe",
241
+ "age": "25"
242
+ },
243
+ "relationships": {
244
+ "tags": [
245
+ {
246
+ "name": "super"
247
+ },
248
+ {
249
+ "name": "awesome"
250
+ }
251
+ ]
252
+ }
253
+ }
254
+ ]
255
+ },
256
+ "encoded": "postsResponse[0][id]=1&postsResponse[0][someId]=du761-8bc98&postsResponse[0][text]=Lorem Ipsum Dolor&postsResponse[0][user][firstname]=John&postsResponse[0][user][lastname]=Doe&postsResponse[0][user][age]=25&postsResponse[0][relationships][tags][0][name]=super&postsResponse[0][relationships][tags][1][name]=awesome&postsResponse[1][id]=1&postsResponse[1][someId]=pa813-7jx02&postsResponse[1][text]=Lorem Ipsum Dolor&postsResponse[1][user][firstname]=Mary&postsResponse[1][user][lastname]=Doe&postsResponse[1][user][age]=25&postsResponse[1][relationships][tags][0][name]=super&postsResponse[1][relationships][tags][1][name]=awesome"
257
+ },
258
+ {
259
+ "data": {
260
+ "posts": [
261
+ {
262
+ "id": "1",
263
+ "someId": "du761-8bc98",
264
+ "text": "Lorem Ipsum Dolor",
265
+ "user": {
266
+ "firstname": "John",
267
+ "lastname": "Doe",
268
+ "age": "25"
269
+ },
270
+ "relationships": {
271
+ "tags": [
272
+ {
273
+ "name": "super"
274
+ },
275
+ {
276
+ "name": "awesome"
277
+ }
278
+ ]
279
+ }
280
+ },
281
+ {
282
+ "id": "1",
283
+ "someId": "pa813-7jx02",
284
+ "text": "Lorem Ipsum Dolor",
285
+ "user": {
286
+ "firstname": "Mary",
287
+ "lastname": "Doe",
288
+ "age": "25"
289
+ },
290
+ "relationships": {
291
+ "tags": [
292
+ {
293
+ "name": "super"
294
+ },
295
+ {
296
+ "name": "awesome"
297
+ }
298
+ ]
299
+ }
300
+ }
301
+ ],
302
+ "total": "2"
303
+ },
304
+ "encoded": "posts[0][id]=1&posts[0][someId]=du761-8bc98&posts[0][text]=Lorem Ipsum Dolor&posts[0][user][firstname]=John&posts[0][user][lastname]=Doe&posts[0][user][age]=25&posts[0][relationships][tags][0][name]=super&posts[0][relationships][tags][1][name]=awesome&posts[1][id]=1&posts[1][someId]=pa813-7jx02&posts[1][text]=Lorem Ipsum Dolor&posts[1][user][firstname]=Mary&posts[1][user][lastname]=Doe&posts[1][user][age]=25&posts[1][relationships][tags][0][name]=super&posts[1][relationships][tags][1][name]=awesome&total=2"
305
+ }
306
+ ]