Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pandas/io/json/_json.py: 23%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

532 statements  

1from __future__ import annotations 

2 

3from abc import ( 

4 ABC, 

5 abstractmethod, 

6) 

7from collections import abc 

8from itertools import islice 

9from typing import ( 

10 TYPE_CHECKING, 

11 Any, 

12 Generic, 

13 Literal, 

14 Self, 

15 TypeVar, 

16 final, 

17 overload, 

18) 

19import warnings 

20 

21import numpy as np 

22 

23from pandas._config import option_context 

24 

25from pandas._libs import lib 

26from pandas._libs.json import ( 

27 ujson_dumps, 

28 ujson_loads, 

29) 

30from pandas._libs.tslibs import iNaT 

31from pandas.compat._optional import import_optional_dependency 

32from pandas.errors import ( 

33 AbstractMethodError, 

34 OutOfBoundsDatetime, 

35) 

36from pandas.util._decorators import set_module 

37from pandas.util._validators import check_dtype_backend 

38 

39from pandas.core.dtypes.common import ( 

40 ensure_str, 

41 is_string_dtype, 

42 pandas_dtype, 

43) 

44from pandas.core.dtypes.dtypes import PeriodDtype 

45 

46from pandas import ( 

47 ArrowDtype, 

48 DataFrame, 

49 Index, 

50 MultiIndex, 

51 Series, 

52 isna, 

53 notna, 

54 to_datetime, 

55) 

56from pandas.core.reshape.concat import concat 

57 

58from pandas.io._util import arrow_table_to_pandas 

59from pandas.io.common import ( 

60 IOHandles, 

61 dedup_names, 

62 get_handle, 

63 is_potential_multi_index, 

64 stringify_path, 

65) 

66from pandas.io.json._normalize import convert_to_line_delimits 

67from pandas.io.json._table_schema import ( 

68 build_table_schema, 

69 parse_table_schema, 

70 set_default_names, 

71) 

72from pandas.io.parsers.readers import validate_integer 

73 

74if TYPE_CHECKING: 

75 from collections.abc import ( 

76 Callable, 

77 Hashable, 

78 Mapping, 

79 ) 

80 from types import TracebackType 

81 

82 from pandas._typing import ( 

83 CompressionOptions, 

84 DtypeArg, 

85 DtypeBackend, 

86 FilePath, 

87 IndexLabel, 

88 JSONEngine, 

89 JSONSerializable, 

90 ReadBuffer, 

91 StorageOptions, 

92 WriteBuffer, 

93 ) 

94 

95 from pandas.core.generic import NDFrame 

96 

97FrameSeriesStrT = TypeVar("FrameSeriesStrT", bound=Literal["frame", "series"]) 

98 

99 

100# interface to/from 

101@overload 

102def to_json( 

103 path_or_buf: FilePath | WriteBuffer[str] | WriteBuffer[bytes], 

104 obj: NDFrame, 

105 orient: str | None = ..., 

106 date_format: str = ..., 

107 double_precision: int = ..., 

108 force_ascii: bool = ..., 

109 date_unit: str = ..., 

110 default_handler: Callable[[Any], JSONSerializable] | None = ..., 

111 lines: bool = ..., 

112 compression: CompressionOptions = ..., 

113 index: bool | None = ..., 

114 indent: int = ..., 

115 storage_options: StorageOptions = ..., 

116 mode: Literal["a", "w"] = ..., 

117) -> None: ... 

118 

119 

120@overload 

121def to_json( 

122 path_or_buf: None, 

123 obj: NDFrame, 

124 orient: str | None = ..., 

125 date_format: str = ..., 

126 double_precision: int = ..., 

127 force_ascii: bool = ..., 

128 date_unit: str = ..., 

129 default_handler: Callable[[Any], JSONSerializable] | None = ..., 

130 lines: bool = ..., 

131 compression: CompressionOptions = ..., 

132 index: bool | None = ..., 

133 indent: int = ..., 

134 storage_options: StorageOptions = ..., 

135 mode: Literal["a", "w"] = ..., 

136) -> str: ... 

137 

138 

139def to_json( 

140 path_or_buf: FilePath | WriteBuffer[str] | WriteBuffer[bytes] | None, 

141 obj: NDFrame, 

142 orient: str | None = None, 

143 date_format: str = "epoch", 

144 double_precision: int = 10, 

145 force_ascii: bool = True, 

146 date_unit: str = "ms", 

147 default_handler: Callable[[Any], JSONSerializable] | None = None, 

148 lines: bool = False, 

149 compression: CompressionOptions = "infer", 

150 index: bool | None = None, 

151 indent: int = 0, 

152 storage_options: StorageOptions | None = None, 

153 mode: Literal["a", "w"] = "w", 

154) -> str | None: 

155 if orient in ["records", "values"] and index is True: 

156 raise ValueError( 

157 "'index=True' is only valid when 'orient' is 'split', 'table', " 

158 "'index', or 'columns'." 

159 ) 

160 elif orient in ["index", "columns"] and index is False: 

161 raise ValueError( 

162 "'index=False' is only valid when 'orient' is 'split', 'table', " 

163 "'records', or 'values'." 

164 ) 

165 elif index is None: 

166 # will be ignored for orient='records' and 'values' 

167 index = True 

168 

169 if lines and orient != "records": 

170 raise ValueError("'lines' keyword only valid when 'orient' is records") 

171 

172 if mode not in ["a", "w"]: 

173 msg = ( 

174 f"mode={mode} is not a valid option." 

175 "Only 'w' and 'a' are currently supported." 

176 ) 

177 raise ValueError(msg) 

178 

179 if mode == "a" and (not lines or orient != "records"): 

180 msg = ( 

181 "mode='a' (append) is only supported when " 

182 "lines is True and orient is 'records'" 

183 ) 

184 raise ValueError(msg) 

185 

186 if orient == "table" and isinstance(obj, Series): 

187 obj = obj.to_frame(name=obj.name or "values") 

188 

189 if date_format == "epoch": 

190 # for epoch (numeric) format, convert datetime-likes to the desired 

191 # unit up front, such that the C ObjToJSON code can simply write out 

192 # the integer values without worrying about conversion 

193 if date_unit not in ["s", "ms", "us", "ns"]: 

194 raise ValueError(f"Invalid value '{date_unit}' for option 'date_unit'") 

195 if isinstance(obj, DataFrame): 

196 copied = False 

197 cols = np.nonzero(obj.dtypes.map(lambda dt: dt.kind in ["M", "m"]))[0] 

198 if len(cols): 

199 obj = obj.copy(deep=False) 

200 copied = True 

201 for col in cols: 

202 obj.isetitem(col, obj.iloc[:, col].dt.as_unit(date_unit)) 

203 if obj.index.dtype.kind in "Mm": 

204 if not copied: 

205 obj = obj.copy(deep=False) 

206 copied = True 

207 obj.index = Series(obj.index).dt.as_unit(date_unit) 

208 if obj.columns.dtype.kind in "Mm": 

209 if not copied: 

210 obj = obj.copy(deep=False) 

211 copied = True 

212 obj.columns = Series(obj.columns).dt.as_unit(date_unit) 

213 elif isinstance(obj, Series): 

214 if obj.dtype.kind in "Mm": 

215 obj = obj.copy(deep=False) 

216 obj = obj.dt.as_unit(date_unit) 

217 if obj.index.dtype.kind in "Mm": 

218 obj = obj.copy(deep=False) 

219 obj.index = Series(obj.index).dt.as_unit(date_unit) 

220 

221 writer: type[Writer] 

222 if orient == "table" and isinstance(obj, DataFrame): 

223 writer = JSONTableWriter 

224 elif isinstance(obj, Series): 

225 writer = SeriesWriter 

226 elif isinstance(obj, DataFrame): 

227 writer = FrameWriter 

228 else: 

229 raise NotImplementedError("'obj' should be a Series or a DataFrame") 

230 

231 s = writer( 

232 obj, 

233 orient=orient, 

234 date_format=date_format, 

235 double_precision=double_precision, 

236 ensure_ascii=force_ascii, 

237 date_unit=date_unit, 

238 default_handler=default_handler, 

239 index=index, 

240 indent=indent, 

241 ).write() 

242 

243 if lines: 

244 s = convert_to_line_delimits(s) 

245 

246 if path_or_buf is not None: 

247 # apply compression and byte/text conversion 

248 with get_handle( 

249 path_or_buf, mode, compression=compression, storage_options=storage_options 

250 ) as handles: 

251 handles.handle.write(s) 

252 else: 

253 return s 

254 return None 

255 

256 

257class Writer(ABC): 

258 _default_orient: str 

259 

260 def __init__( 

261 self, 

262 obj: NDFrame, 

263 orient: str | None, 

264 date_format: str, 

265 double_precision: int, 

266 ensure_ascii: bool, 

267 date_unit: str, 

268 index: bool, 

269 default_handler: Callable[[Any], JSONSerializable] | None = None, 

270 indent: int = 0, 

271 ) -> None: 

272 self.obj = obj 

273 

274 if orient is None: 

275 orient = self._default_orient 

276 

277 self.orient = orient 

278 self.date_format = date_format 

279 self.double_precision = double_precision 

280 self.ensure_ascii = ensure_ascii 

281 self.date_unit = date_unit 

282 self.default_handler = default_handler 

283 self.index = index 

284 self.indent = indent 

285 self._format_axes() 

286 

287 def _format_axes(self) -> None: 

288 raise AbstractMethodError(self) 

289 

290 def write(self) -> str: 

291 iso_dates = self.date_format == "iso" 

292 return ujson_dumps( 

293 self.obj_to_write, 

294 orient=self.orient, 

295 double_precision=self.double_precision, 

296 ensure_ascii=self.ensure_ascii, 

297 date_unit=self.date_unit, 

298 iso_dates=iso_dates, 

299 default_handler=self.default_handler, 

300 indent=self.indent, 

301 ) 

302 

303 @property 

304 @abstractmethod 

305 def obj_to_write(self) -> NDFrame | Mapping[IndexLabel, Any]: 

306 """Object to write in JSON format.""" 

307 

308 

309class SeriesWriter(Writer): 

310 _default_orient = "index" 

311 

312 @property 

313 def obj_to_write(self) -> NDFrame | Mapping[IndexLabel, Any]: 

314 if not self.index and self.orient == "split": 

315 return {"name": self.obj.name, "data": self.obj.values} 

316 else: 

317 return self.obj 

318 

319 def _format_axes(self) -> None: 

320 if not self.obj.index.is_unique and self.orient == "index": 

321 raise ValueError(f"Series index must be unique for orient='{self.orient}'") 

322 

323 

324class FrameWriter(Writer): 

325 _default_orient = "columns" 

326 

327 @property 

328 def obj_to_write(self) -> NDFrame | Mapping[IndexLabel, Any]: 

329 if not self.index and self.orient == "split": 

330 obj_to_write = self.obj.to_dict(orient="split") 

331 del obj_to_write["index"] 

332 else: 

333 obj_to_write = self.obj 

334 return obj_to_write 

335 

336 def _format_axes(self) -> None: 

337 """ 

338 Try to format axes if they are datelike. 

339 """ 

340 if not self.obj.index.is_unique and self.orient in ("index", "columns"): 

341 raise ValueError( 

342 f"DataFrame index must be unique for orient='{self.orient}'." 

343 ) 

344 if not self.obj.columns.is_unique and self.orient in ( 

345 "index", 

346 "columns", 

347 "records", 

348 ): 

349 raise ValueError( 

350 f"DataFrame columns must be unique for orient='{self.orient}'." 

351 ) 

352 

353 

354class JSONTableWriter(FrameWriter): 

355 _default_orient = "records" 

356 

357 def __init__( 

358 self, 

359 obj, 

360 orient: str | None, 

361 date_format: str, 

362 double_precision: int, 

363 ensure_ascii: bool, 

364 date_unit: str, 

365 index: bool, 

366 default_handler: Callable[[Any], JSONSerializable] | None = None, 

367 indent: int = 0, 

368 ) -> None: 

369 """ 

370 Adds a `schema` attribute with the Table Schema, resets 

371 the index (can't do in caller, because the schema inference needs 

372 to know what the index is, forces orient to records, and forces 

373 date_format to 'iso'. 

374 """ 

375 super().__init__( 

376 obj, 

377 orient, 

378 date_format, 

379 double_precision, 

380 ensure_ascii, 

381 date_unit, 

382 index, 

383 default_handler=default_handler, 

384 indent=indent, 

385 ) 

386 

387 if date_format != "iso": 

388 msg = ( 

389 "Trying to write with `orient='table'` and " 

390 f"`date_format='{date_format}'`. Table Schema requires dates " 

391 "to be formatted with `date_format='iso'`" 

392 ) 

393 raise ValueError(msg) 

394 

395 self.schema = build_table_schema(obj, index=self.index) 

396 if self.index: 

397 obj = set_default_names(obj) 

398 

399 # NotImplemented on a column MultiIndex 

400 if obj.ndim == 2 and isinstance(obj.columns, MultiIndex): 

401 raise NotImplementedError( 

402 "orient='table' is not supported for MultiIndex columns" 

403 ) 

404 

405 # TODO: Do this timedelta properly in objToJSON.c See GH #15137 

406 if ((obj.ndim == 1) and (obj.name in set(obj.index.names))) or len( 

407 obj.columns.intersection(obj.index.names) 

408 ): 

409 msg = "Overlapping names between the index and columns" 

410 raise ValueError(msg) 

411 

412 timedeltas = obj.select_dtypes(include=["timedelta"]).columns 

413 copied = False 

414 if len(timedeltas): 

415 obj = obj.copy() 

416 copied = True 

417 obj[timedeltas] = obj[timedeltas].map(lambda x: x.isoformat()) 

418 

419 # exclude index from obj if index=False 

420 if not self.index: 

421 self.obj = obj.reset_index(drop=True) 

422 else: 

423 # Convert PeriodIndex to datetimes before serializing 

424 if isinstance(obj.index.dtype, PeriodDtype): 

425 if not copied: 

426 obj = obj.copy(deep=False) 

427 obj.index = obj.index.to_timestamp() 

428 self.obj = obj.reset_index(drop=False) 

429 self.date_format = "iso" 

430 self.orient = "records" 

431 self.index = index 

432 

433 @property 

434 def obj_to_write(self) -> NDFrame | Mapping[IndexLabel, Any]: 

435 return {"schema": self.schema, "data": self.obj} 

436 

437 

438@overload 

439def read_json( 

440 path_or_buf: FilePath | ReadBuffer[str] | ReadBuffer[bytes], 

441 *, 

442 orient: str | None = ..., 

443 typ: Literal["frame"] = ..., 

444 dtype: DtypeArg | None = ..., 

445 convert_axes: bool | None = ..., 

446 convert_dates: bool | list[str] = ..., 

447 keep_default_dates: bool = ..., 

448 precise_float: bool = ..., 

449 date_unit: str | None = ..., 

450 encoding: str | None = ..., 

451 encoding_errors: str | None = ..., 

452 lines: bool = ..., 

453 chunksize: int, 

454 compression: CompressionOptions = ..., 

455 nrows: int | None = ..., 

456 storage_options: StorageOptions = ..., 

457 dtype_backend: DtypeBackend | lib.NoDefault = ..., 

458 engine: JSONEngine = ..., 

459) -> JsonReader[Literal["frame"]]: ... 

460 

461 

462@overload 

463def read_json( 

464 path_or_buf: FilePath | ReadBuffer[str] | ReadBuffer[bytes], 

465 *, 

466 orient: str | None = ..., 

467 typ: Literal["series"], 

468 dtype: DtypeArg | None = ..., 

469 convert_axes: bool | None = ..., 

470 convert_dates: bool | list[str] = ..., 

471 keep_default_dates: bool = ..., 

472 precise_float: bool = ..., 

473 date_unit: str | None = ..., 

474 encoding: str | None = ..., 

475 encoding_errors: str | None = ..., 

476 lines: bool = ..., 

477 chunksize: int, 

478 compression: CompressionOptions = ..., 

479 nrows: int | None = ..., 

480 storage_options: StorageOptions = ..., 

481 dtype_backend: DtypeBackend | lib.NoDefault = ..., 

482 engine: JSONEngine = ..., 

483) -> JsonReader[Literal["series"]]: ... 

484 

485 

486@overload 

487def read_json( 

488 path_or_buf: FilePath | ReadBuffer[str] | ReadBuffer[bytes], 

489 *, 

490 orient: str | None = ..., 

491 typ: Literal["series"], 

492 dtype: DtypeArg | None = ..., 

493 convert_axes: bool | None = ..., 

494 convert_dates: bool | list[str] = ..., 

495 keep_default_dates: bool = ..., 

496 precise_float: bool = ..., 

497 date_unit: str | None = ..., 

498 encoding: str | None = ..., 

499 encoding_errors: str | None = ..., 

500 lines: bool = ..., 

501 chunksize: None = ..., 

502 compression: CompressionOptions = ..., 

503 nrows: int | None = ..., 

504 storage_options: StorageOptions = ..., 

505 dtype_backend: DtypeBackend | lib.NoDefault = ..., 

506 engine: JSONEngine = ..., 

507) -> Series: ... 

508 

509 

510@overload 

511def read_json( 

512 path_or_buf: FilePath | ReadBuffer[str] | ReadBuffer[bytes], 

513 *, 

514 orient: str | None = ..., 

515 typ: Literal["frame"] = ..., 

516 dtype: DtypeArg | None = ..., 

517 convert_axes: bool | None = ..., 

518 convert_dates: bool | list[str] = ..., 

519 keep_default_dates: bool = ..., 

520 precise_float: bool = ..., 

521 date_unit: str | None = ..., 

522 encoding: str | None = ..., 

523 encoding_errors: str | None = ..., 

524 lines: bool = ..., 

525 chunksize: None = ..., 

526 compression: CompressionOptions = ..., 

527 nrows: int | None = ..., 

528 storage_options: StorageOptions = ..., 

529 dtype_backend: DtypeBackend | lib.NoDefault = ..., 

530 engine: JSONEngine = ..., 

531) -> DataFrame: ... 

532 

533 

534@set_module("pandas") 

535def read_json( 

536 path_or_buf: FilePath | ReadBuffer[str] | ReadBuffer[bytes], 

537 *, 

538 orient: str | None = None, 

539 typ: Literal["frame", "series"] = "frame", 

540 dtype: DtypeArg | None = None, 

541 convert_axes: bool | None = None, 

542 convert_dates: bool | list[str] = True, 

543 keep_default_dates: bool = True, 

544 precise_float: bool = False, 

545 date_unit: str | None = None, 

546 encoding: str | None = None, 

547 encoding_errors: str | None = "strict", 

548 lines: bool = False, 

549 chunksize: int | None = None, 

550 compression: CompressionOptions = "infer", 

551 nrows: int | None = None, 

552 storage_options: StorageOptions | None = None, 

553 dtype_backend: DtypeBackend | lib.NoDefault = lib.no_default, 

554 engine: JSONEngine = "ujson", 

555) -> DataFrame | Series | JsonReader: 

