Repository: tefra/xsdata

Issue:
JsonParser failes on union fields with dataclasses and primitive types
It's trying to parse all types as dataclasses 

Discussion:
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=370&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=370&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=370&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=370&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=370&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=370&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=370&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=370&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=370&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=370&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=370&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=370&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=370) 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=370&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=370&metric=new_duplicated_lines_density&view=list)


codecov[bot]: # [Codecov](https://codecov.io/gh/tefra/xsdata/pull/370?src=pr&el=h1) Report
> Merging [#370](https://codecov.io/gh/tefra/xsdata/pull/370?src=pr&el=desc) (66b9fa3) into [master](https://codecov.io/gh/tefra/xsdata/commit/c31e7c4951671c0bc135cd831bdf39fe34f7a36a?el=desc) (c31e7c4) will **decrease** coverage by `0.03%`.
> The diff coverage is `83.33%`.

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

```diff
@@             Coverage Diff             @@
##            master     #370      +/-   ##
===========================================
- Coverage   100.00%   99.96%   -0.04%     
===========================================
  Files           70       70              
  Lines         5671     5641      -30     
  Branches       997     1000       +3     
===========================================
- Hits          5671     5639      -32     
- Misses           0        1       +1     
- Partials         0        1       +1     
```


| [Impacted Files](https://codecov.io/gh/tefra/xsdata/pull/370?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [xsdata/formats/dataclass/parsers/json.py](https://codecov.io/gh/tefra/xsdata/pull/370/diff?src=pr&el=tree#diff-eHNkYXRhL2Zvcm1hdHMvZGF0YWNsYXNzL3BhcnNlcnMvanNvbi5weQ==) | `98.34% <83.33%> (-1.66%)` | :arrow_down: |
| [xsdata/cli.py](https://codecov.io/gh/tefra/xsdata/pull/370/diff?src=pr&el=tree#diff-eHNkYXRhL2NsaS5weQ==) | `100.00% <0.00%> (ø)` | |
| [xsdata/models/xsd.py](https://codecov.io/gh/tefra/xsdata/pull/370/diff?src=pr&el=tree#diff-eHNkYXRhL21vZGVscy94c2QucHk=) | `100.00% <0.00%> (ø)` | |
| [xsdata/models/wsdl.py](https://codecov.io/gh/tefra/xsdata/pull/370/diff?src=pr&el=tree#diff-eHNkYXRhL21vZGVscy93c2RsLnB5) | `100.00% <0.00%> (ø)` | |
| [xsdata/models/config.py](https://codecov.io/gh/tefra/xsdata/pull/370/diff?src=pr&el=tree#diff-eHNkYXRhL21vZGVscy9jb25maWcucHk=) | `100.00% <0.00%> (ø)` | |
| [xsdata/models/mixins.py](https://codecov.io/gh/tefra/xsdata/pull/370/diff?src=pr&el=tree#diff-eHNkYXRhL21vZGVscy9taXhpbnMucHk=) | `100.00% <0.00%> (ø)` | |
| [xsdata/codegen/models.py](https://codecov.io/gh/tefra/xsdata/pull/370/diff?src=pr&el=tree#diff-eHNkYXRhL2NvZGVnZW4vbW9kZWxzLnB5) | `100.00% <0.00%> (ø)` | |
| [xsdata/codegen/writer.py](https://codecov.io/gh/tefra/xsdata/pull/370/diff?src=pr&el=tree#diff-eHNkYXRhL2NvZGVnZW4vd3JpdGVyLnB5) | `100.00% <0.00%> (ø)` | |
| [xsdata/formats/mixins.py](https://codecov.io/gh/tefra/xsdata/pull/370/diff?src=pr&el=tree#diff-eHNkYXRhL2Zvcm1hdHMvbWl4aW5zLnB5) | `100.00% <0.00%> (ø)` | |
| [xsdata/codegen/resolver.py](https://codecov.io/gh/tefra/xsdata/pull/370/diff?src=pr&el=tree#diff-eHNkYXRhL2NvZGVnZW4vcmVzb2x2ZXIucHk=) | `100.00% <0.00%> (ø)` | |
| ... and [14 more](https://codecov.io/gh/tefra/xsdata/pull/370/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/tefra/xsdata/pull/370?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/370?src=pr&el=footer). Last update [c31e7c4...66b9fa3](https://codecov.io/gh/tefra/xsdata/pull/370?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=370&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=370&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=370&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=370&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=370&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=370&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=370&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=370&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=370&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=370&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=370&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=370&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=370) 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=370&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=370&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/xsdata/formats/dataclass/parsers/json.py b/xsdata/formats/dataclass/parsers/json.py
index cdfa3d7c..bb27550d 100644
--- a/xsdata/formats/dataclass/parsers/json.py
+++ b/xsdata/formats/dataclass/parsers/json.py
@@ -5,8 +5,10 @@ import warnings
 from dataclasses import dataclass
 from dataclasses import field
 from dataclasses import fields
+from dataclasses import is_dataclass
 from typing import Any
 from typing import Dict
+from typing import List
 from typing import Optional
 from typing import Type
 
@@ -56,7 +58,10 @@ class JsonParser(AbstractParser):
             return dict(value)
 
         if var.is_clazz_union:
-            return self.bind_dataclass_union(value, var)
+            if isinstance(value, dict):
+                return self.bind_dataclass_union(value, var)
+
+            return self.bind_type_union(value, var)
 
         if var.clazz:
             return self.bind_dataclass(value, var.clazz)
@@ -67,7 +72,7 @@ class JsonParser(AbstractParser):
         if var.elements:
             return self.bind_choice(value, var)
 
-        return self.parse_value(value, var)
+        return self.parse_value(value, var.types, var.default, var.tokens)
 
     def bind_dataclass(self, data: Dict, clazz: Type[T]) -> T:
         """Recursively build the given model from the input dict data."""
@@ -93,6 +98,9 @@ class JsonParser(AbstractParser):
         obj = None
         max_score = -1.0
         for clazz in var.types:
+            if not is_dataclass(clazz):
+                continue
+
             candidate = self.bind_dataclass(value, clazz)
             score = ParserUtils.score_object(candidate)
             if score > max_score:
@@ -101,6 +109,10 @@ class JsonParser(AbstractParser):
 
         return obj
 
+    def bind_type_union(self, value: Any, var: XmlVar) -> Any:
+        types = [tp for tp in var.types if not is_dataclass(tp)]
+        return self.parse_value(value, types, var.default, var.tokens)
+
     def bind_wildcard(self, value: Any) -> Any:
         """Bind data to a wildcard model."""
         if isinstance(value, Dict):
@@ -165,10 +177,12 @@ class JsonParser(AbstractParser):
         raise ParserError(f"XmlElements undefined choice: `{var.name}` for `{value}`")
 
     @classmethod
-    def parse_value(cls, value: Any, var: XmlVar) -> Any:
+    def parse_value(
+        cls, value: Any, types: List[Type], default: Any, tokens: bool
+    ) -> Any:
         """Convert any value to one of the given var types."""
         return ParserUtils.parse_value(
-            value, var.types, var.default, ns_map=EMPTY_MAP, tokens=var.tokens
+            value, types, default, ns_map=EMPTY_MAP, tokens=tokens
         )
 
 


Test patch:
diff --git a/tests/formats/dataclass/parsers/test_json.py b/tests/formats/dataclass/parsers/test_json.py
index f3ce19ab..da82f838 100644
--- a/tests/formats/dataclass/parsers/test_json.py
+++ b/tests/formats/dataclass/parsers/test_json.py
@@ -81,7 +81,11 @@ class JsonParserTests(TestCase):
         b = make_dataclass("b", [("x", int), ("y", str), ("z", float)])
         c = make_dataclass("c", [("x", int)])
         var = XmlVar(
-            element=True, name="union", qname="union", types=[a, b, c], dataclass=True
+            element=True,
+            name="union",
+            qname="union",
+            types=[a, b, c, int],
+            dataclass=True,
         )
 
         data = {"x": 1, "y": "foo", "z": 1.0}
@@ -89,6 +93,19 @@ class JsonParserTests(TestCase):
 
         self.assertIsInstance(actual, b)
 
+    def test_bind_type_union(self):
+        a = make_dataclass("a", [("x", int), ("y", str)])
+        var = XmlVar(
+            element=True,
+            name="union",
+            qname="union",
+            types=[a, int, float],
+            dataclass=True,
+        )
+
+        data = "1.1"
+        self.assertEqual(1.1, self.parser.bind_value(var, data))
+
     def test_bind_choice_simple(self):
         var = XmlVar(
             elements=True,