|
| 1 | +# Copyright (c) Meta Platforms, Inc. and affiliates. |
| 2 | + |
| 3 | +""" |
| 4 | +JSON compatibility layer for tritonparse. |
| 5 | +
|
| 6 | +Provides a unified interface that uses orjson when available (for performance) |
| 7 | +and falls back to stdlib json (for environments where orjson is unavailable, |
| 8 | +e.g. CPython 3.14 free-threading builds). |
| 9 | +
|
| 10 | +``loads()`` accepts ``str | bytes | bytearray | memoryview`` inputs. |
| 11 | +``dumps()`` returns ``str``. |
| 12 | +""" |
| 13 | + |
| 14 | +try: |
| 15 | + import orjson as _orjson |
| 16 | + |
| 17 | + _HAS_ORJSON = True |
| 18 | +except ImportError: |
| 19 | + import json as _json |
| 20 | + |
| 21 | + _HAS_ORJSON = False |
| 22 | + |
| 23 | + |
| 24 | +def _coerce_keys(obj): |
| 25 | + """Recursively convert non-string dict keys to strings. |
| 26 | +
|
| 27 | + stdlib ``json.dumps`` raises ``TypeError`` on non-string keys, whereas |
| 28 | + orjson's ``OPT_NON_STR_KEYS`` converts them automatically. This helper |
| 29 | + replicates that behavior for the fallback path. |
| 30 | + """ |
| 31 | + if isinstance(obj, dict): |
| 32 | + return {str(k): _coerce_keys(v) for k, v in obj.items()} |
| 33 | + if isinstance(obj, list): |
| 34 | + return [_coerce_keys(v) for v in obj] |
| 35 | + return obj |
| 36 | + |
| 37 | + |
| 38 | +if _HAS_ORJSON: |
| 39 | + JSONDecodeError = _orjson.JSONDecodeError |
| 40 | + |
| 41 | + def loads(data): |
| 42 | + """Deserialize JSON string/bytes to a Python object.""" |
| 43 | + return _orjson.loads(data) |
| 44 | + |
| 45 | + def dumps(obj, *, indent=False, sort_keys=False): |
| 46 | + """Serialize a Python object to a JSON ``str``. |
| 47 | +
|
| 48 | + Args: |
| 49 | + obj: The object to serialize. |
| 50 | + indent: If True, pretty-print with 2-space indent. |
| 51 | + sort_keys: If True, sort dictionary keys. |
| 52 | + """ |
| 53 | + option = _orjson.OPT_NON_STR_KEYS |
| 54 | + if indent: |
| 55 | + option |= _orjson.OPT_INDENT_2 |
| 56 | + if sort_keys: |
| 57 | + option |= _orjson.OPT_SORT_KEYS |
| 58 | + return _orjson.dumps(obj, option=option).decode() |
| 59 | + |
| 60 | +else: |
| 61 | + from json import JSONDecodeError # noqa: F401 |
| 62 | + |
| 63 | + def loads(data): |
| 64 | + """Deserialize JSON string/bytes to a Python object.""" |
| 65 | + if isinstance(data, (bytes, bytearray, memoryview)): |
| 66 | + data = ( |
| 67 | + bytes(data).decode() if isinstance(data, memoryview) else data.decode() |
| 68 | + ) |
| 69 | + return _json.loads(data) |
| 70 | + |
| 71 | + def dumps(obj, *, indent=False, sort_keys=False): |
| 72 | + """Serialize a Python object to a JSON ``str``. |
| 73 | +
|
| 74 | + Args: |
| 75 | + obj: The object to serialize. |
| 76 | + indent: If True, pretty-print with 2-space indent. |
| 77 | + sort_keys: If True, sort dictionary keys. |
| 78 | + """ |
| 79 | + obj = _coerce_keys(obj) |
| 80 | + kwargs = {"ensure_ascii": False} |
| 81 | + if indent: |
| 82 | + kwargs["indent"] = 2 |
| 83 | + else: |
| 84 | + kwargs["separators"] = (",", ":") |
| 85 | + if sort_keys: |
| 86 | + kwargs["sort_keys"] = True |
| 87 | + return _json.dumps(obj, **kwargs) |
0 commit comments