556 """ 

557 Convert a JSON string to pandas object. 

558 

559 This method reads JSON files or JSON-like data and converts them into pandas 

560 objects. It supports a variety of input formats, including line-delimited JSON, 

561 compressed files, and various data representations (table, records, index-based, 

562 etc.). When `chunksize` is specified, an iterator is returned instead of loading 

563 the entire data into memory. 

564 

565 Parameters 

566 ---------- 

567 path_or_buf : a str path, path object or file-like object 

568 Any valid string path is acceptable. The string could be a URL. Valid 

569 URL schemes include http, ftp, s3, and file. For file URLs, a host is 

570 expected. A local file could be: 

571 ``file://localhost/path/to/table.json``. 

572 

573 If you want to pass in a path object, pandas accepts any 

574 ``os.PathLike``. 

575 

576 By file-like object, we refer to objects with a ``read()`` method, 

577 such as a file handle (e.g. via builtin ``open`` function) 

578 or ``StringIO``. 

579 

580 orient : str, optional 

581 Indication of expected JSON string format. 

582 Compatible JSON strings can be produced by ``to_json()`` with a 

583 corresponding orient value. 

584 The set of possible orients is: 

585 

586 - ``'split'`` : dict like 

587 ``{index -> [index], columns -> [columns], data -> [values]}`` 

588 - ``'records'`` : list like 

589 ``[{column -> value}, ... , {column -> value}]`` 

590 - ``'index'`` : dict like ``{index -> {column -> value}}`` 

591 - ``'columns'`` : dict like ``{column -> {index -> value}}`` 

592 - ``'values'`` : just the values array 

593 - ``'table'`` : dict like ``{'schema': {schema}, 'data': {data}}`` 

594 

595 The allowed and default values depend on the value 

596 of the `typ` parameter. 

597 

598 * when ``typ == 'series'``, 

599 

600 - allowed orients are ``{'split','records','index'}`` 

601 - default is ``'index'`` 

602 - The Series index must be unique for orient ``'index'``. 

603 

604 * when ``typ == 'frame'``, 

605 

606 - allowed orients are ``{'split','records','index', 

607 'columns','values', 'table'}`` 

608 - default is ``'columns'`` 

609 - The DataFrame index must be unique for orients ``'index'`` and 

610 ``'columns'``. 

611 - The DataFrame columns must be unique for orients ``'index'``, 

612 ``'columns'``, and ``'records'``. 

613 

614 typ : {'frame', 'series'}, default 'frame' 

615 The type of object to recover. 

616 

617 dtype : bool or dict, default None 

618 If True, infer dtypes; if a dict of column to dtype, then use those; 

619 if False, then don't infer dtypes at all, applies only to the data. 

620 

621 For all ``orient`` values except ``'table'``, default is True. 

622 

623 convert_axes : bool, default None 

624 Try to convert the axes to the proper dtypes. 

625 

626 For all ``orient`` values except ``'table'``, default is True. 

627 

628 convert_dates : bool or list of str, default True 

629 If True then default datelike columns may be converted (depending on 

630 keep_default_dates). 

631 If False, no dates will be converted. 

632 If a list of column names, then those columns will be converted and 

633 default datelike columns may also be converted (depending on 

634 keep_default_dates). 

635 

636 keep_default_dates : bool, default True 

637 If parsing dates (convert_dates is not False), then try to parse the 

638 default datelike columns. 

639 A column label is datelike if 

640 

641 * it ends with ``'_at'``, 

642 

643 * it ends with ``'_time'``, 

644 

645 * it begins with ``'timestamp'``, 

646 

647 * it is ``'modified'``, or 

648 

649 * it is ``'date'``. 

650 

651 precise_float : bool, default False 

652 Set to enable usage of higher precision (strtod) function when 

653 decoding string to double values. Default (False) is to use fast but 

654 less precise builtin functionality. 

655 

656 date_unit : str, default None 

657 The timestamp unit to detect if converting dates. The default behaviour 

658 is to try and detect the correct precision, but if this is not desired 

659 then pass one of 's', 'ms', 'us' or 'ns' to force parsing only seconds, 

660 milliseconds, microseconds or nanoseconds respectively. 

661 

662 encoding : str, default is 'utf-8' 

663 The encoding to use to decode py3 bytes. 

664 

665 encoding_errors : str, optional, default "strict" 

666 How encoding errors are treated. `List of possible values 

667 <https://docs.python.org/3/library/codecs.html#error-handlers>`_ . 

668 

669 lines : bool, default False 

670 Read the file as a json object per line. 

671 

672 chunksize : int, optional 

673 Return JsonReader object for iteration. 

674 See the `line-delimited json docs 

675 <https://pandas.pydata.org/pandas-docs/stable/user_guide/io.html#line-delimited-json>`_ 

676 for more information on ``chunksize``. 

677 This can only be passed if `lines=True`. 

678 If this is None, the file will be read into memory all at once. 

679 

680 compression : str or dict, default 'infer' 

681 For on-the-fly decompression of on-disk data. If 'infer' and 'path_or_buf' is 

682 path-like, then detect compression from the following extensions: '.gz', 

683 '.bz2', '.zip', '.xz', '.zst', '.tar', '.tar.gz', '.tar.xz' or '.tar.bz2' 

684 (otherwise no compression). 

685 If using 'zip' or 'tar', the ZIP file must contain only one data file to be 

686 read in. 

687 Set to ``None`` for no decompression. 

688 Can also be a dict with key ``'method'`` set 

689 to one of {``'zip'``, ``'gzip'``, ``'bz2'``, ``'zstd'``, ``'xz'``, ``'tar'``} 

690 and other key-value pairs are forwarded to 

691 ``zipfile.ZipFile``, ``gzip.GzipFile``, 

692 ``bz2.BZ2File``, ``zstandard.ZstdDecompressor``, ``lzma.LZMAFile`` or 

693 ``tarfile.TarFile``, respectively. 

694 As an example, the following could be passed for Zstandard decompression using a 

695 custom compression dictionary: 

696 ``compression={'method': 'zstd', 'dict_data': my_compression_dict}``. 

697 

698 nrows : int, optional 

699 The number of lines from the line-delimited jsonfile that has to be read. 

700 This can only be passed if `lines=True`. 

701 If this is None, all the rows will be returned. 

702 

703 storage_options : dict, optional 

704 Extra options that make sense for a particular storage connection, e.g. 

705 host, port, username, password, etc. For HTTP(S) URLs the key-value pairs 

706 are forwarded to ``urllib.request.Request`` as header options. For other 

707 URLs (e.g. starting with "s3://", and "gcs://") the key-value pairs are 

708 forwarded to ``fsspec.open``. Please see ``fsspec`` and ``urllib`` for more 

709 details, and for more examples on storage options refer `here 

710 <https://pandas.pydata.org/docs/user_guide/io.html? 

711 highlight=storage_options#reading-writing-remote-files>`_. 

712 

713 dtype_backend : {'numpy_nullable', 'pyarrow'} 

714 Back-end data type applied to the resultant :class:`DataFrame` 

715 (still experimental). If not specified, the default behavior 

716 is to not use nullable data types. If specified, the behavior 

717 is as follows: 

718 

719 * ``"numpy_nullable"``: returns nullable-dtype-backed :class:`DataFrame` 

720 * ``"pyarrow"``: returns pyarrow-backed nullable 

721 :class:`ArrowDtype` :class:`DataFrame` 

722 

723 .. versionadded:: 2.0 

724 

725 engine : {"ujson", "pyarrow"}, default "ujson" 

726 Parser engine to use. The ``"pyarrow"`` engine is only available when 

727 ``lines=True``. 

728 

729 .. versionadded:: 2.0 

730 

731 Returns 

732 ------- 

733 Series, DataFrame, or pandas.api.typing.JsonReader 

734 A JsonReader is returned when ``chunksize`` is not ``0`` or ``None``. 

735 Otherwise, the type returned depends on the value of ``typ``. 

736 

737 See Also 

738 -------- 

739 DataFrame.to_json : Convert a DataFrame to a JSON string. 

740 Series.to_json : Convert a Series to a JSON string. 

741 json_normalize : Normalize semi-structured JSON data into a flat table. 

742 

743 Notes 

744 ----- 

745 Specific to ``orient='table'``, if a :class:`DataFrame` with a literal 

746 :class:`Index` name of `index` gets written with :func:`to_json`, the 

747 subsequent read operation will incorrectly set the :class:`Index` name to 

748 ``None``. This is because `index` is also used by :func:`DataFrame.to_json` 

749 to denote a missing :class:`Index` name, and the subsequent 

750 :func:`read_json` operation cannot distinguish between the two. The same 

751 limitation is encountered with a :class:`MultiIndex` and any names 

752 beginning with ``'level_'``. 

753 

754 Examples 

755 -------- 

756 >>> from io import StringIO 

757 >>> df = pd.DataFrame( 

758 ... [["a", "b"], ["c", "d"]], 

759 ... index=["row 1", "row 2"], 

760 ... columns=["col 1", "col 2"], 

761 ... ) 

762 

763 Encoding/decoding a Dataframe using ``'split'`` formatted JSON: 

764 

765 >>> df.to_json(orient="split") 

766 '{"columns":["col 1","col 2"],"index":["row 1","row 2"],"data":[["a","b"],["c","d"]]}' 

767 

768 >>> pd.read_json(StringIO(_), orient="split") # noqa: F821 

769 col 1 col 2 

770 row 1 a b 

771 row 2 c d 

772 

773 Encoding/decoding a Dataframe using ``'index'`` formatted JSON: 

774 

775 >>> df.to_json(orient="index") 

776 '{"row 1":{"col 1":"a","col 2":"b"},"row 2":{"col 1":"c","col 2":"d"}}' 

777 

778 >>> pd.read_json(StringIO(_), orient="index") # noqa: F821 

779 col 1 col 2 

780 row 1 a b 

781 row 2 c d 

782 

783 Encoding/decoding a Dataframe using ``'records'`` formatted JSON. 

784 Note that index labels are not preserved with this encoding. 

785 

786 >>> df.to_json(orient="records") 

787 '[{"col 1":"a","col 2":"b"},{"col 1":"c","col 2":"d"}]' 

788 

789 >>> pd.read_json(StringIO(_), orient="records") # noqa: F821 

790 col 1 col 2 

791 0 a b 

792 1 c d 

793 

794 Encoding with Table Schema 

795 

796 >>> df.to_json(orient="table") 

797 '{"schema":{"fields":[{"name":"index","type":"string","extDtype":"str"},{"name":"col 1","type":"string","extDtype":"str"},{"name":"col 2","type":"string","extDtype":"str"}],"primaryKey":["index"],"pandas_version":"1.4.0"},"data":[{"index":"row 1","col 1":"a","col 2":"b"},{"index":"row 2","col 1":"c","col 2":"d"}]}' 

798 

799 The following example uses ``dtype_backend="numpy_nullable"`` 

800 

801 >>> data = '''{"index": {"0": 0, "1": 1}, 

802 ... "a": {"0": 1, "1": null}, 

803 ... "b": {"0": 2.5, "1": 4.5}, 

804 ... "c": {"0": true, "1": false}, 

805 ... "d": {"0": "a", "1": "b"}, 

806 ... "e": {"0": 1577.2, "1": 1577.1}}''' 

807 >>> pd.read_json(StringIO(data), dtype_backend="numpy_nullable") 

808 index a b c d e 

809 0 0 1 2.5 True a 1577.2 

810 1 1 <NA> 4.5 False b 1577.1 

811 """ # noqa: E501 

