{ "info": { "author": "gulats", "author_email": "bharat.gulati.certi@gmail.com", "bugtrack_url": null, "classifiers": [], "description": "# DC JSON\n\nThis library provides a simple API for encoding and decoding [dataclasses](https://docs.python.org/3/library/dataclasses.html) to and from JSON.\n\nIt's recursive (see caveats below), so you can easily work with nested dataclasses.\nIn addition to the supported types in the \n[py to JSON table](https://docs.python.org/3/library/json.html#py-to-json-table), this library supports the following:\n- any arbitrary [Collection](https://docs.python.org/3/library/collections.abc.html#collections.abc.Collection) type is supported.\n[Mapping](https://docs.python.org/3/library/collections.abc.html#collections.abc.Mapping) types are encoded as JSON objects and `str` types as JSON strings. \nAny other Collection types are encoded into JSON arrays, but decoded into the original collection types.\n- [datetime](https://docs.python.org/3/library/datetime.html#available-types) \nobjects. `datetime` objects are encoded to `float` (JSON number) using \n[timestamp](https://docs.python.org/3/library/datetime.html#datetime.datetime.timestamp).\nAs specified in the `datetime` docs, if your `datetime` object is naive, it will \nassume your system local timezone when calling `.timestamp()`. JSON nunbers \ncorresponding to a `datetime` field in your dataclass are decoded \ninto a datetime-aware object, with `tzinfo` set to your system local timezone.\nThus, if you encode a datetime-naive object, you will decode into a \ndatetime-aware object. This is important, because encoding and decoding won't \nstrictly be inverses. See this section if you want to override this default\nbehavior (for example, if you want to use ISO).\n- [UUID](https://docs.python.org/3/library/uuid.html#uuid.UUID) objects. They \nare encoded as `str` (JSON string).\n\n\n**The [latest release](https://github.com/lidatong/dc-json/releases/latest) is compatible with both Python 3.7 and Python 3.6 (with the dataclasses backport).**\n\n## Quickstart\n`pip install dc-json`\n\n#### Approach 1: Class decorator\n\n```python\nfrom dataclasses import dataclass\nfrom dc_json import dataclass_json\n\n@dataclass_json\n@dataclass\nclass Person:\n name: str\n\nlidatong = Person('lidatong')\n\n# Encoding to JSON\nlidatong.to_json() # '{\"name\": \"lidatong\"}'\n\n# Decoding from JSON\nPerson.from_json('{\"name\": \"lidatong\"}') # Person(name='lidatong')\n```\n\nNote that the `@dataclass_json` decorator must be stacked above the `@dataclass`\ndecorator (order matters!)\n\n#### Approach 2: Inherit from a mixin\n\n```python\nfrom dataclasses import dataclass\nfrom dc_json import DataClassJsonMixin\n\n@dataclass\nclass Person(DataClassJsonMixin):\n name: str\n\nlidatong = Person('lidatong')\n\n# A different example from Approach 1 above, but usage is the exact same\nassert Person.from_json(lidatong.to_json()) == lidatong\n```\n\nPick whichever approach suits your taste. The differences in implementation are\ninvisible in usage.\n\n## How do I...\n\n\n### Use my dataclass with JSON arrays or objects?\n\n```python\nfrom dataclasses import dataclass\nfrom dc_json import dataclass_json\n\n@dataclass_json\n@dataclass\nclass Person:\n name: str\n```\n\n**Encode into a JSON array containing instances of my Data Class**\n\n```python\npeople_json = [Person('lidatong')]\nPerson.schema().dumps(people_json, many=True) # '[{\"name\": \"lidatong\"}]'\n```\n\n**Decode a JSON array containing instances of my Data Class**\n\n```python\npeople_json = '[{\"name\": \"lidatong\"}]'\nPerson.schema().loads(people_json, many=True) # [Person(name='lidatong')]\n```\n\n**Encode as part of a larger JSON object containing my Data Class (e.g. an HTTP \nrequest/response)**\n\n```python\nimport json\n\nperson_dict = Person.schema().dump(Person('lidatong'))\n\nresponse_dict = {\n 'response': {\n 'person': person_dict\n }\n}\n\nresponse_json = json.dumps(response_dict)\n```\n\nIn this case, we do two steps. First, we encode the dataclass into a \n**python dictionary** rather than a JSON string, using `schema()` and `dump`. \nScroll down for a section addressing that.\n\nSecond, we leverage the built-in `json.dumps` to serialize our `dataclass` into \na JSON string.\n\n**Decode as part of a larger JSON object containing my Data Class (e.g. an HTTP \nresponse)**\n\n```python\nimport json\n\nresponse_dict = json.loads('{\"response\": {\"person\": {\"name\": \"lidatong\"}}}')\n\nperson_dict = response_dict['response']\n\nperson = Person.schema().load(person_dict)\n```\n\nIn a similar vein to encoding above, we leverage the built-in `json` module.\n\nFirst, call `json.loads` to read the entire JSON object into a \ndictionary. We then access the key of the value containing the encoded dict of \nour `Person` that we want to decode (`response_dict['response']`).\n\nSecond, we load in the dictionary using `Person.schema().load`.\n\n\n\n\n### Encode or decode into Python lists/dictionaries rather than JSON?\n\nThis can be by calling `.schema()` and then using the corresponding \nencoder/decoder methods, ie. `.load(...)`/`.dump(...)`.\n\n**Encode into a single Python dictionary**\n\n```python\nperson = Person('lidatong')\nPerson.schema().dump(person) # {\"name\": \"lidatong\"}\n```\n\n**Encode into a list of Python dictionaries**\n\n```python\npeople = [Person('lidatong')]\nPerson.schema().dump(people, many=True) # [{\"name\": \"lidatong\"}]\n```\n\n**Decode a dictionary into a single dataclass instance**\n\n```python\nperson_dict = {\"name\": \"lidatong\"}\nPerson.schema().load(person_dict) # Person(name='lidatong')\n```\n\n**Decode a list of dictionaries into a list of dataclass instances**\n\n```python\npeople_dicts = [{\"name\": \"lidatong\"}]\nPerson.schema().load(people_dicts, many=True) # [Person(name='lidatong')]\n```\n\n### Handle missing or optional field values when decoding?\n\nBy default, any fields in your dataclass that use `default` or \n`default_factory` will have the values filled with the provided default, if the\ncorresponding field is missing from the JSON you're decoding.\n\n**Decode JSON with missing field**\n\n```python\n@dataclass_json\n@dataclass\nclass Student:\n id: int\n name: str = 'student'\n\nStudent.from_json('{\"id\": 1}') # Student(id=1, name='student')\n```\n\nNotice `from_json` filled the field `name` with the specified default 'student'\nwhen it was missing from the JSON.\n\nSometimes you have fields that are typed as `Optional`, but you don't \nnecessarily want to assign a default. In that case, you can use the \n`infer_missing` kwarg to make `from_json` infer the missing field value as `None`.\n\n**Decode optional field without default**\n\n```python\n@dataclass_json\n@dataclass\nclass Tutor:\n id: int\n student: Optional[Student]\n\nTutor.from_json('{\"id\": 1}') # Tutor(id=1, student=None)\n```\n\nPersonally I recommend you leverage dataclass defaults rather than using \n`infer_missing`, but if for some reason you need to decouple the behavior of \nJSON decoding from the field's default value, this will allow you to do so.\n\n### Explanation\n\nBriefly, on what's going on under the hood in the above examples: calling \n`.schema()` will have this library generate a\n[marshmallow schema]('https://marshmallow.readthedocs.io/en/3.0/api_reference.html#schema)\nfor you. It also fills in the corresponding object hook, so that marshmallow\nwill create an instance of your Data Class on `load` (e.g.\n`Person.schema().load` returns a `Person`) rather than a `dict`, which it does\nby default in marshmallow.\n\n**Performance note**\n\n`.schema()` is not cached (it generates the schema on every call), so if you\nhave a nested Data Class you may want to save the result to a variable to \navoid re-generation of the schema on every usage.\n\n```python\nperson_schema = Person.schema()\nperson_schema.dump(people, many=True)\n\n# later in the code...\n\nperson_schema.dump(person)\n```\n\n\n### Override the default encode / decode / marshmallow field of a specific field?\n\nSee [Overriding](#Overriding)\n\n\n\n## Marshmallow interop\n\nUsing the `dataclass_json` decorator or mixing in `DataClassJsonMixin` will\nprovide you with an additional method `.schema()`.\n\n`.schema()` generates a schema exactly equivalent to manually creating a\nmarshmallow schema for your dataclass. You can reference the [marshmallow API docs](https://marshmallow.readthedocs.io/en/3.0/api_reference.html#schema)\nto learn other ways you can use the schema returned by `.schema()`.\n\nYou can pass in the exact same arguments to `.schema()` that you would when\nconstructing a `PersonSchema` instance, e.g. `.schema(many=True)`, and they will\nget passed through to the marshmallow schema.\n\n```python\nfrom dataclasses import dataclass\nfrom dc_json import dataclass_json\n\n@dataclass_json\n@dataclass\nclass Person:\n name: str\n\n# You don't need to do this - it's generated for you by `.schema()`!\nfrom marshmallow import Schema, fields\n\nclass PersonSchema(Schema):\n name = fields.Str()\n```\n\n## Overriding / Extending\n\n#### Overriding\n\nFor example, you might want to encode/decode `datetime` objects using ISO format\nrather than the default `timestamp`.\n\n```python\nfrom dataclasses import dataclass, field\nfrom dc_json import dataclass_json\nfrom datetime import datetime\nfrom marshmallow import fields\n\n@dataclass_json\n@dataclass\nclass DataClassWithIsoDatetime:\n created_at: datetime = field(\n metadata={'dc_json': {\n 'encoder': datetime.isoformat,\n 'decoder': datetime.fromisoformat,\n 'mm_field': fields.DateTime(format='iso')\n }})\n```\n\n#### Extending\n\nSimilarly, you might want to extend `dc_json` to encode `date` objects.\n\n```python\nfrom dataclasses import dataclass, field\nfrom dc_json import dataclass_json\nfrom datetime import date\nfrom marshmallow import fields\n\n@dataclass_json\n@dataclass\nclass DataClassWithIsoDatetime:\n created_at: date = field(\n metadata={'dc_json': {\n 'encoder': date.isoformat,\n 'decoder': date.fromisoformat,\n 'mm_field': fields.DateTime(format='iso')\n }})\n```\n\nAs you can see, you can **override** or **extend** the default codecs by providing a \"hook\" via a \ncallable:\n- `encoder`: a callable, which will be invoked to convert the field value when encoding to JSON\n- `decoder`: a callable, which will be invoked to convert the JSON value when decoding from JSON\n- `mm_field`: a marshmallow field, which will affect the behavior of any operations involving `.schema()`\n\nNote that these hooks will be invoked regardless if you're using \n`.to_json`/`dump`/`dumps`\nand `.from_json`/`load`/`loads`. So apply overrides / extensions judiciously, making sure to \ncarefully consider whether the interaction of the encode/decode/mm_field is consistent with what you expect!\n\n## A larger example\n\n```python\nfrom dataclasses import dataclass\nfrom dc_json import dataclass_json\nfrom typing import List\n\n@dataclass_json\n@dataclass(frozen=True)\nclass Minion:\n name: str\n\n\n@dataclass_json\n@dataclass(frozen=True)\nclass Boss:\n minions: List[Minion]\n\nboss = Boss([Minion('evil minion'), Minion('very evil minion')])\nboss_json = \"\"\"\n{\n \"minions\": [\n {\n \"name\": \"evil minion\"\n },\n {\n \"name\": \"very evil minion\"\n }\n ]\n}\n\"\"\".strip()\n\nassert boss.to_json(indent=4) == boss_json\nassert Boss.from_json(boss_json) == boss\n```\n\n\n## Caveats\nData Classes that contain forward references (e.g. recursive dataclasses) are\nnot currently supported.", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/Gulats/dc-json", "keywords": "dataclasses json", "license": "Unlicense", "maintainer": "", "maintainer_email": "", "name": "dc-json", "package_url": "https://pypi.org/project/dc-json/", "platform": "", "project_url": "https://pypi.org/project/dc-json/", "project_urls": { "Homepage": "https://github.com/Gulats/dc-json" }, "release_url": "https://pypi.org/project/dc-json/0.2.3/", "requires_dist": null, "requires_python": ">=3.6", "summary": "Easily serialize dataclasses to and from JSON", "version": "0.2.3" }, "last_serial": 5110537, "releases": { "0.2.3": [ { "comment_text": "", "digests": { "md5": "652e2450da9dc2e59189eae40a932378", "sha256": "c58c57359086eaa6f710c1c0fc0090dbf42ede934ea5a0ac9b518941ed13e3b6" }, "downloads": -1, "filename": "dc-json-0.2.3.tar.gz", "has_sig": false, "md5_digest": "652e2450da9dc2e59189eae40a932378", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 14191, "upload_time": "2019-04-07T17:52:07", "url": "https://files.pythonhosted.org/packages/8a/0e/01265af0c8d328fa46bb0fbf3377bd2acb0b5f0b6a6ba48aa38409f13e5f/dc-json-0.2.3.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "652e2450da9dc2e59189eae40a932378", "sha256": "c58c57359086eaa6f710c1c0fc0090dbf42ede934ea5a0ac9b518941ed13e3b6" }, "downloads": -1, "filename": "dc-json-0.2.3.tar.gz", "has_sig": false, "md5_digest": "652e2450da9dc2e59189eae40a932378", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 14191, "upload_time": "2019-04-07T17:52:07", "url": "https://files.pythonhosted.org/packages/8a/0e/01265af0c8d328fa46bb0fbf3377bd2acb0b5f0b6a6ba48aa38409f13e5f/dc-json-0.2.3.tar.gz" } ] }