{"id":40844752,"url":"https://github.com/hit9/dataclass-jsonable","last_synced_at":"2026-01-21T23:11:42.713Z","repository":{"id":57747373,"uuid":"522636371","full_name":"hit9/dataclass-jsonable","owner":"hit9","description":"Simple, practical and overridable conversions between dataclasses and jsonable dictionaries (long term maintenance).","archived":false,"fork":false,"pushed_at":"2025-04-03T07:33:58.000Z","size":91,"stargazers_count":11,"open_issues_count":0,"forks_count":4,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-01-08T06:35:05.742Z","etag":null,"topics":["conversion","dataclasses","dictionaries","json","jsonable"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hit9.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-BSD3","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-08-08T17:00:07.000Z","updated_at":"2025-05-20T07:23:56.000Z","dependencies_parsed_at":"2025-04-03T08:25:15.006Z","dependency_job_id":"c9043baf-684d-4544-8525-02deb6032c7f","html_url":"https://github.com/hit9/dataclass-jsonable","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/hit9/dataclass-jsonable","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hit9%2Fdataclass-jsonable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hit9%2Fdataclass-jsonable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hit9%2Fdataclass-jsonable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hit9%2Fdataclass-jsonable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hit9","download_url":"https://codeload.github.com/hit9/dataclass-jsonable/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hit9%2Fdataclass-jsonable/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28646861,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-21T21:29:11.980Z","status":"ssl_error","status_checked_at":"2026-01-21T21:24:31.872Z","response_time":86,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["conversion","dataclasses","dictionaries","json","jsonable"],"created_at":"2026-01-21T23:11:42.030Z","updated_at":"2026-01-21T23:11:42.700Z","avatar_url":"https://github.com/hit9.png","language":"Python","readme":"# dataclass-jsonable\n\n[![dataclass-jsonable ci](https://github.com/hit9/dataclass-jsonable/actions/workflows/ci.yml/badge.svg)](https://github.com/hit9/dataclass-jsonable/actions/workflows/ci.yml)\n![](https://img.shields.io/badge/license-BSD3-brightgreen)\n\n[中文说明](README.zh.md)\n\nSimple and flexible conversions between dataclasses and jsonable dictionaries.\n\nIt maps dataclasses to jsonable dictionaries but not json strings.\n\n\n## Features\n\n* Easy to use.\n* Supports common type annotations.\n* Supports recursive conversions.\n* Supports field-level and dataclass-level overriding.\n\n## Installation\n\nRequirements: Python \u003e= 3.7\n\nInstall via `pip`:\n\n```\npip install dataclass-jsonable\n```\n\n## Quick Example\n\n```python\nfrom dataclasses import dataclass\nfrom datetime import datetime\nfrom decimal import Decimal\nfrom enum import IntEnum\nfrom typing import List\nfrom dataclass_jsonable import J\n\nclass Color(IntEnum):\n    BLACK = 0\n    BLUE = 1\n    RED = 2\n\n@dataclass\nclass Pen(J):\n    color: Color\n    price: Decimal\n    produced_at: datetime\n\n@dataclass\nclass Box(J):\n    pens: List[Pen]\n\nbox = Box(pens=[Pen(color=Color.BLUE, price=Decimal(\"20.1\"), produced_at=datetime.now())])\n\n# Encode to a jsonable dictionary.\nd = box.json()\nprint(d)  # {'pens': [{'color': 1, 'price': '20.1', 'produced_at': 1660023062}]}\n\n# Construct dataclass from a jsonable dictionary.\nprint(Box.from_json(d))\n```\n\nAPIs are only the two: `.json()` and `.from_json()`.\n\n## Built-in Supported Types\n\n* `bool`, `int`, `float`, `str`, `None` encoded as it is.\n\n  ```python\n  @dataclass\n  class Obj(J):\n      a: int\n      b: str\n      c: bool\n      d: None\n\n  Obj(a=1, b=\"b\", c=True, d=None).json()\n  # =\u003e {'a': 1, 'b': 'b', 'c': True, 'd': None}\n  ```\n\n* `Decimal` encoded to `str`.\n\n  ```python\n  @dataclass\n  class Obj(J):\n      a: Decimal\n\n  Obj(a=Decimal(\"3.1\")).json()  # =\u003e {'a': '3.1'}\n  ```\n\n* `datetime` encoded to timestamp integer via `.timestamp()` method.\n  `timedelta` encoded to integer via `.total_seconds()` method.\n\n  ```python\n  @dataclass\n  class Obj(J):\n      a: datetime\n      b: timedelta\n\n  Obj(a=datetime.now(), b=timedelta(minutes=1)).json()\n  # =\u003e {'a': 1660062019, 'b': 60}\n  ```\n\n* `Enum` and `IntEnum` encoded to their values via `.value` attribute.\n\n  ```python\n  @dataclass\n  class Obj(J):\n      status: Status\n\n  Obj(status=Status.DONE).json()  # =\u003e {'status': 1}\n  ```\n\n* `Any` is encoded according to its `type`.\n\n  ```python\n  @dataclass\n  class Obj(J):\n      a: Any\n\n  Obj(1).json()  # {'a': 1}\n  Obj(\"a\").json()  # {'a': 'a'}\n  Obj.from_json({\"a\": 1})  # Obj(a=1)\n  ```\n\n* `Optional[X]` is supported, but `Union[X, Y, ...]` is not.\n\n  ```python\n  @dataclass\n  class Obj(J):\n      a: Optional[int] = None\n\n  Obj(a=1).json()  # =\u003e {'a': 1}\n  ```\n\n* `List[X]`, `Tuple[X]`, `Set[X]` are all encoded to `list`.\n\n  ```python\n  @dataclass\n  class Obj(J):\n      a: List[int]\n      b: Set[int]\n      c: Tuple[int, str]\n      d: Tuple[int, ...]\n\n  Obj(a=[1], b={2, 3}, c=(4, \"5\"), d=(7, 8, 9)).json())\n  # =\u003e {'a': [1], 'b': [2, 3], 'c': [4, '5'], 'd': [7, 8, 9]}\n\n  Obj.from_json({\"a\": [1], \"b\": [2, 3], \"c\": [4, \"5\"], \"d\": [7, 8, 9]}))\n  # =\u003e Obj(a=[1], b={2, 3}, c=(4, '5'), d=(7, 8, 9))\n  ```\n\n* `Dict[str, X]` encoded to `dict`.\n\n  ```python\n  @dataclass\n  class Obj(J):\n      a: Dict[str, int]\n  Obj(a={\"x\": 1}).json()  # =\u003e {'a': {'x': 1}}\n  Obj.from_json({\"a\": {\"x\": 1}}) # =\u003e Obj(a={'x': 1})\n  ```\n\n* Nested or recursively `JSONAble` (or `J`) dataclasses.\n\n  ```python\n  @dataclass\n  class Elem(J):\n      k: str\n\n  @dataclass\n  class Obj(J):\n      a: List[Elem]\n\n  Obj([Elem(\"v\")]).json()  # =\u003e {'a': [{'k': 'v'}]}\n  Obj.from_json({\"a\": [{\"k\": \"v\"}]})  # Obj(a=[Elem(k='v')])\n  ```\n\n* Postponed annotations (the `ForwardRef` in [PEP 563](https://www.python.org/dev/peps/pep-0563/)).\n\n  ```python\n  @dataclass\n  class Node(J):\n      name: str\n      left: Optional[\"Node\"] = None\n      right: Optional[\"Node\"] = None\n\n  root = Node(\"root\", left=Node(\"left\"), right=Node(\"right\"))\n  root.json()\n  # {'name': 'root', 'left': {'name': 'left', 'left': None, 'right': None}, 'right': {'name': 'right', 'left': None, 'right': None}}\n  ```\n\nIf these built-in default conversion behaviors do not meet your needs,\nor your type is not on the list,\nyou can use [json_options](#customization--overriding-examples) introduced below to customize it.\n\n## Customization / Overriding Examples\n\nWe can override the default conversion behaviors with `json_options`,\nwhich uses the dataclass field's metadata for field-level customization purpose,\nand the namespace is `j`.\n\nThe following pseudo code gives the pattern:\n\n```python\nfrom dataclasses import field\nfrom dataclass_jsonable import json_options\n\n@dataclass\nclass Struct(J):\n    attr: T = field(metadata={\"j\": json_options(**kwds)})\n```\n\nAn example list about `json_options`:\n\n* Specific a custom dictionary key over the default field's name:\n\n   ```python\n   @dataclass\n   class Person(J):\n       attr: str = field(metadata={\"j\": json_options(name=\"new_attr\")})\n   Person(attr=\"value\").json() # =\u003e {\"new_attr\": \"value\"}\n   ```\n\n  And more, we can use a function to specific a custom dictionary key.\n  This may be convenient to work with class-level `__default_json_options__` attribute (check it below).\n\n  ```python\n  @dataclass\n  class Obj(J):\n      simple_value: int = field(metadata={\"j\": json_options(name_converter=to_camel_case)})\n  Obj(simple_value=1).json()  # =\u003e {\"simpleValue\": 1}\n  ```\n\n  And we may specific a custom field name converter when converts dictionary to dataclass:\n\n  ```python\n  @dataclass\n  def Person(J):\n    name: str = field(\n          metadata={\n              \"j\": json_options(\n                  name_converter=lambda x: x.capitalize(),\n                  name_inverter=lambda x: \"nickname\",\n            )\n        }\n    )\n  ```\n\n  As the `Person` defined above, it will convert to dictionary like `{\"Name\": \"Jack\"}` and can be loaded from `{\"nickname\": \"Jack\"}`.\n\n* Omit a field if its value is empty:\n\n   ```python\n   @dataclass\n   class Book(J):\n       name: str = field(metadata={\"j\": json_options(omitempty=True)})\n   Book(name=\"\").json() # =\u003e {}\n   ```\n\n  Further, we can specify what is 'empty' via option `omitempty_tester`:\n\n   ```python\n   @dataclass\n   class Book(J):\n       attr: Optional[str] = field(\n           default=None,\n           metadata={\n               # By default, we test `empty` using `not x`.\n               \"j\": json_options(omitempty=True, omitempty_tester=lambda x: x is None)\n           },\n       )\n\n   Book(attr=\"\").json()  # =\u003e {'attr': ''}\n   Book(attr=None).json()  # =\u003e {}\n   ```\n\n* Always skip a field. So we can stop some \"private\" fields from exporting:\n\n   ```python\n   @dataclass\n   class Obj(J):\n       attr: str = field(metadata={\"j\": json_options(skip=True)})\n\n   Obj(attr=\"private\").json() # =\u003e {}\n   ```\n\n* Always keep a field without encoding nor decoding, this prevents the default encoding/decoding behavior:\n\n   ```python\n   @dataclass\n   class Obj(J):\n       timestamp: datetime = field(metadata={\"j\": json_options(keep=True)})\n\n   Obj(timestamp=datetime.now()).json() # =\u003e  {'timestamp': datetime.datetime(2023, 9, 5, 14, 54, 24, 679103)}\n   ```\n\n* dataclasses's `field` allows us to pass a `default` or `default_factory` argument to\n  set a default value:\n\n  ```python\n  @dataclass\n  class Obj(J):\n      attr: List[str] = field(default_factory=list, metadata={\"j\": json_options(**kwds)})\n  ```\n\n  There's also an option `default_before_decoding` in dataclass-jsonable,\n  which specifics a default value before decoding if the key is missing in the dictionary.\n  Sometimes this way is more concise:\n\n  ```python\n  @dataclass\n  class Obj(J):\n      updated_at: datetime = field(metadata={\"j\": json_options(default_before_decoding=0)})\n\n  Obj.from_json({})  # =\u003e Obj(updated_at=datetime.datetime(1970, 1, 1, 8, 0))\n  ```\n\n  dataclass-jsonable also introduces a class-level similar option `__default_factory__`.\n  If a field has no `default` or `default_factory` declared, and has no `default_before_decoding` option used,\n  this function will generate a default value according to its type, to prevent a\n  \"missing positional arguments\" TypeError from raising.\n\n  ```python\n  from dataclass_jsonable import J, zero\n\n  @dataclass\n  class Obj(J):\n      __default_factory__ = zero\n\n      n: int\n      s: str\n      k: List[str]\n\n  Obj.from_json({})  # =\u003e Obj(n=0, s='', k=[])\n  ```\n\n* Override the default encoders and decoders.\n\n  This way, you have complete control over how to encode and decode at field level.\n\n  ```python\n  @dataclass\n  class Obj(J):\n      elems: List[str] = field(\n          metadata={\n              \"j\": json_options(\n                  encoder=lambda x: \",\".join(x),\n                  decoder=lambda x: x.split(\",\"),\n              )\n          }\n      )\n\n  Obj(elems=[\"a\", \"b\", \"c\"]).json()  # =\u003e {'elems': 'a,b,c'}\n  Obj.from_json({\"elems\": \"a,b,c\"})  # =\u003e Obj(elems=['a', 'b', 'c'])\n  ```\n\n  The following code snippet about `datetime` is a very common example,\n  you might want ISO format datetime conversion over timestamp integers.\n\n  ```python\n  @dataclass\n  class Record(J):\n      created_at: datetime = field(\n          default_factory=datetime.now,\n          metadata={\n              \"j\": json_options(\n                  encoder=datetime.isoformat,\n                  decoder=datetime.fromisoformat,\n              )\n          },\n      )\n\n  Record().json()  # =\u003e {'created_at': '2022-08-09T23:23:02.543007'}\n  ```\n\n  dataclass-jsonable gives `encoder` and `decoder` better alias names since 0.1.1:\n  `to_json` and `from_json`.\n\n  ```python\n  @dataclass\n  class Obj(J):\n      elems: List[str] = field(\n          metadata={\n              \"j\": json_options(\n                  to_json=lambda x: \",\".join(x),  # Alias for encoder\n                  from_json=lambda x: x.split(\",\"),  # Alias for decoder\n              )\n          }\n      )\n\n  Obj(elems=[\"a\", \"b\", \"c\"]).json()  # =\u003e {'elems': 'a,b,c'}\n  Obj.from_json({\"elems\": \"a,b,c\"})  # =\u003e Obj(elems=['a', 'b', 'c'])\n  ```\n\n* For some very narrow scenarios, we may need to execute a hook function before decoding,\n  for example, the data to be decoded is a serialized json string,\n  and but we still want to use the built-in decoder functions instead of making a new decoder.\n\n  ```python\n  import json\n\n  @dataclass\n  class Obj(J):\n      data: Dict[str, Any] = field(metadata={\"j\": json_options(before_decoder=json.loads)})\n\n  Obj.from_json({\"data\": '{\"k\": \"v\"}'})\n  # =\u003e Obj(data={'k': 'v'})\n  ```\n\n* Customize default behaviors at the class level.\n\n  If an option is not explicitly set at the field level,\n  the `__default_json_options__` provided at the class level will be attempted.\n\n  ````python\n  @dataclass\n  class Obj(J):\n      __default_json_options__ = json_options(omitempty=True)\n\n      a: Optional[int] = None\n      b: Optional[str] = None\n\n  Obj(b=\"b\").json() # =\u003e {'b': 'b'}\n  ````\n\n  ```python\n  @dataclass\n  class Obj(J):\n      __default_json_options__ = json_options(name_converter=to_camel_case)\n\n      status_code: int\n      simple_value: str\n\n  Obj2(status_code=1, simple_value=\"simple\").json()\n  # =\u003e {\"statusCode\": 1, \"simpleValue\": \"simple\"}\n  ```\n\n## Debuging\n\nIt provides a method `obj._get_origin_json()`,\nit returns the original json dictionary which constructs instance `obj` via `from_json()`.\n\n```python\nd = {\"a\": 1}\nobj = Obj.from_json(d)\nobj._get_origin_json()\n# =\u003e {\"a\": 1}\n```\n\n## License\n\nBSD.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhit9%2Fdataclass-jsonable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhit9%2Fdataclass-jsonable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhit9%2Fdataclass-jsonable/lists"}