812 if orient == "table" and dtype: 

813 raise ValueError("cannot pass both dtype and orient='table'") 

814 if orient == "table" and convert_axes: 

815 raise ValueError("cannot pass both convert_axes and orient='table'") 

816 

817 check_dtype_backend(dtype_backend) 

818 

819 if dtype is None and orient != "table": 

820 # error: Incompatible types in assignment (expression has type "bool", variable 

821 # has type "Union[ExtensionDtype, str, dtype[Any], Type[str], Type[float], 

822 # Type[int], Type[complex], Type[bool], Type[object], Dict[Hashable, 

823 # Union[ExtensionDtype, Union[str, dtype[Any]], Type[str], Type[float], 

824 # Type[int], Type[complex], Type[bool], Type[object]]], None]") 

825 dtype = True # type: ignore[assignment] 

826 if convert_axes is None and orient != "table": 

827 convert_axes = True 

828 

829 json_reader = JsonReader( 

830 path_or_buf, 

831 orient=orient, 

832 typ=typ, 

833 dtype=dtype, 

834 convert_axes=convert_axes, 

835 convert_dates=convert_dates, 

836 keep_default_dates=keep_default_dates, 

837 precise_float=precise_float, 

838 date_unit=date_unit, 

839 encoding=encoding, 

840 lines=lines, 

841 chunksize=chunksize, 

842 compression=compression, 

843 nrows=nrows, 

844 storage_options=storage_options, 

845 encoding_errors=encoding_errors, 

846 dtype_backend=dtype_backend, 

847 engine=engine, 

848 ) 

849 

850 if chunksize: 

851 return json_reader 

852 else: 

853 return json_reader.read() 

854 

855 

856@set_module("pandas.api.typing") 

857class JsonReader(abc.Iterator, Generic[FrameSeriesStrT]): 

858 """ 

859 JsonReader provides an interface for reading in a JSON file. 

860 

861 If initialized with ``lines=True`` and ``chunksize``, can be iterated over 

862 ``chunksize`` lines at a time. Otherwise, calling ``read`` reads in the 

863 whole document. 

864 """ 

865 

866 def __init__( 

867 self, 

868 filepath_or_buffer, 

869 orient, 

870 typ: FrameSeriesStrT, 

871 dtype, 

872 convert_axes: bool | None, 

873 convert_dates, 

874 keep_default_dates: bool, 

875 precise_float: bool, 

876 date_unit, 

877 encoding, 

878 lines: bool, 

879 chunksize: int | None, 

880 compression: CompressionOptions, 

881 nrows: int | None, 

882 storage_options: StorageOptions | None = None, 

883 encoding_errors: str | None = "strict", 

884 dtype_backend: DtypeBackend | lib.NoDefault = lib.no_default, 

885 engine: JSONEngine = "ujson", 

886 ) -> None: 

887 self.orient = orient 

888 self.typ = typ 

889 self.dtype = dtype 

890 self.convert_axes = convert_axes 

891 self.convert_dates = convert_dates 

892 self.keep_default_dates = keep_default_dates 

893 self.precise_float = precise_float 

894 self.date_unit = date_unit 

895 self.encoding = encoding 

896 self.engine = engine 

897 self.compression = compression 

898 self.storage_options = storage_options 

899 self.lines = lines 

900 self.chunksize = chunksize 

901 self.nrows_seen = 0 

