From d62f21fd15be2ab196080f0d2c334aa42663b555 Mon Sep 17 00:00:00 2001 From: fhgffy <102001626+fhgffy@users.noreply.github.com> Date: Sun, 11 Oct 2026 02:34:50 +0800 Subject: [PATCH] Honor explicit ensure_ascii options in JSON output --- AUTHORS.rst | 1 + CHANGES.rst | 5 +++++ box/converters.py | 5 +++-- test/test_converters.py | 49 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 57 insertions(+), 3 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 6b44851..dccb7e5 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -37,6 +37,7 @@ Code contributions: - Jesper Schlegel (jesperschlegel) - J vanBemmel (jbemmel) - m-janicki +- fhgffy Suggestions and bug reporting: diff --git a/CHANGES.rst b/CHANGES.rst index c4a347d..723f5d5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,11 @@ Changelog ========= +Unreleased +---------- + +* Honor explicit ``ensure_ascii`` options in ``Box`` and ``BoxList`` JSON output. + Version 7.4.1 ------------- diff --git a/box/converters.py b/box/converters.py index bb91ba6..c986ea9 100644 --- a/box/converters.py +++ b/box/converters.py @@ -156,12 +156,13 @@ def _exists(filename: str | PathLike, create: bool = False) -> Path: def _to_json( obj, filename: str | PathLike | None = None, encoding: str = "utf-8", errors: str = "strict", **json_kwargs ): + json_kwargs.setdefault("ensure_ascii", False) if filename: _exists(filename, create=True) with open(filename, "w", encoding=encoding, errors=errors) as f: - json.dump(obj, f, ensure_ascii=False, **json_kwargs) + json.dump(obj, f, **json_kwargs) else: - return json.dumps(obj, ensure_ascii=False, **json_kwargs) + return json.dumps(obj, **json_kwargs) def _from_json( diff --git a/test/test_converters.py b/test/test_converters.py index d66b75b..5d66315 100644 --- a/test/test_converters.py +++ b/test/test_converters.py @@ -10,7 +10,7 @@ import pytest from ruamel.yaml import YAML -from box import BoxError +from box import Box, BoxError, BoxList from box.converters import _from_toml, _to_json, _to_msgpack, _to_toml, _to_yaml toml_string = """[movies.Spaceballs] @@ -75,6 +75,53 @@ def test_to_json(self): assert "Rick Moranis" in open(m_file).read() assert json.load(open(m_file)) == json.loads(movie_string) + @pytest.mark.parametrize("box_type", [Box, BoxList]) + @pytest.mark.parametrize("to_file", [False, True]) + @pytest.mark.parametrize( + "ensure_ascii, expected", + [ + (None, '{"café": "你好😀"}'), + (False, '{"café": "你好😀"}'), + (True, r'{"caf\u00e9": "\u4f60\u597d\ud83d\ude00"}'), + ], + ) + def test_to_json_ensure_ascii(self, box_type, to_file, ensure_ascii, expected, tmp_path): + data = {"café": "你好😀"} + if box_type is BoxList: + data = [data] + expected = f"[{expected}]" + value = box_type(data) + kwargs = {} if ensure_ascii is None else {"ensure_ascii": ensure_ascii} + + if to_file: + filename = tmp_path / "unicode.json" + encoding = "ascii" if ensure_ascii else "utf-8" + assert value.to_json(filename=filename, encoding=encoding, **kwargs) is None + actual = filename.read_text(encoding=encoding) + else: + actual = value.to_json(**kwargs) + + assert actual == expected + assert json.loads(actual) == data + + @pytest.mark.parametrize("ensure_ascii", [None, False, True]) + def test_to_json_multiline_ensure_ascii(self, ensure_ascii, tmp_path): + value = BoxList([{"text": "你好"}, {"text": "😀"}]) + kwargs = {} if ensure_ascii is None else {"ensure_ascii": ensure_ascii} + filename = tmp_path / "unicode.jsonl" + + assert value.to_json(filename=filename, multiline=True, **kwargs) is None + + expected = '{"text": "你好"}\n{"text": "😀"}' + if ensure_ascii: + expected = r'{"text": "\u4f60\u597d"}' + "\n" + r'{"text": "\ud83d\ude00"}' + assert filename.read_text(encoding="utf-8") == expected + + def test_to_json_ensure_ascii_with_other_options(self): + value = Box(z="你好", a=1) + + assert value.to_json(ensure_ascii=True, sort_keys=True, separators=(",", ":")) == r'{"a":1,"z":"\u4f60\u597d"}' + def test_to_yaml(self): m_file = os.path.join(tmp_dir, "movie_data") movie_string = _to_yaml(movie_data)