Skip to content

Commit b6cbd44

Browse files
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.
1 parent 5cdb606 commit b6cbd44

3 files changed

Lines changed: 12 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
66

77
### Fixed
88
- 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!
9+
- 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` [[#XXXX](https://github.com/plotly/plotly.py/pull/XXXX)]
910

1011

1112
## [6.8.0] - 2026-06-03

plotly/io/_json.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ def write_json(fig, file, validate=True, pretty=False, remove_uids=True, engine=
295295
else:
296296
# We previously succeeded in interpreting `file` as a pathlib object.
297297
# Now we can use `write_bytes()`.
298-
path.write_text(json_str)
298+
path.write_text(json_str, "utf-8")
299299

300300

301301
def from_json_plotly(value, engine=None):
@@ -464,7 +464,7 @@ def read_json(file, output_type="Figure", skip_invalid=False, engine=None):
464464
# Read file contents into JSON string
465465
# -----------------------------------
466466
if path is not None:
467-
json_str = path.read_text()
467+
json_str = path.read_text("utf-8")
468468
else:
469469
json_str = file.read()
470470

tests/test_io/test_to_from_json.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,11 @@ def test_read_json_from_pathlib(fig1, fig_type_spec, fig_type):
179179
# read_json on mock file
180180
fig1_loaded = pio.read_json(filemock, output_type=fig_type_spec)
181181

182+
# The file must be read as UTF-8 (JSON is UTF-8 per RFC 8259); otherwise a
183+
# figure with non-ASCII text is mangled on platforms whose default codec is
184+
# not UTF-8 (e.g. cp1252 on Windows).
185+
filemock.read_text.assert_called_once_with("utf-8")
186+
182187
# Check return type
183188
assert isinstance(fig1_loaded, fig_type)
184189

@@ -240,7 +245,10 @@ def test_write_json_pathlib(fig1, pretty, remove_uids):
240245

241246
# check write contents
242247
expected = pio.to_json(fig1, pretty=pretty, remove_uids=remove_uids)
243-
filemock.write_text.assert_called_once_with(expected)
248+
# The figure must be written as UTF-8 so non-ASCII text does not raise
249+
# UnicodeEncodeError on platforms whose default codec is not UTF-8
250+
# (e.g. cp1252 on Windows).
251+
filemock.write_text.assert_called_once_with(expected, "utf-8")
244252

245253

246254
@pytest.mark.parametrize("pretty", [True, False])

0 commit comments

Comments
 (0)