902 self.nrows = nrows 

903 self.encoding_errors = encoding_errors 

904 self.handles: IOHandles[str] | None = None 

905 self.dtype_backend = dtype_backend 

906 

907 if self.engine not in {"pyarrow", "ujson"}: 

908 raise ValueError( 

909 f"The engine type {self.engine} is currently not supported." 

910 ) 

911 if self.chunksize is not None: 

912 self.chunksize = validate_integer("chunksize", self.chunksize, 1) 

913 if not self.lines: 

914 raise ValueError("chunksize can only be passed if lines=True") 

915 if self.engine == "pyarrow": 

916 raise ValueError( 

917 "currently pyarrow engine doesn't support chunksize parameter" 

918 ) 

919 if self.nrows is not None: 

920 self.nrows = validate_integer("nrows", self.nrows, 0) 

921 if not self.lines: 

922 raise ValueError("nrows can only be passed if lines=True") 

923 if self.engine == "pyarrow": 

924 if not self.lines: 

925 raise ValueError( 

926 "currently pyarrow engine only supports " 

927 "the line-delimited JSON format" 

928 ) 

929 self.data = filepath_or_buffer 

930 elif self.engine == "ujson": 

931 data = self._get_data_from_filepath(filepath_or_buffer) 

932 # If self.chunksize, we prepare the data for the `__next__` method. 

933 # Otherwise, we read it into memory for the `read` method. 

934 if not (self.chunksize or self.nrows): 

935 with self: 

936 self.data = data.read() 

937 else: 

938 self.data = data 

939 

940 def _get_data_from_filepath(self, filepath_or_buffer): 

941 """ 

942 The function read_json accepts three input types: 

943 1. filepath (string-like) 

944 2. file-like object (e.g. open file object, StringIO) 

945 """ 

946 filepath_or_buffer = stringify_path(filepath_or_buffer) 

947 try: 

948 self.handles = get_handle( 

949 filepath_or_buffer, 

950 "r", 

951 encoding=self.encoding, 

952 compression=self.compression, 

953 storage_options=self.storage_options, 

954 errors=self.encoding_errors, 

955 ) 

956 except OSError as err: 

957 raise FileNotFoundError( 

958 f"File {filepath_or_buffer} does not exist" 

959 ) from err 

960 filepath_or_buffer = self.handles.handle 

961 return filepath_or_buffer 

962 

963 def _combine_lines(self, lines) -> str: 

964 """ 

965 Combines a list of JSON objects into one JSON object. 

966 """ 

967 return ( 

968 f"[{','.join([line for line in (line.strip() for line in lines) if line])}]" 

969 ) 

970 

971 @overload 

972 def read(self: JsonReader[Literal["frame"]]) -> DataFrame: ... 

973 

974 @overload 

975 def read(self: JsonReader[Literal["series"]]) -> Series: ... 

976 

977 @overload 

978 def read(self: JsonReader[Literal["frame", "series"]]) -> DataFrame | Series: ... 

979 

980 def read(self) -> DataFrame | Series: 

981 """ 

982 Read the whole JSON input into a pandas object. 

983 """ 

984 obj: DataFrame | Series 

985 with self: 

986 if self.engine == "pyarrow": 

987 obj = self._read_pyarrow() 

988 elif self.engine == "ujson": 

989 obj = self._read_ujson() 

990 

991 return obj 

992 

993 def _read_pyarrow(self) -> DataFrame: 

994 """ 

995 Read JSON using the pyarrow engine. 

996 """ 

997 pyarrow_json = import_optional_dependency("pyarrow.json") 

998 options = None 

999 

1000 if isinstance(self.dtype, dict): 

1001 pa = import_optional_dependency("pyarrow") 

1002 fields = [] 

1003 for field, dtype in self.dtype.items(): 

1004 pd_dtype = pandas_dtype(dtype) 

1005 if isinstance(pd_dtype, ArrowDtype): 

1006 fields.append((field, pd_dtype.pyarrow_dtype)) 

1007 

1008 schema = pa.schema(fields) 

1009 options = pyarrow_json.ParseOptions( 

1010 explicit_schema=schema, unexpected_field_behavior="infer" 

1011 ) 

1012 

1013 pa_table = pyarrow_json.read_json(self.data, parse_options=options) 

1014 df = arrow_table_to_pandas(pa_table, dtype_backend=self.dtype_backend) 

1015 

1016 return df 

1017 

1018 def _read_ujson(self) -> DataFrame | Series: 

1019 """ 

1020 Read JSON using the ujson engine. 

1021 """ 

1022 obj: DataFrame | Series 

1023 if self.lines: 

1024 if self.chunksize: 

1025 obj = concat(self) 

1026 elif self.nrows: 

1027 lines = list(islice(self.data, self.nrows)) 

1028 lines_json = self._combine_lines(lines) 

1029 obj = self._get_object_parser(lines_json) 

1030 else: 

1031 data = ensure_str(self.data) 

1032 data_lines = data.split("\n") 

1033 obj = self._get_object_parser(self._combine_lines(data_lines)) 

1034 else: 

1035 obj = self._get_object_parser(self.data) 

1036 if self.dtype_backend is not lib.no_default: 

1037 with option_context("future.distinguish_nan_and_na", False): 

1038 return obj.convert_dtypes( 

1039 infer_objects=False, dtype_backend=self.dtype_backend 

1040 ) 

1041 else: 

1042 return obj 

1043 

1044 def _get_object_parser(self, json: str) -> DataFrame | Series: 

1045 """ 

1046 Parses a json document into a pandas object. 

1047 """ 

1048 typ = self.typ 

1049 dtype = self.dtype 

1050 kwargs = { 

1051 "orient": self.orient, 

1052 "dtype": self.dtype, 

1053 "convert_axes": self.convert_axes, 

1054 "convert_dates": self.convert_dates, 

1055 "keep_default_dates": self.keep_default_dates, 

1056 "precise_float": self.precise_float, 

1057 "date_unit": self.date_unit, 

1058 "dtype_backend": self.dtype_backend, 

1059 } 

1060 if typ == "frame": 

1061 return FrameParser(json, **kwargs).parse() 

1062 elif typ == "series": 

1063 if not isinstance(dtype, bool): 

1064 kwargs["dtype"] = dtype 

1065 return SeriesParser(json, **kwargs).parse() 

1066 else: 

1067 raise ValueError(f"{typ=} must be 'frame' or 'series'.") 

1068 

1069 def close(self) -> None: 

1070 """ 

1071 If we opened a stream earlier, in _get_data_from_filepath, we should 

1072 close it. 

1073 

1074 If an open stream or file was passed, we leave it open. 

1075 """ 

1076 if self.handles is not None: 

1077 self.handles.close() 

1078 

1079 def __iter__(self) -> Self: 

1080 return self 

1081 

1082 @overload 

1083 def __next__(self: JsonReader[Literal["frame"]]) -> DataFrame: ... 

