Skip to content

Commit c47ff8f

Browse files
WillAydproost
authored andcommitted
Add Indent Support in to_json (pandas-dev#28130)
1 parent 04c4abc commit c47ff8f

File tree

9 files changed

+326
-78
lines changed

9 files changed

+326
-78
lines changed

doc/source/whatsnew/v1.0.0.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Other enhancements
3636
when using the ``pyarrow`` engine. It is currently not yet supported when converting back to
3737
pandas (so it will become an integer or float dtype depending on the presence of missing data).
3838
(:issue:`28368`)
39-
-
39+
- :meth:`DataFrame.to_json` now accepts an ``indent`` integer argument to enable pretty printing of JSON output (:issue:`12004`)
4040

4141

4242
Build Changes
@@ -217,6 +217,7 @@ I/O
217217
- Improve infinity parsing. :meth:`read_csv` now interprets ``Infinity``, ``+Infinity``, ``-Infinity`` as floating point values (:issue:`10065`)
218218
- Bug in :meth:`DataFrame.to_csv` where values were truncated when the length of ``na_rep`` was shorter than the text input data. (:issue:`25099`)
219219
- Bug in :func:`DataFrame.to_string` where values were truncated using display options instead of outputting the full content (:issue:`9784`)
220+
- Bug in :meth:`DataFrame.to_json` where a datetime column label would not be written out in ISO format with ``orient="table"`` (:issue:`28130`)
220221

221222
Plotting
222223
^^^^^^^^

pandas/_libs/src/ujson/lib/ultrajson.h

+4
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,10 @@ typedef struct __JSONObjectEncoder {
244244
If true, '<', '>', and '&' characters will be encoded as \u003c, \u003e, and \u0026, respectively. If false, no special encoding will be used. */
245245
int encodeHTMLChars;
246246

247+
/*
248+
Configuration for spaces of indent */
249+
int indent;
250+
247251
/*
248252
Set to an error message if error occurred */
249253
const char *errorMsg;

pandas/_libs/src/ujson/lib/ultrajsonenc.c

+26-2
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,22 @@ FASTCALL_ATTR INLINE_PREFIX void FASTCALL_MSVC strreverse(char *begin,
728728
while (end > begin) aux = *end, *end-- = *begin, *begin++ = aux;
729729
}
730730

731+
void Buffer_AppendIndentNewlineUnchecked(JSONObjectEncoder *enc)
732+
{
733+
if (enc->indent > 0) Buffer_AppendCharUnchecked(enc, '\n');
734+
}
735+
736+
// This function could be refactored to only accept enc as an argument,
737+
// but this is a straight vendor from ujson source
738+
void Buffer_AppendIndentUnchecked(JSONObjectEncoder *enc, JSINT32 value)
739+
{
740+
int i;
741+
if (enc->indent > 0)
742+
while (value-- > 0)
743+
for (i = 0; i < enc->indent; i++)
744+
Buffer_AppendCharUnchecked(enc, ' ');
745+
}
746+
731747
void Buffer_AppendIntUnchecked(JSONObjectEncoder *enc, JSINT32 value) {
732748
char *wstr;
733749
JSUINT32 uvalue = (value < 0) ? -value : value;
@@ -960,24 +976,28 @@ void encode(JSOBJ obj, JSONObjectEncoder *enc, const char *name,
960976
enc->iterBegin(obj, &tc);
961977

962978
Buffer_AppendCharUnchecked(enc, '[');
979+
Buffer_AppendIndentNewlineUnchecked (enc);
963980

964981
while (enc->iterNext(obj, &tc)) {
965982
if (count > 0) {
966983
Buffer_AppendCharUnchecked(enc, ',');
967984
#ifndef JSON_NO_EXTRA_WHITESPACE
968985
Buffer_AppendCharUnchecked(buffer, ' ');
969986
#endif
987+
Buffer_AppendIndentNewlineUnchecked (enc);
970988
}
971989

972990
iterObj = enc->iterGetValue(obj, &tc);
973991

974992
enc->level++;
993+
Buffer_AppendIndentUnchecked (enc, enc->level);
975994
encode(iterObj, enc, NULL, 0);
976995
count++;
977996
}
978997

979998
enc->iterEnd(obj, &tc);
980-
Buffer_Reserve(enc, 2);
999+
Buffer_AppendIndentNewlineUnchecked (enc);
1000+
Buffer_AppendIndentUnchecked (enc, enc->level);
9811001
Buffer_AppendCharUnchecked(enc, ']');
9821002
break;
9831003
}
@@ -987,25 +1007,29 @@ void encode(JSOBJ obj, JSONObjectEncoder *enc, const char *name,
9871007
enc->iterBegin(obj, &tc);
9881008

9891009
Buffer_AppendCharUnchecked(enc, '{');
1010+
Buffer_AppendIndentNewlineUnchecked (enc);
9901011

9911012
while (enc->iterNext(obj, &tc)) {
9921013
if (count > 0) {
9931014
Buffer_AppendCharUnchecked(enc, ',');
9941015
#ifndef JSON_NO_EXTRA_WHITESPACE
9951016
Buffer_AppendCharUnchecked(enc, ' ');
9961017
#endif
1018+
Buffer_AppendIndentNewlineUnchecked (enc);
9971019
}
9981020

9991021
iterObj = enc->iterGetValue(obj, &tc);
10001022
objName = enc->iterGetName(obj, &tc, &szlen);
10011023

10021024
enc->level++;
1025+
Buffer_AppendIndentUnchecked (enc, enc->level);
10031026
encode(iterObj, enc, objName, szlen);
10041027
count++;
10051028
}
10061029

10071030
enc->iterEnd(obj, &tc);
1008-
Buffer_Reserve(enc, 2);
1031+
Buffer_AppendIndentNewlineUnchecked (enc);
1032+
Buffer_AppendIndentUnchecked (enc, enc->level);
10091033
Buffer_AppendCharUnchecked(enc, '}');
10101034
break;
10111035
}

pandas/_libs/src/ujson/python/objToJSON.c

+17-7
Original file line numberDiff line numberDiff line change
@@ -2373,10 +2373,16 @@ char *Object_iterGetName(JSOBJ obj, JSONTypeContext *tc, size_t *outLen) {
23732373
}
23742374

23752375
PyObject *objToJSON(PyObject *self, PyObject *args, PyObject *kwargs) {
2376-
static char *kwlist[] = {
2377-
"obj", "ensure_ascii", "double_precision", "encode_html_chars",
2378-
"orient", "date_unit", "iso_dates", "default_handler",
2379-
NULL};
2376+
static char *kwlist[] = {"obj",
2377+
"ensure_ascii",
2378+
"double_precision",
2379+
"encode_html_chars",
2380+
"orient",
2381+
"date_unit",
2382+
"iso_dates",
2383+
"default_handler",
2384+
"indent",
2385+
NULL};
23802386

23812387
char buffer[65536];
23822388
char *ret;
@@ -2389,6 +2395,7 @@ PyObject *objToJSON(PyObject *self, PyObject *args, PyObject *kwargs) {
23892395
char *sdateFormat = NULL;
23902396
PyObject *oisoDates = 0;
23912397
PyObject *odefHandler = 0;
2398+
int indent = 0;
23922399

23932400
PyObjectEncoder pyEncoder = {{
23942401
Object_beginTypeContext,
@@ -2410,6 +2417,7 @@ PyObject *objToJSON(PyObject *self, PyObject *args, PyObject *kwargs) {
24102417
idoublePrecision,
24112418
1, // forceAscii
24122419
0, // encodeHTMLChars
2420+
0, // indent
24132421
}};
24142422
JSONObjectEncoder *encoder = (JSONObjectEncoder *)&pyEncoder;
24152423

@@ -2434,10 +2442,10 @@ PyObject *objToJSON(PyObject *self, PyObject *args, PyObject *kwargs) {
24342442

24352443
PRINTMARK();
24362444

2437-
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|OiOssOO", kwlist, &oinput,
2438-
&oensureAscii, &idoublePrecision,
2445+
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|OiOssOOi", kwlist,
2446+
&oinput, &oensureAscii, &idoublePrecision,
24392447
&oencodeHTMLChars, &sOrient, &sdateFormat,
2440-
&oisoDates, &odefHandler)) {
2448+
&oisoDates, &odefHandler, &indent)) {
24412449
return NULL;
24422450
}
24432451

@@ -2503,6 +2511,8 @@ PyObject *objToJSON(PyObject *self, PyObject *args, PyObject *kwargs) {
25032511
pyEncoder.defaultHandler = odefHandler;
25042512
}
25052513

2514+
encoder->indent = indent;
2515+
25062516
pyEncoder.originalOutputFormat = pyEncoder.outputFormat;
25072517
PRINTMARK();
25082518
ret = JSON_EncodeObject(oinput, encoder, buffer, sizeof(buffer));

pandas/_typing.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
FilePathOrBuffer = Union[str, Path, IO[AnyStr]]
2323

2424
FrameOrSeries = TypeVar("FrameOrSeries", bound="NDFrame")
25-
Scalar = Union[str, int, float]
25+
Scalar = Union[str, int, float, bool]
2626
Axis = Union[str, int]
2727
Ordered = Optional[bool]
2828

pandas/core/generic.py

+31-12
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import re
99
from textwrap import dedent
1010
from typing import (
11+
Any,
1112
Callable,
1213
Dict,
1314
FrozenSet,
@@ -60,7 +61,7 @@
6061
from pandas.core.dtypes.missing import isna, notna
6162

6263
import pandas as pd
63-
from pandas._typing import Dtype, FilePathOrBuffer
64+
from pandas._typing import Dtype, FilePathOrBuffer, Scalar
6465
from pandas.core import missing, nanops
6566
import pandas.core.algorithms as algos
6667
from pandas.core.base import PandasObject, SelectionMixin
@@ -2245,17 +2246,18 @@ def to_excel(
22452246

22462247
def to_json(
22472248
self,
2248-
path_or_buf=None,
2249-
orient=None,
2250-
date_format=None,
2251-
double_precision=10,
2252-
force_ascii=True,
2253-
date_unit="ms",
2254-
default_handler=None,
2255-
lines=False,
2256-
compression="infer",
2257-
index=True,
2258-
):
2249+
path_or_buf: Optional[FilePathOrBuffer] = None,
2250+
orient: Optional[str] = None,
2251+
date_format: Optional[str] = None,
2252+
double_precision: int = 10,
2253+
force_ascii: bool_t = True,
2254+
date_unit: str = "ms",
2255+
default_handler: Optional[Callable[[Any], Union[Scalar, List, Dict]]] = None,
2256+
lines: bool_t = False,
2257+
compression: Optional[str] = "infer",
2258+
index: bool_t = True,
2259+
indent: Optional[int] = None,
2260+
) -> Optional[str]:
22592261
"""
22602262
Convert the object to a JSON string.
22612263
@@ -2335,6 +2337,11 @@ def to_json(
23352337
23362338
.. versionadded:: 0.23.0
23372339
2340+
indent : integer, optional
2341+
Length of whitespace used to indent each record.
2342+
2343+
.. versionadded:: 1.0.0
2344+
23382345
Returns
23392346
-------
23402347
None or str
@@ -2345,6 +2352,13 @@ def to_json(
23452352
--------
23462353
read_json
23472354
2355+
Notes
2356+
-----
2357+
The behavior of ``indent=0`` varies from the stdlib, which does not
2358+
indent the output but does insert newlines. Currently, ``indent=0``
2359+
and the default ``indent=None`` are equivalent in pandas, though this
2360+
may change in a future release.
2361+
23482362
Examples
23492363
--------
23502364
@@ -2395,6 +2409,10 @@ def to_json(
23952409
date_format = "iso"
23962410
elif date_format is None:
23972411
date_format = "epoch"
2412+
2413+
config.is_nonnegative_int(indent)
2414+
indent = indent or 0
2415+
23982416
return json.to_json(
23992417
path_or_buf=path_or_buf,
24002418
obj=self,
@@ -2407,6 +2425,7 @@ def to_json(
24072425
lines=lines,
24082426
compression=compression,
24092427
index=index,
2428+
indent=indent,
24102429
)
24112430

24122431
def to_hdf(self, path_or_buf, key, **kwargs):

0 commit comments

Comments
 (0)