From 1c182c4a290ab2bb530857166b864f38a1e83170 Mon Sep 17 00:00:00 2001 From: Luke J Date: Sat, 27 Jun 2026 13:23:27 +1200 Subject: [PATCH] fix: write and read figure JSON as UTF-8 write_json wrote figure JSON with Path.write_text(json_str) and read_json read it back with Path.read_text(), both omitting the encoding. On platforms whose default text encoding is not UTF-8 (e.g. cp1252 on Windows), writing a figure containing non-ASCII text raised UnicodeEncodeError and reading produced mojibake. write_html already passes "utf-8" explicitly; apply the same to the JSON I/O path so figures round-trip everywhere. Update the existing pathlib mock tests to assert the UTF-8 encoding. --- CHANGELOG.md | 1 + plotly/io/_json.py | 4 ++-- tests/test_io/test_to_from_json.py | 10 +++++++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 222706d86b4..25bb70a46e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Raise a clear `ValueError` when an unsupported marginal plot type is passed to Plotly Express, instead of failing later with a cryptic `'NoneType' object has no attribute 'constructor'` message [[#5625](https://github.com/plotly/plotly.py/pull/5625)], with thanks to @eugen-goebel for the contribution! +- Read and write figure JSON files as UTF-8 in `read_json`/`write_json` so figures containing non-ASCII text round-trip correctly on platforms whose default encoding is not UTF-8 (e.g. cp1252 on Windows), matching the existing behavior of `write_html` [[#5633](https://github.com/plotly/plotly.py/pull/5633)] ## [6.8.0] - 2026-06-03 diff --git a/plotly/io/_json.py b/plotly/io/_json.py index 0e741711f4f..63e866b57b5 100644 --- a/plotly/io/_json.py +++ b/plotly/io/_json.py @@ -295,7 +295,7 @@ def write_json(fig, file, validate=True, pretty=False, remove_uids=True, engine= else: # We previously succeeded in interpreting `file` as a pathlib object. # Now we can use `write_bytes()`. - path.write_text(json_str) + path.write_text(json_str, "utf-8") def from_json_plotly(value, engine=None): @@ -464,7 +464,7 @@ def read_json(file, output_type="Figure", skip_invalid=False, engine=None): # Read file contents into JSON string # ----------------------------------- if path is not None: - json_str = path.read_text() + json_str = path.read_text("utf-8") else: json_str = file.read() diff --git a/tests/test_io/test_to_from_json.py b/tests/test_io/test_to_from_json.py index 21b9473b397..04f2444fb61 100644 --- a/tests/test_io/test_to_from_json.py +++ b/tests/test_io/test_to_from_json.py @@ -179,6 +179,11 @@ def test_read_json_from_pathlib(fig1, fig_type_spec, fig_type): # read_json on mock file fig1_loaded = pio.read_json(filemock, output_type=fig_type_spec) + # The file must be read as UTF-8 (JSON is UTF-8 per RFC 8259); otherwise a + # figure with non-ASCII text is mangled on platforms whose default codec is + # not UTF-8 (e.g. cp1252 on Windows). + filemock.read_text.assert_called_once_with("utf-8") + # Check return type assert isinstance(fig1_loaded, fig_type) @@ -240,7 +245,10 @@ def test_write_json_pathlib(fig1, pretty, remove_uids): # check write contents expected = pio.to_json(fig1, pretty=pretty, remove_uids=remove_uids) - filemock.write_text.assert_called_once_with(expected) + # The figure must be written as UTF-8 so non-ASCII text does not raise + # UnicodeEncodeError on platforms whose default codec is not UTF-8 + # (e.g. cp1252 on Windows). + filemock.write_text.assert_called_once_with(expected, "utf-8") @pytest.mark.parametrize("pretty", [True, False])