diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 44bc82008e718..765714282c8fc 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -162,6 +162,7 @@ Other enhancements - :class:`pandas.api.typing.FrozenList` is available for typing the outputs of :attr:`MultiIndex.names`, :attr:`MultiIndex.codes` and :attr:`MultiIndex.levels` (:issue:`58237`) - :class:`pandas.api.typing.SASReader` is available for typing the output of :func:`read_sas` (:issue:`55689`) - Added :meth:`.Styler.to_typst` to write Styler objects to file, buffer or string in Typst format (:issue:`57617`) +- Added functionality to support saving json without escaping forward slashes by adding to :meth:`DataFrame.to_json` (:issue:`61442`) - Added missing :meth:`pandas.Series.info` to API reference (:issue:`60926`) - :class:`pandas.api.typing.NoDefault` is available for typing ``no_default`` - :func:`DataFrame.to_excel` now raises an ``UserWarning`` when the character count in a cell exceeds Excel's limitation of 32767 characters (:issue:`56954`) @@ -228,7 +229,6 @@ Other enhancements - Support reading Stata 102-format (Stata 1) dta files (:issue:`58978`) - Support reading Stata 110-format (Stata 7) dta files (:issue:`47176`) - Switched wheel upload to **PyPI Trusted Publishing** (OIDC) for release-tag pushes in ``wheels.yml``. (:issue:`61718`) -- .. --------------------------------------------------------------------------- .. _whatsnew_300.notable_bug_fixes: diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 43078ef3a263c..deaf1a698e0de 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -2342,6 +2342,7 @@ def to_json( indent: int | None = None, storage_options: StorageOptions | None = None, mode: Literal["a", "w"] = "w", + escape_forward_slashes: bool = True, ) -> str | None: """ Convert the object to a JSON string. @@ -2429,6 +2430,9 @@ def to_json( Accepted args are 'w' (writing) and 'a' (append) only. mode='a' is only supported when lines is True and orient is 'records'. + escape_forward_slashes : bool, optional, default True + Remove backward slashes that get added in conversion to json. + Returns ------- None or str @@ -2629,6 +2633,7 @@ def to_json( indent=indent, storage_options=storage_options, mode=mode, + escape_forward_slashes=escape_forward_slashes, ) @final diff --git a/pandas/io/json/_json.py b/pandas/io/json/_json.py index bfa61253c9c1f..62d3b60f7c0f2 100644 --- a/pandas/io/json/_json.py +++ b/pandas/io/json/_json.py @@ -114,6 +114,7 @@ def to_json( indent: int = ..., storage_options: StorageOptions = ..., mode: Literal["a", "w"] = ..., + escape_forward_slashes: Literal[True, False] = ..., ) -> None: ... @@ -133,6 +134,7 @@ def to_json( indent: int = ..., storage_options: StorageOptions = ..., mode: Literal["a", "w"] = ..., + escape_forward_slashes: Literal[True, False] = ..., ) -> str: ... @@ -151,6 +153,7 @@ def to_json( indent: int = 0, storage_options: StorageOptions | None = None, mode: Literal["a", "w"] = "w", + escape_forward_slashes: Literal[True, False] = True, ) -> str | None: if orient in ["records", "values"] and index is True: raise ValueError( @@ -218,6 +221,23 @@ def to_json( ) as handles: handles.handle.write(s) else: + if not escape_forward_slashes: + pattern = r"\/" + i = 1 + new_s = "" + skip = False + for letter in s: + if skip is False: + if letter + s[i] == pattern: + new_s = new_s + r"/" + skip = True + else: + new_s = new_s + letter + else: + skip = False + if i < len(s) - 1: + i = i + 1 + return new_s return s return None diff --git a/pandas/tests/io/json/test_pandas.py b/pandas/tests/io/json/test_pandas.py index d67e725233127..a655b771c9dee 100644 --- a/pandas/tests/io/json/test_pandas.py +++ b/pandas/tests/io/json/test_pandas.py @@ -2286,6 +2286,25 @@ def test_to_json_ea_null(): assert result == expected +def test_to_json_escape_forward_slashes(): + df = DataFrame( + { + "path1": Series(["/escape/three/slashes"], dtype="str"), + "path2": Series(["/"], dtype="str"), + "path3": Series(["ending/"], dtype="str"), + "path4": Series(["/beginning"], dtype="str"), + "path5": Series(["/multiples//multiple"], dtype="str"), + "path6": Series(["//"], dtype="str"), + } + ) + result = df.to_json(orient="records", escape_forward_slashes=False) + expected1 = '[{"path1":"/escape/three/slashes","path2":"/","path3":"ending/"' + expected2 = ',"path4":"/beginning","path5":"/multiples//multiple","path6":"//"}]' + expected = expected1 + expected2 + + assert result == expected + + def test_read_json_lines_rangeindex(): # GH 57429 data = """