From 6060876c3945f2443efb44a48b98f834e89d8aa8 Mon Sep 17 00:00:00 2001 From: Max Muoto Date: Thu, 8 May 2025 18:57:41 -0500 Subject: [PATCH 01/17] feat: Improve `json.dumps`, `json.dump` typing --- stdlib/json/__init__.pyi | 47 +++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/stdlib/json/__init__.pyi b/stdlib/json/__init__.pyi index 63e9718ee151..ee1b3d6af714 100644 --- a/stdlib/json/__init__.pyi +++ b/stdlib/json/__init__.pyi @@ -1,14 +1,34 @@ from _typeshed import SupportsRead, SupportsWrite from collections.abc import Callable -from typing import Any +from typing import Any, TypeAlias, TypeVar, overload from .decoder import JSONDecodeError as JSONDecodeError, JSONDecoder as JSONDecoder from .encoder import JSONEncoder as JSONEncoder __all__ = ["dump", "dumps", "load", "loads", "JSONDecoder", "JSONDecodeError", "JSONEncoder"] +_T = TypeVar("_T") + +_JSON: TypeAlias = dict[str, _JSON] | list[_JSON] | str | int | float | bool | None + +@overload +def dumps( + obj: _JSON, + *, + skipkeys: bool = False, + ensure_ascii: bool = True, + check_circular: bool = True, + allow_nan: bool = True, + cls: type[JSONEncoder] | None = None, + indent: None | int | str = None, + separators: tuple[str, str] | None = None, + default: None = None, + sort_keys: bool = False, + **kwds: Any, +) -> str: ... +@overload def dumps( - obj: Any, + obj: _T, *, skipkeys: bool = False, ensure_ascii: bool = True, @@ -17,12 +37,29 @@ def dumps( cls: type[JSONEncoder] | None = None, indent: None | int | str = None, separators: tuple[str, str] | None = None, - default: Callable[[Any], Any] | None = None, + default: Callable[[_T], _JSON], sort_keys: bool = False, **kwds: Any, ) -> str: ... +@overload +def dump( + obj: _JSON, + fp: SupportsWrite[str], + *, + skipkeys: bool = False, + ensure_ascii: bool = True, + check_circular: bool = True, + allow_nan: bool = True, + cls: type[JSONEncoder] | None = None, + indent: None | int | str = None, + separators: tuple[str, str] | None = None, + default: None = None, + sort_keys: bool = False, + **kwds: Any, +) -> None: ... +@overload def dump( - obj: Any, + obj: _T, fp: SupportsWrite[str], *, skipkeys: bool = False, @@ -32,7 +69,7 @@ def dump( cls: type[JSONEncoder] | None = None, indent: None | int | str = None, separators: tuple[str, str] | None = None, - default: Callable[[Any], Any] | None = None, + default: Callable[[_T], _JSON], sort_keys: bool = False, **kwds: Any, ) -> None: ... From f7c16d0745a3762e3b327c80fcd156fa87cda80b Mon Sep 17 00:00:00 2001 From: Max Muoto Date: Thu, 8 May 2025 18:59:52 -0500 Subject: [PATCH 02/17] Tweak --- stdlib/json/__init__.pyi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stdlib/json/__init__.pyi b/stdlib/json/__init__.pyi index ee1b3d6af714..9160a102c1de 100644 --- a/stdlib/json/__init__.pyi +++ b/stdlib/json/__init__.pyi @@ -1,6 +1,7 @@ from _typeshed import SupportsRead, SupportsWrite from collections.abc import Callable -from typing import Any, TypeAlias, TypeVar, overload +from typing import Any, TypeVar, overload +from typing_extensions import TypeAlias from .decoder import JSONDecodeError as JSONDecodeError, JSONDecoder as JSONDecoder from .encoder import JSONEncoder as JSONEncoder From c741607d2e38c5099ccbae6c843a29b4bfbd379b Mon Sep 17 00:00:00 2001 From: Max Muoto Date: Thu, 8 May 2025 19:09:10 -0500 Subject: [PATCH 03/17] Add test --- stdlib/@tests/test_cases/check_json.py | 55 ++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 stdlib/@tests/test_cases/check_json.py diff --git a/stdlib/@tests/test_cases/check_json.py b/stdlib/@tests/test_cases/check_json.py new file mode 100644 index 000000000000..49db8ea16242 --- /dev/null +++ b/stdlib/@tests/test_cases/check_json.py @@ -0,0 +1,55 @@ +import json +from decimal import Decimal + + +# By default, json.dumps() will not accept any non-serializable objects. +class CustomClass: ... + + +json.dumps(CustomClass()) # Error +json.dumps(object()) # Error +json.dumps(Decimal(1)) # Error + +# Serializable types are supported, included nested JSON. +json.dumps( + { + "a": 34, + "b": [1, 2, 3], + "c": { + "d": "hello", + "e": False, + }, + } +) +json.dumps( + { + "numbers": [1, 2, 3, 4, 5], + "strings": ["hello", "world"], + "booleans": [True, False], + "null": None, + "nested": {"array": [[1, 2], [3, 4.34]], "object": {"x": 1, "y": 2}}, + } +) +json.dumps({"empty_array": [], "empty_object": {}, "float": 3.14, "scientific": 1.23e-4, "unicode": "Hello 世界"}) +json.dumps(1) +json.dumps(1.23) +json.dumps(True) +json.dumps(False) +json.dumps(None) +json.dumps("hello") + + +# Custom types are supported when a custom encoder is provided. +def decimal_encoder(obj: Decimal) -> float: + return float(obj) + + +json.dumps(Decimal(1), default=decimal_encoder) + + +# If the custom encoder doesn't return JSON, it will raise. +def custom_encoder(obj: Decimal) -> Decimal: + return obj + + +json.dumps(Decimal(1), default=custom_encoder) # type: ignore From 2a034188ff0ad6b6118b6be86625ef21a285bb23 Mon Sep 17 00:00:00 2001 From: Max Muoto Date: Thu, 8 May 2025 19:10:37 -0500 Subject: [PATCH 04/17] Fixes --- stdlib/@tests/test_cases/check_json.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/stdlib/@tests/test_cases/check_json.py b/stdlib/@tests/test_cases/check_json.py index 49db8ea16242..c3bcf58c05f2 100644 --- a/stdlib/@tests/test_cases/check_json.py +++ b/stdlib/@tests/test_cases/check_json.py @@ -2,13 +2,13 @@ from decimal import Decimal -# By default, json.dumps() will not accept any non-serializable objects. +# By default, json.dumps() will not accept any non JSON-serializable objects. class CustomClass: ... -json.dumps(CustomClass()) # Error -json.dumps(object()) # Error -json.dumps(Decimal(1)) # Error +json.dumps(CustomClass()) # type: ignore +json.dumps(object()) # type: ignore +json.dumps(Decimal(1)) # type: ignore # Serializable types are supported, included nested JSON. json.dumps( @@ -47,7 +47,7 @@ def decimal_encoder(obj: Decimal) -> float: json.dumps(Decimal(1), default=decimal_encoder) -# If the custom encoder doesn't return JSON, it will raise. +# If the custom encoder doesn't return JSON, it will lead a typing error.. def custom_encoder(obj: Decimal) -> Decimal: return obj From 07f1922acffa4b9a7ad9a0ca9b1c40e2359cfb55 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 9 May 2025 00:10:50 +0000 Subject: [PATCH 05/17] [pre-commit.ci] auto fixes from pre-commit.com hooks --- stdlib/@tests/test_cases/check_json.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/stdlib/@tests/test_cases/check_json.py b/stdlib/@tests/test_cases/check_json.py index c3bcf58c05f2..2d000f662843 100644 --- a/stdlib/@tests/test_cases/check_json.py +++ b/stdlib/@tests/test_cases/check_json.py @@ -11,16 +11,7 @@ class CustomClass: ... json.dumps(Decimal(1)) # type: ignore # Serializable types are supported, included nested JSON. -json.dumps( - { - "a": 34, - "b": [1, 2, 3], - "c": { - "d": "hello", - "e": False, - }, - } -) +json.dumps({"a": 34, "b": [1, 2, 3], "c": {"d": "hello", "e": False}}) json.dumps( { "numbers": [1, 2, 3, 4, 5], From 87b10d5b50c4240723decbef14f6d309bc55ff01 Mon Sep 17 00:00:00 2001 From: Max Muoto Date: Thu, 8 May 2025 19:12:31 -0500 Subject: [PATCH 06/17] Tweak test --- stdlib/@tests/test_cases/check_json.py | 1 - 1 file changed, 1 deletion(-) diff --git a/stdlib/@tests/test_cases/check_json.py b/stdlib/@tests/test_cases/check_json.py index 2d000f662843..bad385adad23 100644 --- a/stdlib/@tests/test_cases/check_json.py +++ b/stdlib/@tests/test_cases/check_json.py @@ -21,7 +21,6 @@ class CustomClass: ... "nested": {"array": [[1, 2], [3, 4.34]], "object": {"x": 1, "y": 2}}, } ) -json.dumps({"empty_array": [], "empty_object": {}, "float": 3.14, "scientific": 1.23e-4, "unicode": "Hello 世界"}) json.dumps(1) json.dumps(1.23) json.dumps(True) From 9d548a65799803444924a4e2c814df107d99bdd3 Mon Sep 17 00:00:00 2001 From: Max Muoto Date: Thu, 8 May 2025 19:26:34 -0500 Subject: [PATCH 07/17] Tweak --- stdlib/json/__init__.pyi | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/stdlib/json/__init__.pyi b/stdlib/json/__init__.pyi index 9160a102c1de..57436855170d 100644 --- a/stdlib/json/__init__.pyi +++ b/stdlib/json/__init__.pyi @@ -10,7 +10,11 @@ __all__ = ["dump", "dumps", "load", "loads", "JSONDecoder", "JSONDecodeError", " _T = TypeVar("_T") -_JSON: TypeAlias = dict[str, _JSON] | list[_JSON] | str | int | float | bool | None +# We could use `_JSON: TypeAlias = dict[str, _JSON] | list[_JSON] | str | int | float | bool | None` +# as the type, but that forces users to define their own custom JSON types when using `json.dumps() +# or `json.dump()` on a passed in value. Instead we opt for a more permissive type that focuses on +# checking non-nested structures. +_JSON: TypeAlias = dict[str, Any] | list[Any] | str | int | float | bool | None @overload def dumps( From 07cf9d06e5359d2689e8346e51cbbecae7959394 Mon Sep 17 00:00:00 2001 From: Max Muoto Date: Thu, 8 May 2025 19:27:14 -0500 Subject: [PATCH 08/17] Tweak --- stdlib/json/__init__.pyi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stdlib/json/__init__.pyi b/stdlib/json/__init__.pyi index 57436855170d..1a06b12e5d2c 100644 --- a/stdlib/json/__init__.pyi +++ b/stdlib/json/__init__.pyi @@ -14,7 +14,8 @@ _T = TypeVar("_T") # as the type, but that forces users to define their own custom JSON types when using `json.dumps() # or `json.dump()` on a passed in value. Instead we opt for a more permissive type that focuses on # checking non-nested structures. -_JSON: TypeAlias = dict[str, Any] | list[Any] | str | int | float | bool | None +_JSONValue: TypeAlias = str | int | float | bool | None +_JSON: TypeAlias = dict[str, Any] | list[_JSONValue] | str | int | float | bool | None @overload def dumps( From c51d852f862e655560b54b628e3ca2d35cfa6fbb Mon Sep 17 00:00:00 2001 From: Max Muoto Date: Thu, 8 May 2025 19:29:58 -0500 Subject: [PATCH 09/17] Tweak --- stdlib/@tests/test_cases/check_json.py | 3 +++ stdlib/json/__init__.pyi | 7 +------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/stdlib/@tests/test_cases/check_json.py b/stdlib/@tests/test_cases/check_json.py index bad385adad23..2f0b3a3e5bc2 100644 --- a/stdlib/@tests/test_cases/check_json.py +++ b/stdlib/@tests/test_cases/check_json.py @@ -28,6 +28,9 @@ class CustomClass: ... json.dumps(None) json.dumps("hello") +x: dict[str, float | int] = {"a": 1, "b": 2.0} +json.dumps(x) + # Custom types are supported when a custom encoder is provided. def decimal_encoder(obj: Decimal) -> float: diff --git a/stdlib/json/__init__.pyi b/stdlib/json/__init__.pyi index 1a06b12e5d2c..ed615b48329c 100644 --- a/stdlib/json/__init__.pyi +++ b/stdlib/json/__init__.pyi @@ -10,12 +10,7 @@ __all__ = ["dump", "dumps", "load", "loads", "JSONDecoder", "JSONDecodeError", " _T = TypeVar("_T") -# We could use `_JSON: TypeAlias = dict[str, _JSON] | list[_JSON] | str | int | float | bool | None` -# as the type, but that forces users to define their own custom JSON types when using `json.dumps() -# or `json.dump()` on a passed in value. Instead we opt for a more permissive type that focuses on -# checking non-nested structures. -_JSONValue: TypeAlias = str | int | float | bool | None -_JSON: TypeAlias = dict[str, Any] | list[_JSONValue] | str | int | float | bool | None +_JSON: TypeAlias = dict[str, _JSON] | list[_JSON] | str | float | bool | None @overload def dumps( From 454786b624649f8ca901717601f0ecc47b9fb5e5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 9 May 2025 00:32:01 +0000 Subject: [PATCH 10/17] [pre-commit.ci] auto fixes from pre-commit.com hooks --- stdlib/@tests/test_cases/check_json.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stdlib/@tests/test_cases/check_json.py b/stdlib/@tests/test_cases/check_json.py index 2f0b3a3e5bc2..9e0f165f1b13 100644 --- a/stdlib/@tests/test_cases/check_json.py +++ b/stdlib/@tests/test_cases/check_json.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import json from decimal import Decimal From 6d07be5ad61d66c7ee8b4bf24fb441371604f3fc Mon Sep 17 00:00:00 2001 From: Max Muoto Date: Thu, 8 May 2025 19:32:06 -0500 Subject: [PATCH 11/17] Further broaden --- stdlib/@tests/test_cases/check_json.py | 3 +++ stdlib/json/__init__.pyi | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/stdlib/@tests/test_cases/check_json.py b/stdlib/@tests/test_cases/check_json.py index 9e0f165f1b13..292ba00faaa7 100644 --- a/stdlib/@tests/test_cases/check_json.py +++ b/stdlib/@tests/test_cases/check_json.py @@ -33,6 +33,9 @@ class CustomClass: ... x: dict[str, float | int] = {"a": 1, "b": 2.0} json.dumps(x) +z: dict[str, dict[str, dict[str, list[int]]]] = {"a": {"b": {"c": [1, 2, 3]}}} +json.dumps(z) + # Custom types are supported when a custom encoder is provided. def decimal_encoder(obj: Decimal) -> float: diff --git a/stdlib/json/__init__.pyi b/stdlib/json/__init__.pyi index ed615b48329c..9b58c12a6a7d 100644 --- a/stdlib/json/__init__.pyi +++ b/stdlib/json/__init__.pyi @@ -1,5 +1,5 @@ from _typeshed import SupportsRead, SupportsWrite -from collections.abc import Callable +from collections.abc import Callable, Mapping, Sequence from typing import Any, TypeVar, overload from typing_extensions import TypeAlias @@ -10,7 +10,7 @@ __all__ = ["dump", "dumps", "load", "loads", "JSONDecoder", "JSONDecodeError", " _T = TypeVar("_T") -_JSON: TypeAlias = dict[str, _JSON] | list[_JSON] | str | float | bool | None +_JSON: TypeAlias = Mapping[str, _JSON] | Sequence[_JSON] | str | float | bool | None @overload def dumps( From a05d94a6dbe3ae7e519b94ca880157bc3fc0c67d Mon Sep 17 00:00:00 2001 From: Max Muoto Date: Thu, 8 May 2025 19:39:22 -0500 Subject: [PATCH 12/17] Add json.dump tests --- stdlib/@tests/test_cases/check_json.py | 34 ++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/stdlib/@tests/test_cases/check_json.py b/stdlib/@tests/test_cases/check_json.py index 292ba00faaa7..e870bd14f2c0 100644 --- a/stdlib/@tests/test_cases/check_json.py +++ b/stdlib/@tests/test_cases/check_json.py @@ -1,19 +1,28 @@ -from __future__ import annotations - import json from decimal import Decimal +class _File: + def write(self, s: str) -> int: ... + + +fp = _File() + + # By default, json.dumps() will not accept any non JSON-serializable objects. class CustomClass: ... json.dumps(CustomClass()) # type: ignore +json.dump(CustomClass(), fp) # type: ignore json.dumps(object()) # type: ignore +json.dump(object(), fp) # type: ignore json.dumps(Decimal(1)) # type: ignore +json.dump(Decimal(1), fp) # type: ignore # Serializable types are supported, included nested JSON. json.dumps({"a": 34, "b": [1, 2, 3], "c": {"d": "hello", "e": False}}) +json.dump({"a": 34, "b": [1, 2, 3], "c": {"d": "hello", "e": False}}, fp) json.dumps( { "numbers": [1, 2, 3, 4, 5], @@ -23,18 +32,31 @@ class CustomClass: ... "nested": {"array": [[1, 2], [3, 4.34]], "object": {"x": 1, "y": 2}}, } ) +json.dump( + { + "numbers": [1, 2, 3, 4, 5], + "strings": ["hello", "world"], + "booleans": [True, False], + "null": None, + "nested": {"array": [[1, 2], [3, 4.34]], "object": {"x": 1, "y": 2}}, + }, + fp, +) json.dumps(1) +json.dump(1, fp) json.dumps(1.23) +json.dump(1.23, fp) json.dumps(True) -json.dumps(False) -json.dumps(None) -json.dumps("hello") +json.dump(True, fp) +# Test explicit nested types that might cause variance issues. x: dict[str, float | int] = {"a": 1, "b": 2.0} json.dumps(x) +json.dump(x, fp) z: dict[str, dict[str, dict[str, list[int]]]] = {"a": {"b": {"c": [1, 2, 3]}}} json.dumps(z) +json.dump(z, fp) # Custom types are supported when a custom encoder is provided. @@ -43,6 +65,7 @@ def decimal_encoder(obj: Decimal) -> float: json.dumps(Decimal(1), default=decimal_encoder) +json.dump(Decimal(1), fp, default=decimal_encoder) # If the custom encoder doesn't return JSON, it will lead a typing error.. @@ -51,3 +74,4 @@ def custom_encoder(obj: Decimal) -> Decimal: json.dumps(Decimal(1), default=custom_encoder) # type: ignore +json.dump(Decimal(1), fp, default=custom_encoder) # type: ignore From fe7076771ec53976e309a9b676d053c106f731f5 Mon Sep 17 00:00:00 2001 From: Max Muoto Date: Thu, 8 May 2025 21:15:55 -0500 Subject: [PATCH 13/17] Add future import --- stdlib/@tests/test_cases/check_json.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stdlib/@tests/test_cases/check_json.py b/stdlib/@tests/test_cases/check_json.py index e870bd14f2c0..febee56c0d96 100644 --- a/stdlib/@tests/test_cases/check_json.py +++ b/stdlib/@tests/test_cases/check_json.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import json from decimal import Decimal From b16b4f66f489a05e485783425ccddc1adf3459c5 Mon Sep 17 00:00:00 2001 From: Max Muoto Date: Thu, 8 May 2025 21:33:23 -0500 Subject: [PATCH 14/17] Support typed dictionaries --- stdlib/@tests/test_cases/check_json.py | 9 +++++++++ stdlib/json/__init__.pyi | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/stdlib/@tests/test_cases/check_json.py b/stdlib/@tests/test_cases/check_json.py index febee56c0d96..9ef267b9847f 100644 --- a/stdlib/@tests/test_cases/check_json.py +++ b/stdlib/@tests/test_cases/check_json.py @@ -2,6 +2,7 @@ import json from decimal import Decimal +from typing import TypedDict class _File: @@ -77,3 +78,11 @@ def custom_encoder(obj: Decimal) -> Decimal: json.dumps(Decimal(1), default=custom_encoder) # type: ignore json.dump(Decimal(1), fp, default=custom_encoder) # type: ignore + + +class MyTypedDict(TypedDict): + a: str + b: str + + +json.dumps(MyTypedDict(a="hello", b="world")) diff --git a/stdlib/json/__init__.pyi b/stdlib/json/__init__.pyi index 9b58c12a6a7d..07a858cea1cf 100644 --- a/stdlib/json/__init__.pyi +++ b/stdlib/json/__init__.pyi @@ -10,7 +10,9 @@ __all__ = ["dump", "dumps", "load", "loads", "JSONDecoder", "JSONDecodeError", " _T = TypeVar("_T") -_JSON: TypeAlias = Mapping[str, _JSON] | Sequence[_JSON] | str | float | bool | None +# Mapping[str, object] is used to maintain compatibility with typed dictionaries +# despite it being very loose it's preferrable to using Any. +_JSON: TypeAlias = Mapping[str, object] | Sequence[_JSON] | str | float | bool | None @overload def dumps( From ae677f10741ec36584245b6bdb0e844097b8074a Mon Sep 17 00:00:00 2001 From: Max Muoto Date: Thu, 8 May 2025 22:12:38 -0500 Subject: [PATCH 15/17] Support subclasses... --- stdlib/@tests/test_cases/check_json.py | 12 ++++++- stdlib/json/__init__.pyi | 45 +++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/stdlib/@tests/test_cases/check_json.py b/stdlib/@tests/test_cases/check_json.py index 9ef267b9847f..3dae6735de6b 100644 --- a/stdlib/@tests/test_cases/check_json.py +++ b/stdlib/@tests/test_cases/check_json.py @@ -2,7 +2,7 @@ import json from decimal import Decimal -from typing import TypedDict +from typing import Any, TypedDict class _File: @@ -86,3 +86,13 @@ class MyTypedDict(TypedDict): json.dumps(MyTypedDict(a="hello", b="world")) + + +# We should allow anything for subclasses of json.JSONEncoder. +# Type-checking custom encoders is not practical without generics. +class MyJSONEncoder(json.JSONEncoder): + def default(self, obj: Any) -> Any: ... + + +json.dumps(Decimal(1), cls=MyJSONEncoder) +json.dump(Decimal(1), fp, cls=MyJSONEncoder) diff --git a/stdlib/json/__init__.pyi b/stdlib/json/__init__.pyi index 07a858cea1cf..45b89da8260e 100644 --- a/stdlib/json/__init__.pyi +++ b/stdlib/json/__init__.pyi @@ -22,7 +22,7 @@ def dumps( ensure_ascii: bool = True, check_circular: bool = True, allow_nan: bool = True, - cls: type[JSONEncoder] | None = None, + cls: None = None, indent: None | int | str = None, separators: tuple[str, str] | None = None, default: None = None, @@ -31,19 +31,36 @@ def dumps( ) -> str: ... @overload def dumps( - obj: _T, + obj: _JSON | _T, *, skipkeys: bool = False, ensure_ascii: bool = True, check_circular: bool = True, allow_nan: bool = True, - cls: type[JSONEncoder] | None = None, + cls: None = None, indent: None | int | str = None, separators: tuple[str, str] | None = None, default: Callable[[_T], _JSON], sort_keys: bool = False, **kwds: Any, ) -> str: ... + +# Type-checking subclasses without generics isn't practical. +@overload +def dumps( + obj: object, + *, + skipkeys: bool = False, + ensure_ascii: bool = True, + check_circular: bool = True, + allow_nan: bool = True, + cls: type[JSONEncoder], + indent: None | int | str = None, + separators: tuple[str, str] | None = None, + default: Callable[[Any], Any] | None = None, + sort_keys: bool = False, + **kwds: Any, +) -> str: ... @overload def dump( obj: _JSON, @@ -53,7 +70,7 @@ def dump( ensure_ascii: bool = True, check_circular: bool = True, allow_nan: bool = True, - cls: type[JSONEncoder] | None = None, + cls: None = None, indent: None | int | str = None, separators: tuple[str, str] | None = None, default: None = None, @@ -62,7 +79,7 @@ def dump( ) -> None: ... @overload def dump( - obj: _T, + obj: _JSON | _T, fp: SupportsWrite[str], *, skipkeys: bool = False, @@ -76,6 +93,24 @@ def dump( sort_keys: bool = False, **kwds: Any, ) -> None: ... + +# Type-checking subclasses without generics isn't practical. +@overload +def dump( + obj: object, + fp: SupportsWrite[str], + *, + skipkeys: bool = False, + ensure_ascii: bool = True, + check_circular: bool = True, + allow_nan: bool = True, + cls: type[JSONEncoder], + indent: None | int | str = None, + separators: tuple[str, str] | None = None, + default: Callable[[Any], Any] | None = None, + sort_keys: bool = False, + **kwds: Any, +) -> None: ... def loads( s: str | bytes | bytearray, *, From 5fab9e85ceb608c6caab02da97ecfb74cf9ed5e4 Mon Sep 17 00:00:00 2001 From: Max Muoto Date: Thu, 8 May 2025 22:16:53 -0500 Subject: [PATCH 16/17] Tweak --- stdlib/@tests/test_cases/check_json.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/stdlib/@tests/test_cases/check_json.py b/stdlib/@tests/test_cases/check_json.py index 3dae6735de6b..c5f959304f25 100644 --- a/stdlib/@tests/test_cases/check_json.py +++ b/stdlib/@tests/test_cases/check_json.py @@ -90,8 +90,7 @@ class MyTypedDict(TypedDict): # We should allow anything for subclasses of json.JSONEncoder. # Type-checking custom encoders is not practical without generics. -class MyJSONEncoder(json.JSONEncoder): - def default(self, obj: Any) -> Any: ... +class MyJSONEncoder(json.JSONEncoder): ... json.dumps(Decimal(1), cls=MyJSONEncoder) From ab39c3fed6349928c2f68b791c901e2e3a33835a Mon Sep 17 00:00:00 2001 From: Max Muoto Date: Thu, 8 May 2025 22:25:22 -0500 Subject: [PATCH 17/17] Remove Any --- stdlib/@tests/test_cases/check_json.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/@tests/test_cases/check_json.py b/stdlib/@tests/test_cases/check_json.py index c5f959304f25..15fc78a8ca48 100644 --- a/stdlib/@tests/test_cases/check_json.py +++ b/stdlib/@tests/test_cases/check_json.py @@ -2,7 +2,7 @@ import json from decimal import Decimal -from typing import Any, TypedDict +from typing import TypedDict class _File: