Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor JsonParser #495

Merged
merged 7 commits into from
May 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ exclude: tests/fixtures|docs/examples

repos:
- repo: https://github.com/asottile/pyupgrade
rev: v2.15.0
rev: v2.18.1
hooks:
- id: pyupgrade
args: [--py37-plus]
Expand All @@ -11,7 +11,7 @@ repos:
hooks:
- id: reorder-python-imports
- repo: https://github.com/ambv/black
rev: 21.5b0
rev: 21.5b1
hooks:
- id: black
- repo: https://gitlab.com/pycqa/flake8
Expand All @@ -26,7 +26,7 @@ repos:
args: ["--suppress-none-returning"]

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.4.0
rev: v4.0.1
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
Expand Down
46 changes: 44 additions & 2 deletions docs/json.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,22 @@
JSON Binding
============

Binding JSON lacks a bit in features and for edge cases with wildcards and derived
types doing roundtrip conversions is not always possible.
All binding modules rely on a :class:`~xsdata.formats.dataclass.context.XmlContext`
instance to cache marshalling information.

It's recommended to either reuse the same parser/serializer instance or reuse the
context instance.

.. code-block::

from xsdata.formats.dataclass.context import XmlContext
from xsdata.formats.dataclass.parsers import JsonParser
from xsdata.formats.dataclass.serializers import JsonSerializer

context = XmlContext()
parser = JsonParser(context=context)
serializer = JsonSerializer(context=context)


.. testsetup:: *

Expand Down Expand Up @@ -91,6 +105,28 @@ From json path
BookForm(author='Hightower, Kim', title='The First Book', genre='Fiction', price=44.95, pub_date=XmlDate(2000, 10, 1), review='An amazing story of nothing.', id='bk001', lang='en')


Ignore unknown properties
-------------------------

.. doctest::

>>> from tests.fixtures.books import * # Import all classes
>>> from xsdata.formats.dataclass.parsers.config import ParserConfig
...
>>> config = ParserConfig(
... fail_on_unknown_properties=False,
... )
>>> json_string = """{
... "author": "Hightower, Kim",
... "unknown_property": "I will fail"
... }"""
>>> parser = JsonParser(config=config)
>>> parser.from_string(json_string, BookForm)
BookForm(author='Hightower, Kim', title=None, genre=None, price=None, pub_date=None, review=None, id=None, lang='en')

API :ref:`Reference <ParserConfig>`.


Unknown json target type
------------------------

Expand All @@ -113,6 +149,12 @@ all the imported modules to find a matching dataclass.
>>> parser.from_string(json_string)
BookForm(author='Hightower, Kim', title='The First Book', genre='Fiction', price=44.95, pub_date=XmlDate(2000, 10, 1), review='An amazing story of nothing.', id='bk001', lang='en')

.. warning::

The class locator searches for a dataclass that includes all the input object
properties. This process doesn't work for documents with unknown properties even
if the configuration option is disabled!


List of Objects
---------------
Expand Down
61 changes: 61 additions & 0 deletions tests/fixtures/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from dataclasses import dataclass, field
from typing import List, Dict, Union, Optional
from xml.etree.ElementTree import QName


@dataclass
class TypeA:
x: int


@dataclass
class TypeB:
x: int
y: str


@dataclass
class TypeC:
x: int
y: str
z: float


@dataclass
class TypeD:
x: int
y: str
z: Optional[bool]


@dataclass
class ExtendedType:
a: Optional[TypeA] = field(default=None)
any: Optional[object] = field(default=None)
wildcard: Optional[object] = field(default=None, metadata={"type": "Wildcard"})


@dataclass
class ChoiceType:
choice: List[object] = field(metadata={
"type": "Elements",
"choices": (
{"name": "a", "type": TypeA},
{"name": "b", "type": TypeB},
{"name": "int", "type": int},
{"name": "int2", "type": int},
{"name": "float", "type": float},
{"name": "qname", "type": QName},
{"name": "tokens", "type": List[int], "tokens": True},
)
})


@dataclass
class UnionType:
element: Union[TypeA, TypeB, TypeC, TypeD]


@dataclass
class AttrsType:
attrs: Dict[str, str] = field(metadata={"type": "Attributes"})
2 changes: 1 addition & 1 deletion tests/formats/dataclass/models/test_builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def test_build_with_no_dataclass_raises_exception(self, *args):
with self.assertRaises(XmlContextError) as cm:
XmlMetaBuilder.build(int, None, return_input, return_input)

self.assertEqual(f"Object {int} is not a dataclass.", str(cm.exception))
self.assertEqual(f"Type '{int}' is not a dataclass.", str(cm.exception))

def test_build_vars(self):
result = XmlMetaBuilder.build_vars(BookForm, None, text.pascal_case, str.upper)
Expand Down
Loading