Repository: tefra/xsdata
Pull request #434 (2021-03-25 21:34:37)

Fix literal qname converter
Yeah I broke the literal qname `{a}b` parsing used in json. 

sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=VULNERABILITY)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=434&resolved=false&types=SECURITY_HOTSPOT) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=434&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=434&resolved=false&types=SECURITY_HOTSPOT)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo.png' alt='No Coverage information' width='16' height='16' />](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=434) No Coverage information  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=434&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=434&metric=new_duplicated_lines_density&view=list)


codecov[bot]: # [Codecov](https://codecov.io/gh/tefra/xsdata/pull/434?src=pr&el=h1) Report
> Merging [#434](https://codecov.io/gh/tefra/xsdata/pull/434?src=pr&el=desc) (7200e96) into [master](https://codecov.io/gh/tefra/xsdata/commit/73484d0bd8077e9a8cff57d2d1146479274df3eb?el=desc) (73484d0) will **decrease** coverage by `0.08%`.
> The diff coverage is `87.09%`.

[![Impacted file tree graph](https://codecov.io/gh/tefra/xsdata/pull/434/graphs/tree.svg?width=650&height=150&src=pr&token=YzDDLtywvl)](https://codecov.io/gh/tefra/xsdata/pull/434?src=pr&el=tree)

```diff
@@             Coverage Diff             @@
##            master     #434      +/-   ##
===========================================
- Coverage   100.00%   99.91%   -0.09%     
===========================================
  Files           74       74              
  Lines         6875     6894      +19     
  Branches      1211     1217       +6     
===========================================
+ Hits          6875     6888      +13     
- Misses           0        3       +3     
- Partials         0        3       +3     
```


| [Impacted Files](https://codecov.io/gh/tefra/xsdata/pull/434?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [xsdata/formats/converter.py](https://codecov.io/gh/tefra/xsdata/pull/434/diff?src=pr&el=tree#diff-eHNkYXRhL2Zvcm1hdHMvY29udmVydGVyLnB5) | `99.31% <86.66%> (-0.69%)` | :arrow_down: |
| [xsdata/utils/namespaces.py](https://codecov.io/gh/tefra/xsdata/pull/434/diff?src=pr&el=tree#diff-eHNkYXRhL3V0aWxzL25hbWVzcGFjZXMucHk=) | `97.36% <87.50%> (-2.64%)` | :arrow_down: |
| [xsdata/formats/dataclass/parsers/json.py](https://codecov.io/gh/tefra/xsdata/pull/434/diff?src=pr&el=tree#diff-eHNkYXRhL2Zvcm1hdHMvZGF0YWNsYXNzL3BhcnNlcnMvanNvbi5weQ==) | `98.58% <0.00%> (-1.42%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/tefra/xsdata/pull/434?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/tefra/xsdata/pull/434?src=pr&el=footer). Last update [73484d0...7200e96](https://codecov.io/gh/tefra/xsdata/pull/434?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=VULNERABILITY)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=434&resolved=false&types=SECURITY_HOTSPOT) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=434&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=434&resolved=false&types=SECURITY_HOTSPOT)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo.png' alt='No Coverage information' width='16' height='16' />](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=434) No Coverage information  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=434&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=434&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=VULNERABILITY)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=434&resolved=false&types=SECURITY_HOTSPOT) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=434&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=434&resolved=false&types=SECURITY_HOTSPOT)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=434&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo.png' alt='No Coverage information' width='16' height='16' />](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=434) No Coverage information  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=434&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=434&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/README.rst b/README.rst
index 1feadac5..95b7b63d 100644
--- a/README.rst
+++ b/README.rst
@@ -44,7 +44,7 @@ xsData is constantly tested against the
 Getting started
 ---------------
 
-.. code-block:: bash
+.. code-block:: console
 
     $ pip install xsdata[cli,lxml,soap]
 
diff --git a/docs/codegen.rst b/docs/codegen.rst
index bc84499f..985e69e0 100644
--- a/docs/codegen.rst
+++ b/docs/codegen.rst
@@ -5,7 +5,7 @@ Command Line
 
 Make sure the cli requirements are installed.
 
-.. code-block:: bash
+.. code-block:: console
 
     $ pip install xsdata[cli]
 
@@ -18,128 +18,43 @@ Generate Models
 
 .. command-output:: xsdata generate --help
 
-Generate is the default subcommand of the command line interface and can be omitted.
-
-.. code-block:: console
-
-    $ xsdata amadeus/schemas --package amadeus.models
-
-    $ xsdata air_v48_0/AirReqRsp.xsd rail_v48_0/RailReqRsp.xsd --package travelport.models
-
-    $ xsdata http://www.gstatic.com/localfeed/local_feed.xsd --package feeds --print
-
-    $ xsdata https://musicbrainz.org/ws/2/artist/1f9df192-a621-4f54-8850-2c5373b7eac9 --print
-
-
-SOURCE
-------
-
-The generator can work with xml schemas, wsdl services and xml documents directly.
-
-You can pass the path to a file or a remote resource or event a directory with multiple
-sources.
-
-
-config
-------
-
-Specify a configuration file with more advance options.
-
-
-package
--------
-
-Specify where the target module(s) will be created inside the current working directory.
-eg `--package api.models`
-
-.. admonition:: Note
+.. admonition:: See also
     :class: hint
 
-    The cli is relying on the `os.path.commonpath` of the schemas locations to
-    create the final package structure. If you prefer a more flat structure or
-    you have circular import errors check the option :ref:`ns-struct`.
-
-
-output
-------
-
-Specify the output format from the builtin code generators and third party installed
-plugins.
+    - :ref:`Compound fields <Type: Elements>`
+    - :ref:`Docstring styles`
 
-.. hint::
 
-    The default output format is the builtin ``dataclasses``
+Examples
+--------
 
-    Plugins:
-
-    - `PlantUML <https://github.com/tefra/xsdata-plantuml>`_ class diagrams
-
-
-
-
-compound-fields
----------------
-
-The generator by default will flatten repeating choice elements into simple fields.
-The main disadvantage is that the original elements ordering is lost during marshalling.
-With this option you can force the generator to create compound list fields to preserve
-ordering.
-
-See :ref:`Type: Elements`
-
-
-docstring-style
----------------
-
-Choose the style of the generated docstrings.
-
-The accessible is a custom style offering easy access to the docstrings
-of a dataclass' fields or an enum' members for doc/api generators.
-
-.. tab:: reStructuredText
-
-    .. literalinclude:: /../tests/fixtures/docstrings/rst/schema.py
-       :language: python
-       :lines: 37-
-
-.. tab:: NumPy
-
-    .. literalinclude:: /../tests/fixtures/docstrings/numpy/schema.py
-       :language: python
-       :lines: 39-
-
-.. tab:: Google
-
-    .. literalinclude:: /../tests/fixtures/docstrings/google/schema.py
-       :language: python
-       :lines: 38-
+.. code-block:: console
+    :name: directory source
 
-.. tab:: Accessible
+    $ xsdata amadeus/schemas --package amadeus.models
 
-    .. literalinclude:: /../tests/fixtures/docstrings/accessible/schema.py
-       :language: python
-       :lines: 38-
 
+.. code-block:: console
+    :name: file path
 
-verbosity
----------
+    $ xsdata air_v48_0/AirReqRsp.xsd rail_v48_0/RailReqRsp.xsd --package travelport.models
 
-Specify the log level, default is ``INFO``
 
-Available options: ``CRITICAL``, ``ERROR``, ``WARNING``, ``INFO`` or ``DEBUG``
+.. code-block:: console
+    :name: uri resource
 
+    $ xsdata http://www.gstatic.com/localfeed/local_feed.xsd --package feeds --print
 
-print
------
+.. code-block:: console
+    :name: xml url
 
-Redirect generated code to stdOut instead of writing the output to the target files.
+    $ xsdata https://musicbrainz.org/ws/2/artist/1f9df192-a621-4f54-8850-2c5373b7eac9 --print
 
 
-ns-struct
----------
+Output plugins
+--------------
 
-Group classes by the target namespace they were defined. This option creates a more
-flat package structure and solves many circular import errors.
+- `PlantUML <https://github.com/tefra/xsdata-plantuml>`_ class diagrams
 
 
 Initialize Config
diff --git a/docs/demos.rst b/docs/demos.rst
index 84be91f2..5b2901aa 100644
--- a/docs/demos.rst
+++ b/docs/demos.rst
@@ -47,12 +47,11 @@ XML and JSON Binding.
     without coverage.
 
 
-Report
-------
+XSD Results: **10** failed, **14576** passed, **89** skipped, **16** warnings
 
-XML Results: **12** failed, **14575** passed, **88** skipped, **42** warnings
+XML Results: **13** failed, **14573** passed, **90** skipped, **15** warnings
 
-JSON Results: **120** failed, **14546** passed, **64** warnings
+JSON Results: **113** failed, **14562** passed, **1** warnings
 
 
 ✨✨✨✨
@@ -65,3 +64,39 @@ JSON Results: **120** failed, **14546** passed, **64** warnings
 
     The json mode performs roundtrip conversions and compares the initial and final
     outputs
+
+    The xml mode is using the document instances instead of the schemas to generate
+    the binding models.
+
+
+Docstring styles
+================
+
+Choose the style of the generated docstrings.
+
+The accessible is a custom style offering easy access to the docstrings
+of a dataclass' fields or an enum' members for doc/api generators.
+
+.. tab:: reStructuredText
+
+    .. literalinclude:: /../tests/fixtures/docstrings/rst/schema.py
+       :language: python
+       :lines: 37-
+
+.. tab:: NumPy
+
+    .. literalinclude:: /../tests/fixtures/docstrings/numpy/schema.py
+       :language: python
+       :lines: 39-
+
+.. tab:: Google
+
+    .. literalinclude:: /../tests/fixtures/docstrings/google/schema.py
+       :language: python
+       :lines: 38-
+
+.. tab:: Accessible
+
+    .. literalinclude:: /../tests/fixtures/docstrings/accessible/schema.py
+       :language: python
+       :lines: 38-
diff --git a/docs/installation.rst b/docs/installation.rst
index 196456a6..7e346aec 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -6,7 +6,7 @@ Install using pip
 
 The recommended method is to use a virtual environment.
 
-.. code-block:: bash
+.. code-block:: console
 
     $ pip install xsdata[cli,lxml,soap]
 
@@ -20,7 +20,7 @@ The recommended method is to use a virtual environment.
 xsdata has a monthly release cycle, in order to use the latest updates you can also
 install directly from the git repo.
 
-.. code-block:: bash
+.. code-block:: console
 
     $ pip install git+https://github.com/tefra/xsdata@master#egg=xsdata[cli,lxml]
 
@@ -28,7 +28,7 @@ install directly from the git repo.
 Install using conda
 -------------------
 
-.. code-block:: bash
+.. code-block:: console
 
     $ conda install -c conda-forge xsdata
 
diff --git a/xsdata/cli.py b/xsdata/cli.py
index baa26bdd..e5107cf4 100644
--- a/xsdata/cli.py
+++ b/xsdata/cli.py
@@ -34,7 +34,7 @@ def cli():
 
 @cli.command("init-config")
 @click.argument("output", type=click.Path(), default=".xsdata.xml")
-@click.option("--print", is_flag=True, default=False, help="Print output")
+@click.option("-pp", "--print", is_flag=True, default=False, help="Print output")
 def init_config(**kwargs: Any):
     """Create or update a configuration file."""
 
@@ -59,6 +59,7 @@ def init_config(**kwargs: Any):
 @cli.command("download")
 @click.argument("source", required=True)
 @click.option(
+    "-o",
     "--output",
     type=click.Path(resolve_path=True),
     default="./",
@@ -72,39 +73,81 @@ def download(source: str, output: str):
 
 @cli.command("generate")
 @click.argument("source", required=True)
-@click.option("--config", default=".xsdata.xml", help="Configuration file")
-@click.option("--package", required=False, help="Target Package", default="generated")
-@click.option("--output", type=outputs, help="Output Format", default="dataclasses")
 @click.option(
+    "-c",
+    "--config",
+    default=".xsdata.xml",
+    help="Specify a configuration file with advanced options.",
+)
+@click.option(
+    "-p",
+    "--package",
+    required=False,
+    help=(
+        "Specify the target package to be created inside the current working directory "
+        "Default: generated"
+        "\n\n"
+        "The generated module structure relies on the common input source path"
+        "\n\n"
+        "Use the --ns-struct option for a more flat structure and to avoid circular "
+        "import errors."
+    ),
+    default="generated",
+)
+@click.option(
+    "-o",
+    "--output",
+    type=outputs,
+    help=(
+        "Specify the output format from the builtin code generator and any third "
+        "party installed plugins. Default: dataclasses"
+    ),
+    default="dataclasses",
+)
+@click.option(
+    "-cf",
     "--compound-fields",
     type=click.BOOL,
     default=False,
     help=(
-        "Use compound fields for repeating choices. "
-        "Enable if elements ordering matters for your case."
+        "Use compound fields for repeating choices in order to maintain elements "
+        "ordering between data binding operations."
     ),
 )
 @click.option(
+    "-ds",
     "--docstring-style",
     type=docstring_styles,
-    help="Docstring Style",
+    help=(
+        "Specify the docstring style for the default output format. "
+        "Default: reStructuredText"
+    ),
     default="reStructuredText",
 )
-@click.option("--print", is_flag=True, default=False, help="Print output")
 @click.option(
+    "-pp",
+    "--print",
+    is_flag=True,
+    default=False,
+    help="Print to console instead of writing the generated output to files",
+)
+@click.option(
+    "-ns",
     "--ns-struct",
     is_flag=True,
     default=False,
     help=(
-        "Use namespaces to group classes in the same module. "
+        "Use namespaces to group classes in modules. "
         "Useful against circular import errors."
     ),
 )
 def generate(**kwargs: Any):
     """
-    Convert schema definitions to code.
+    Generate code from xml schemas, webservice definitions and any xml
+    document.
 
-    SOURCE can be either a filepath, directory or url
+    The input source can be either a filepath, uri or a directory
+    containing xml, xsd, wsdl files.
     """
     if kwargs["print"]:
         logger.setLevel(logging.ERROR)
diff --git a/xsdata/formats/converter.py b/xsdata/formats/converter.py
index a73bcafb..8f94d745 100644
--- a/xsdata/formats/converter.py
+++ b/xsdata/formats/converter.py
@@ -33,9 +33,8 @@ from xsdata.models.datatype import XmlDuration
 from xsdata.models.datatype import XmlHexBinary
 from xsdata.models.datatype import XmlPeriod
 from xsdata.models.datatype import XmlTime
+from xsdata.utils import namespaces
 from xsdata.utils import text
-from xsdata.utils.namespaces import load_prefix
-from xsdata.utils.namespaces import split_qname
 
 
 class Converter(metaclass=abc.ABCMeta):
@@ -334,9 +333,6 @@ class QNameConverter(Converter):
         self.validate_input_type(value, str)
         namespace, tag = self.resolve(value, ns_map)
 
-        if " " in tag:
-            raise ConverterError()
-
         return QName(namespace, tag) if namespace else QName(tag)
 
     def serialize(
@@ -355,12 +351,12 @@ class QNameConverter(Converter):
         if ns_map is None:
             return value.text
 
-        namespace, tag = split_qname(value.text)
+        namespace, tag = namespaces.split_qname(value.text)
 
         if not namespace:
             return tag
 
-        prefix = load_prefix(namespace, ns_map)
+        prefix = namespaces.load_prefix(namespace, ns_map)
 
         return f"{prefix}:{tag}" if prefix else tag
 
@@ -368,15 +364,24 @@ class QNameConverter(Converter):
     def resolve(value: str, ns_map: Optional[Dict]) -> Tuple:
         value = value.strip()
 
-        if not value or value[0] == "{":
+        if not value:
             raise ConverterError()
 
-        prefix, name = text.split(value, ":")
-        namespace = ns_map.get(prefix) if ns_map else None
-        if prefix and not namespace:
-            raise ConverterError(f"Unknown namespace prefix: `{prefix}`")
+        if value[0] == "{":
+            uri, name = text.split(value[1:], "}")
+
+            if not namespaces.is_uri(uri):
+                raise ConverterError()
+        else:
+            prefix, name = text.split(value, ":")
+            uri = ns_map.get(prefix) if ns_map else None
+            if prefix and not uri:
+                raise ConverterError(f"Unknown namespace prefix: `{prefix}`")
+
+        if " " in name or not namespaces.is_ncname(name):
+            raise ConverterError()
 
-        return namespace, name
+        return uri, name
 
 
 Array = Union[List, Tuple]
diff --git a/xsdata/utils/namespaces.py b/xsdata/utils/namespaces.py
index a10159e2..292f7822 100644
--- a/xsdata/utils/namespaces.py
+++ b/xsdata/utils/namespaces.py
@@ -1,4 +1,5 @@
 import functools
+import re
 from typing import Dict
 from typing import Optional
 from typing import Tuple
@@ -6,9 +7,14 @@ from typing import Tuple
 from xsdata.models.enums import Namespace
 from xsdata.utils import text
 
-
 __uri_ignore__ = ("www", "xsd", "wsdl")
 
+URI_REGEX = re.compile(
+    r"^(([a-zA-Z][0-9a-zA-Z+\\-\\.]*:)?"
+    r"/{0,2}[0-9a-zA-Z;/?:@&=+$\\.\\-_!~*'()%]+)?"
+    r"(#[0-9a-zA-Z;/?:@&=+$\\.\\-_!~*'()%]+)?$"
+)
+
 
 def load_prefix(uri: str, ns_map: Dict) -> Optional[str]:
     """Get or create a prefix for the given uri in the prefix-URI namespace
@@ -101,3 +107,31 @@ def target_uri(tag: str) -> Optional[str]:
 
 def local_name(tag: str) -> str:
     return split_qname(tag)[1]
+
+
+NCNAME_PUNCTUATION = {"\u00B7", "\u0387", ".", "-", "_"}
+
+
+def is_ncname(name: Optional[str]) -> bool:
+    """Verify given string is a valid ncname."""
+    if not name:
+        return False
+
+    char = name[0]
+    if not char.isalpha() and not char == "_":
+        return False
+
+    for char in name[1:]:
+
+        if char.isalpha() or char.isdigit() or char in NCNAME_PUNCTUATION:
+            continue
+        else:
+            return False
+
+    return True
+
+
+def is_uri(uri: Optional[str]) -> bool:
+    """Verify given string is a valid uri."""
+
+    return bool(URI_REGEX.search(uri)) if uri else False


Test patch:
diff --git a/tests/formats/dataclass/parsers/test_json.py b/tests/formats/dataclass/parsers/test_json.py
index 6bcfff75..9c04b309 100644
--- a/tests/formats/dataclass/parsers/test_json.py
+++ b/tests/formats/dataclass/parsers/test_json.py
@@ -1,5 +1,6 @@
 from dataclasses import make_dataclass
 from unittest.case import TestCase
+from xml.etree.ElementTree import QName
 
 from tests import fixtures_dir
 from tests.fixtures.books import BookForm
@@ -124,12 +125,24 @@ class JsonParserTests(TestCase):
                 ),
                 XmlVar(element=True, qname="generic", name="generic", dataclass=True),
                 XmlVar(element=True, qname="float", name="float", types=[float]),
+                XmlVar(element=True, qname="qname", name="qname", types=[QName]),
             ],
         )
         self.assertEqual(1.0, self.parser.bind_choice("1.0", var))
         self.assertEqual(1, self.parser.bind_choice(1, var))
         self.assertEqual([1], self.parser.bind_choice(["1"], var))
-        self.assertEqual("a", self.parser.bind_choice("a", var))
+
+        actual = self.parser.bind_choice("a", var)
+        self.assertEqual(QName("a"), actual)
+        self.assertIsInstance(actual, QName)
+
+        actual = self.parser.bind_choice("{a}b", var)
+        self.assertIsInstance(actual, QName)
+        self.assertEqual(QName("{a}b"), actual)
+
+        actual = self.parser.bind_choice("!NotQName", var)
+        self.assertIsInstance(actual, str)
+        self.assertEqual("!NotQName", actual)
 
     def test_bind_choice_dataclass(self):
         a = make_dataclass("a", [("x", int)])
diff --git a/tests/formats/test_converter.py b/tests/formats/test_converter.py
index f3b737a2..d529cf15 100644
--- a/tests/formats/test_converter.py
+++ b/tests/formats/test_converter.py
@@ -302,23 +302,13 @@ class QNameConverterTests(TestCase):
     def test_deserialize(self):
         convert = self.converter.deserialize
 
-        with self.assertRaises(ConverterError) as cm:
-            convert("a:b")
-        self.assertEqual("Unknown namespace prefix: `a`", str(cm.exception))
-
-        with self.assertRaises(ConverterError) as cm:
-            convert("a:b", ns_map={})
-        self.assertEqual("Unknown namespace prefix: `a`", str(cm.exception))
-
-        with self.assertRaises(ConverterError):
-            convert("", ns_map={})
-
-        with self.assertRaises(ConverterError):
-            convert("{a}b", ns_map={})
+        invalid = ["a:b", "", " ", "{a}1b", "{a} b", "{|}b", "a a"]
 
-        with self.assertRaises(ConverterError):
-            convert("a a")
+        for inv in invalid:
+            with self.assertRaises(ConverterError):
+                convert(inv)
 
+        self.assertEqual(QName("{a}b"), convert("{a}b"))
         self.assertEqual(QName("a"), convert("a", ns_map={}))
         self.assertEqual(QName("aa", "b"), convert("a:b", ns_map={"a": "aa"}))
 
diff --git a/tests/utils/test_namespaces.py b/tests/utils/test_namespaces.py
index 75d9b2c2..eda1d8fc 100644
--- a/tests/utils/test_namespaces.py
+++ b/tests/utils/test_namespaces.py
@@ -6,6 +6,8 @@ from xsdata.utils.namespaces import build_qname
 from xsdata.utils.namespaces import clean_prefixes
 from xsdata.utils.namespaces import clean_uri
 from xsdata.utils.namespaces import generate_prefix
+from xsdata.utils.namespaces import is_ncname
+from xsdata.utils.namespaces import is_uri
 from xsdata.utils.namespaces import load_prefix
 from xsdata.utils.namespaces import prefix_exists
 from xsdata.utils.namespaces import split_qname
@@ -88,3 +90,17 @@ class NamespacesTests(TestCase):
 
         with self.assertRaises(IndexError):
             split_qname("")
+
+    def test_is_ncname(self):
+        self.assertFalse(is_ncname(""))
+        self.assertFalse(is_ncname(":abc123"))
+        self.assertFalse(is_ncname("9abc123"))
+        self.assertFalse(is_ncname("abc:123"))
+        self.assertTrue(is_ncname("a_1.2-b3"))
+
+    def test_is_uri(self):
+        self.assertFalse(is_uri("|"))
+        self.assertFalse(is_uri(""))
+        self.assertTrue(is_uri("a"))
+        self.assertTrue(is_uri("urn:books"))
+        self.assertTrue(is_uri(Namespace.XS.uri))