1084 

1085 @overload 

1086 def __next__(self: JsonReader[Literal["series"]]) -> Series: ... 

1087 

1088 @overload 

1089 def __next__( 

1090 self: JsonReader[Literal["frame", "series"]], 

1091 ) -> DataFrame | Series: ... 

1092 

1093 def __next__(self) -> DataFrame | Series: 

1094 if self.nrows and self.nrows_seen >= self.nrows: 

1095 self.close() 

1096 raise StopIteration 

1097 

1098 lines = list(islice(self.data, self.chunksize)) 

1099 if not lines: 

1100 self.close() 

1101 raise StopIteration 

1102 

1103 try: 

1104 lines_json = self._combine_lines(lines) 

1105 obj = self._get_object_parser(lines_json) 

1106 

1107 # Make sure that the returned objects have the right index. 

1108 obj.index = range(self.nrows_seen, self.nrows_seen + len(obj)) 

1109 self.nrows_seen += len(obj) 

1110 except Exception as ex: 

1111 self.close() 

1112 raise ex 

1113 

1114 if self.dtype_backend is not lib.no_default: 

1115 with option_context("future.distinguish_nan_and_na", False): 

1116 return obj.convert_dtypes( 

1117 infer_objects=False, dtype_backend=self.dtype_backend 

1118 ) 

1119 else: 

1120 return obj 

1121 

1122 def __enter__(self) -> Self: 

1123 return self 

1124 

1125 def __exit__( 

1126 self, 

1127 exc_type: type[BaseException] | None, 

1128 exc_value: BaseException | None, 

1129 traceback: TracebackType | None, 

1130 ) -> None: 

1131 self.close() 

1132 

1133 

1134class Parser: 

1135 _split_keys: tuple[str, ...] 

1136 _default_orient: str 

1137 

1138 _STAMP_UNITS = ("s", "ms", "us", "ns") 

1139 _MIN_STAMPS = { 

1140 "s": 31536000, 

1141 "ms": 31536000000, 

1142 "us": 31536000000000, 

1143 "ns": 31536000000000000, 

1144 } 

1145 json: str 

1146 

1147 def __init__( 

1148 self, 

1149 json: str, 

1150 orient, 

1151 dtype: DtypeArg | None = None, 

1152 convert_axes: bool = True, 

1153 convert_dates: bool | list[str] = True, 

1154 keep_default_dates: bool = False, 

1155 precise_float: bool = False, 

1156 date_unit=None, 

1157 dtype_backend: DtypeBackend | lib.NoDefault = lib.no_default, 

1158 ) -> None: 

1159 self.json = json 

1160 

1161 if orient is None: 

1162 orient = self._default_orient 

1163 

1164 self.orient = orient 

1165 

1166 self.dtype = dtype 

1167 

1168 if date_unit is not None: 

1169 date_unit = date_unit.lower() 

1170 if date_unit not in self._STAMP_UNITS: 

1171 raise ValueError(f"date_unit must be one of {self._STAMP_UNITS}") 

1172 self.min_stamp = self._MIN_STAMPS[date_unit] 

1173 else: 

1174 self.min_stamp = self._MIN_STAMPS["s"] 

1175 

1176 self.precise_float = precise_float 

1177 self.convert_axes = convert_axes 

1178 self.convert_dates = convert_dates 

1179 self.date_unit = date_unit 

1180 self.keep_default_dates = keep_default_dates 

1181 self.dtype_backend = dtype_backend 

1182 

1183 @final 

1184 def check_keys_split(self, decoded: dict) -> None: 

1185 """ 

1186 Checks that dict has only the appropriate keys for orient='split'. 

1187 """ 

1188 bad_keys = set(decoded.keys()).difference(set(self._split_keys)) 

1189 if bad_keys: 

1190 bad_keys_joined = ", ".join(bad_keys) 

1191 raise ValueError(f"JSON data had unexpected key(s): {bad_keys_joined}") 

1192 

1193 @final 

1194 def parse(self) -> DataFrame | Series: 

1195 obj = self._parse() 

1196 

1197 if self.convert_axes: 

1198 obj = self._convert_axes(obj) 

1199 obj = self._try_convert_types(obj) 

1200 return obj 

1201 

1202 def _parse(self) -> DataFrame | Series: 

1203 raise AbstractMethodError(self) 

1204 

1205 @final 

1206 def _convert_axes(self, obj: DataFrame | Series) -> DataFrame | Series: 

1207 """ 

1208 Try to convert axes. 

1209 """ 

1210 for axis_name in obj._AXIS_ORDERS: 

1211 ax = obj._get_axis(axis_name) 

1212 ser = Series(ax, dtype=ax.dtype, copy=False) 

1213 new_ser, result = self._try_convert_data( 

1214 name=axis_name, 

1215 data=ser, 

1216 use_dtypes=False, 

1217 convert_dates=True, 

1218 is_axis=True, 

1219 ) 

1220 if result: 

1221 new_axis = Index(new_ser, dtype=new_ser.dtype, copy=False) 

1222 setattr(obj, axis_name, new_axis) 

1223 return obj 

1224 

1225 def _try_convert_types(self, obj): 

1226 raise AbstractMethodError(self) 

1227 

1228 @final 

1229 def _try_convert_data( 

1230 self, 

1231 name: Hashable, 

1232 data: Series, 

1233 use_dtypes: bool = True, 

1234 convert_dates: bool | list[str] = True, 

1235 is_axis: bool = False, 

1236 ) -> tuple[Series, bool]: 

1237 """ 

1238 Try to parse a Series into a column by inferring dtype. 

1239 """ 

1240 org_data = data 

1241 # don't try to coerce, unless a force conversion 

1242 if use_dtypes: 

1243 if not self.dtype: 

1244 if all(notna(data)): 

1245 return data, False 

1246 

1247 filled = data.fillna(np.nan) 

1248 

1249 return filled, True 

1250 

1251 elif self.dtype is True: 

1252 pass 

1253 elif not _should_convert_dates( 

1254 convert_dates, self.keep_default_dates, name 

1255 ): 

1256 # convert_dates takes precedence over columns listed in dtypes 

1257 dtype = ( 

1258 self.dtype.get(name) if isinstance(self.dtype, dict) else self.dtype 

1259 ) 

1260 if dtype is not None: 

1261 try: 

1262 return data.astype(dtype), True 

1263 except (TypeError, ValueError): 

1264 return data, False 

1265 

1266 if convert_dates: 

1267 new_data = self._try_convert_to_date(data) 

1268 if new_data is not data: 

1269 return new_data, True 

1270 

1271 converted = False 

1272 if self.dtype_backend is not lib.no_default and not is_axis: 

1273 # Fall through for conversion later on 

1274 return data, True 

1275 elif is_string_dtype(data.dtype): 

1276 # try float 

1277 try: 

1278 data = data.astype("float64") 

1279 converted = True 

