Skip to content
Open
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
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Code contributions:
- Jesper Schlegel (jesperschlegel)
- J vanBemmel (jbemmel)
- m-janicki
- fhgffy


Suggestions and bug reporting:
Expand Down
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Changelog
=========

Unreleased
----------

* Honor explicit ``ensure_ascii`` options in ``Box`` and ``BoxList`` JSON output.

Version 7.4.1
-------------

Expand Down
5 changes: 3 additions & 2 deletions box/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
49 changes: 48 additions & 1 deletion test/test_converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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)
Expand Down