diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index 82090c93a965e..252bea3ba774a 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -956,6 +956,7 @@ I/O - Bug in :func:`read_sas` that scrambled column names (:issue:`31243`) - Bug in :func:`read_sas` with RLE-compressed SAS7BDAT files that contain 0x00 control bytes (:issue:`47099`) - Bug in :func:`read_parquet` with ``use_nullable_dtypes=True`` where ``float64`` dtype was returned instead of nullable ``Float64`` dtype (:issue:`45694`) +- Bug in :meth:`DataFrame.to_json` where ``PeriodDtype`` would not make the serialization roundtrip when read back with :meth:`read_json` (:issue:`44720`) Period ^^^^^^ diff --git a/pandas/io/json/_table_schema.py b/pandas/io/json/_table_schema.py index 44c5ce0e5ee83..b7a8b5cc82f7a 100644 --- a/pandas/io/json/_table_schema.py +++ b/pandas/io/json/_table_schema.py @@ -197,6 +197,9 @@ def convert_json_field_to_pandas_type(field) -> str | CategoricalDtype: elif typ == "datetime": if field.get("tz"): return f"datetime64[ns, {field['tz']}]" + elif field.get("freq"): + # GH#47747 using datetime over period to minimize the change surface + return f"period[{field['freq']}]" else: return "datetime64[ns]" elif typ == "any": diff --git a/pandas/tests/io/json/test_json_table_schema.py b/pandas/tests/io/json/test_json_table_schema.py index c90ac2fb3b813..f4c8b9e764d6d 100644 --- a/pandas/tests/io/json/test_json_table_schema.py +++ b/pandas/tests/io/json/test_json_table_schema.py @@ -708,6 +708,44 @@ def test_read_json_table_orient_raises(self, index_nm, vals, recwarn): with pytest.raises(NotImplementedError, match="can not yet read "): pd.read_json(out, orient="table") + @pytest.mark.parametrize( + "index_nm", + [None, "idx", pytest.param("index", marks=pytest.mark.xfail), "level_0"], + ) + @pytest.mark.parametrize( + "vals", + [ + {"ints": [1, 2, 3, 4]}, + {"objects": ["a", "b", "c", "d"]}, + {"objects": ["1", "2", "3", "4"]}, + {"date_ranges": pd.date_range("2016-01-01", freq="d", periods=4)}, + {"categoricals": pd.Series(pd.Categorical(["a", "b", "c", "c"]))}, + { + "ordered_cats": pd.Series( + pd.Categorical(["a", "b", "c", "c"], ordered=True) + ) + }, + {"floats": [1.0, 2.0, 3.0, 4.0]}, + {"floats": [1.1, 2.2, 3.3, 4.4]}, + {"bools": [True, False, False, True]}, + { + "timezones": pd.date_range( + "2016-01-01", freq="d", periods=4, tz="US/Central" + ) # added in # GH 35973 + }, + ], + ) + def test_read_json_table_period_orient(self, index_nm, vals, recwarn): + df = DataFrame( + vals, + index=pd.Index( + (pd.Period(f"2022Q{q}") for q in range(1, 5)), name=index_nm + ), + ) + out = df.to_json(orient="table") + result = pd.read_json(out, orient="table") + tm.assert_frame_equal(df, result) + @pytest.mark.parametrize( "idx", [