1280 except (TypeError, ValueError): 

1281 pass 

1282 

1283 if data.dtype.kind == "f" and data.dtype != "float64": 

1284 # coerce floats to 64 

1285 try: 

1286 data = data.astype("float64") 

1287 converted = True 

1288 except (TypeError, ValueError): 

1289 pass 

1290 

1291 # don't coerce 0-len data 

1292 if len(data) and data.dtype in ("float", "object"): 

1293 # coerce ints if we can 

1294 try: 

1295 new_data = org_data.astype("int64") 

1296 if (new_data == data).all(): 

1297 data = new_data 

1298 converted = True 

1299 except (TypeError, ValueError, OverflowError): 

1300 pass 

1301 

1302 if data.dtype == "int" and data.dtype != "int64": 

1303 # coerce ints to 64 

1304 try: 

1305 data = data.astype("int64") 

1306 converted = True 

1307 except (TypeError, ValueError): 

1308 pass 

1309 

1310 # if we have an index, we want to preserve dtypes 

1311 if name == "index" and len(data): 

1312 if self.orient == "split": 

1313 return data, False 

1314 

1315 return data, converted 

1316 

1317 @final 

1318 def _try_convert_to_date(self, data: Series) -> Series: 

1319 """ 

1320 Try to parse an ndarray like into a date column. 

1321 

1322 Try to coerce object in epoch/iso formats and integer/float in epoch 

1323 formats. 

1324 """ 

1325 # no conversion on empty 

1326 if not len(data): 

1327 return data 

1328 

1329 new_data = data 

1330 

1331 if new_data.dtype == "object" or new_data.dtype == "string": # noqa: PLR1714 

1332 try: 

1333 new_data = data.astype("int64") 

1334 except OverflowError: 

1335 return data 

1336 except (TypeError, ValueError): 

1337 pass 

1338 

1339 # ignore numbers that are out of range 

1340 if issubclass(new_data.dtype.type, np.number): 

1341 in_range = ( 

1342 isna(new_data._values) 

1343 | (new_data > self.min_stamp) 

1344 | (new_data._values == iNaT) 

1345 ) 

1346 if not in_range.all(): 

1347 return data 

1348 

1349 if new_data.dtype == "string": 

1350 with warnings.catch_warnings(): 

1351 # ignore "Could not infer format" warnings from to_datetime 

1352 # which is incorrectly raised for non-date strings 

1353 warnings.simplefilter("ignore", UserWarning) 

1354 for format in (None, "iso8601", "mixed"): 

1355 try: 

1356 return to_datetime(new_data, errors="raise", format=format) 

1357 except Exception: 

1358 pass 

1359 else: 

1360 # numeric or mixed objects 

1361 date_units = (self.date_unit,) if self.date_unit else self._STAMP_UNITS 

1362 for date_unit in date_units: 

1363 try: 

1364 # In case of multiple possible units, infer the likely unit 

1365 # based on the first unit for which the parsed dates fit 

1366 # within the nanoseconds bounds 

1367 # -> do as_unit cast to ensure OutOfBounds error 

1368 data = to_datetime(new_data, errors="raise", unit=date_unit) 

1369 _ = data.dt.as_unit("ns") 

1370 break 

1371 except OutOfBoundsDatetime: 

1372 continue 

1373 except (ValueError, OverflowError, TypeError): 

1374 pass 

1375 return data 

1376 

1377 

1378class SeriesParser(Parser): 

1379 _default_orient = "index" 

1380 _split_keys = ("name", "index", "data") 

1381 

1382 def _parse(self) -> Series: 

1383 data = ujson_loads(self.json, precise_float=self.precise_float) 

1384 

1385 if self.orient == "split": 

1386 decoded = {str(k): v for k, v in data.items()} 

1387 self.check_keys_split(decoded) 

1388 return Series(**decoded) 

1389 else: 

1390 return Series(data) 

1391 

1392 def _try_convert_types(self, obj: Series) -> Series: 

1393 obj, _ = self._try_convert_data("data", obj, convert_dates=self.convert_dates) 

1394 return obj 

1395 

1396 

1397class FrameParser(Parser): 

1398 _default_orient = "columns" 

1399 _split_keys = ("columns", "index", "data") 

1400 

1401 def _parse(self) -> DataFrame: 

1402 json = self.json 

1403 orient = self.orient 

1404 

1405 if orient == "split": 

1406 decoded = { 

1407 str(k): v 

1408 for k, v in ujson_loads(json, precise_float=self.precise_float).items() 

1409 } 

1410 self.check_keys_split(decoded) 

1411 orig_names = [ 

1412 (tuple(col) if isinstance(col, list) else col) 

1413 for col in decoded["columns"] 

1414 ] 

1415 decoded["columns"] = dedup_names( 

1416 orig_names, 

1417 is_potential_multi_index(orig_names, None), 

1418 ) 

1419 return DataFrame(dtype=None, **decoded) 

1420 elif orient == "index": 

1421 return DataFrame.from_dict( 

1422 ujson_loads(json, precise_float=self.precise_float), 

1423 dtype=None, 

1424 orient="index", 

1425 ) 

1426 elif orient == "table": 

1427 return parse_table_schema(json, precise_float=self.precise_float) 

1428 else: 

1429 # includes orient == "columns" 

1430 return DataFrame( 

1431 ujson_loads(json, precise_float=self.precise_float), dtype=None 

1432 ) 

1433 

1434 def _try_convert_types(self, obj: DataFrame) -> DataFrame: 

1435 arrays = [] 

1436 for col_label, series in obj.items(): 

1437 result, _ = self._try_convert_data( 

1438 col_label, 

1439 series, 

1440 convert_dates=_should_convert_dates( 

1441 self.convert_dates, 

1442 keep_default_dates=self.keep_default_dates, 

1443 col=col_label, 

1444 ), 

1445 ) 

1446 arrays.append(result.array) 

1447 return DataFrame._from_arrays( 

1448 arrays, obj.columns, obj.index, verify_integrity=False 

1449 ) 

1450 

1451 

1452def _should_convert_dates( 

1453 convert_dates: bool | list[str], 

1454 keep_default_dates: bool, 

1455 col: Hashable, 

1456) -> bool: 

1457 """ 

1458 Return bool whether a DataFrame column should be cast to datetime. 

1459 """ 

1460 if convert_dates is False: 

1461 # convert_dates=True means follow keep_default_dates 

1462 return False 

1463 elif not isinstance(convert_dates, bool) and col in set(convert_dates): 

1464 return True 

1465 elif not keep_default_dates: 

1466 return False 

1467 elif not isinstance(col, str): 

1468 return False 

1469 col_lower = col.lower() 

1470 if ( 

1471 col_lower.endswith(("_at", "_time")) 

1472 or col_lower in {"modified", "date", "datetime"} 

1473 or col_lower.startswith("timestamp") 

1474 ): 

1475 return True 

1476 return False