Repository: tefra/xsdata
Pull request #294 (2020-10-11 20:19:44)

ElementNode: validate xs:nill for child node
w3c test case: `test_qfe1700c2_qfe1700c2_v`

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


codecov[bot]: # [Codecov](https://codecov.io/gh/tefra/xsdata/pull/294?src=pr&el=h1) Report
> Merging [#294](https://codecov.io/gh/tefra/xsdata/pull/294?src=pr&el=desc) into [master](https://codecov.io/gh/tefra/xsdata/commit/9a8483008ca7b3daccf83be41ba7fd806cb442bf?el=desc) will **decrease** coverage by `0.01%`.
> The diff coverage is `92.30%`.

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

```diff
@@             Coverage Diff             @@
##            master     #294      +/-   ##
===========================================
- Coverage   100.00%   99.98%   -0.02%     
===========================================
  Files           69       69              
  Lines         5284     5248      -36     
  Branches       904      905       +1     
===========================================
- Hits          5284     5247      -37     
- Partials         0        1       +1     
```


| [Impacted Files](https://codecov.io/gh/tefra/xsdata/pull/294?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [xsdata/formats/dataclass/parsers/nodes.py](https://codecov.io/gh/tefra/xsdata/pull/294/diff?src=pr&el=tree#diff-eHNkYXRhL2Zvcm1hdHMvZGF0YWNsYXNzL3BhcnNlcnMvbm9kZXMucHk=) | `99.51% <90.90%> (-0.49%)` | :arrow_down: |
| [xsdata/formats/dataclass/context.py](https://codecov.io/gh/tefra/xsdata/pull/294/diff?src=pr&el=tree#diff-eHNkYXRhL2Zvcm1hdHMvZGF0YWNsYXNzL2NvbnRleHQucHk=) | `100.00% <100.00%> (ø)` | |
| [xsdata/formats/mixins.py](https://codecov.io/gh/tefra/xsdata/pull/294/diff?src=pr&el=tree#diff-eHNkYXRhL2Zvcm1hdHMvbWl4aW5zLnB5) | `100.00% <100.00%> (ø)` | |
| [xsdata/cli.py](https://codecov.io/gh/tefra/xsdata/pull/294/diff?src=pr&el=tree#diff-eHNkYXRhL2NsaS5weQ==) | `100.00% <0.00%> (ø)` | |
| [xsdata/models/xsd.py](https://codecov.io/gh/tefra/xsdata/pull/294/diff?src=pr&el=tree#diff-eHNkYXRhL21vZGVscy94c2QucHk=) | `100.00% <0.00%> (ø)` | |
| [xsdata/models/wsdl.py](https://codecov.io/gh/tefra/xsdata/pull/294/diff?src=pr&el=tree#diff-eHNkYXRhL21vZGVscy93c2RsLnB5) | `100.00% <0.00%> (ø)` | |
| [xsdata/models/config.py](https://codecov.io/gh/tefra/xsdata/pull/294/diff?src=pr&el=tree#diff-eHNkYXRhL21vZGVscy9jb25maWcucHk=) | `100.00% <0.00%> (ø)` | |
| [xsdata/models/mixins.py](https://codecov.io/gh/tefra/xsdata/pull/294/diff?src=pr&el=tree#diff-eHNkYXRhL21vZGVscy9taXhpbnMucHk=) | `100.00% <0.00%> (ø)` | |
| [xsdata/codegen/models.py](https://codecov.io/gh/tefra/xsdata/pull/294/diff?src=pr&el=tree#diff-eHNkYXRhL2NvZGVnZW4vbW9kZWxzLnB5) | `100.00% <0.00%> (ø)` | |
| [xsdata/codegen/writer.py](https://codecov.io/gh/tefra/xsdata/pull/294/diff?src=pr&el=tree#diff-eHNkYXRhL2NvZGVnZW4vd3JpdGVyLnB5) | `100.00% <0.00%> (ø)` | |
| ... and [15 more](https://codecov.io/gh/tefra/xsdata/pull/294/diff?src=pr&el=tree-more) | |

------

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



Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 3d6487cc..9a103b42 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -37,12 +37,12 @@ repos:
       - id: docformatter
         args: ["--in-place", "--pre-summary-newline"]
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v0.782
+    rev: v0.790
     hooks:
       - id: mypy
         additional_dependencies: [tokenize-rt]
   - repo: https://github.com/asottile/setup-cfg-fmt
-    rev: v1.11.0
+    rev: v1.14.0
     hooks:
       - id: setup-cfg-fmt
         args: ["--max-py-version=3.9"]
diff --git a/xsdata/formats/dataclass/context.py b/xsdata/formats/dataclass/context.py
index dd455953..2fdaa2c5 100644
--- a/xsdata/formats/dataclass/context.py
+++ b/xsdata/formats/dataclass/context.py
@@ -225,9 +225,7 @@ class XmlContext:
             ):
                 type_hint = type_hint.__args__[0]
 
-            types = [
-                x for x in type_hint.__args__ if x is not None.__class__  # type: ignore
-            ]
+            types = [x for x in type_hint.__args__ if x is not None.__class__]
         else:
             types.append(type_hint)
 
diff --git a/xsdata/formats/dataclass/parsers/nodes.py b/xsdata/formats/dataclass/parsers/nodes.py
index 7254f3a5..5073ad65 100644
--- a/xsdata/formats/dataclass/parsers/nodes.py
+++ b/xsdata/formats/dataclass/parsers/nodes.py
@@ -6,6 +6,7 @@ from typing import Any
 from typing import ClassVar
 from typing import Dict
 from typing import Iterable
+from typing import Iterator
 from typing import List
 from typing import Optional
 from typing import Tuple
@@ -108,12 +109,18 @@ class ElementNode(XmlNode):
         :raises: ParserError if the element is unknown and parser config is strict.
         """
 
-        var = self.find_var(qname)
-        if not var:
-            if self.config.fail_on_unknown_properties:
-                raise ParserError(f"Unknown property {self.meta.qname}:{qname}")
-            return SkipNode()
+        for var in self.fetch_vars(qname):
+            node = self.build_node(var, attrs, ns_map, position)
+            if node:
+                return node
 
+        if self.config.fail_on_unknown_properties:
+            raise ParserError(f"Unknown property {self.meta.qname}:{qname}")
+        return SkipNode()
+
+    def build_node(
+        self, var: XmlVar, attrs: Dict, ns_map: Dict, position: int
+    ) -> Optional[XmlNode]:
         if var.is_clazz_union:
             return UnionNode(
                 var=var,
@@ -125,9 +132,13 @@ class ElementNode(XmlNode):
 
         if var.clazz:
             xsi_type = ParserUtils.parse_xsi_type(attrs, ns_map)
+            is_nillable = ParserUtils.is_nillable(attrs)
             meta = self.context.fetch(var.clazz, self.meta.namespace, xsi_type)
             mixed = self.meta.find_var(mode=FindMode.MIXED_CONTENT)
 
+            if not is_nillable and meta.nillable:
+                return None
+
             return ElementNode(
                 meta=meta,
                 config=self.config,
@@ -143,13 +154,11 @@ class ElementNode(XmlNode):
 
         return PrimitiveNode(var=var, ns_map=ns_map)
 
-    def find_var(self, qname: str) -> Optional[XmlVar]:
+    def fetch_vars(self, qname: str) -> Iterator[XmlVar]:
         for mode in self.FIND_MODES_ORDERED:
             var = self.meta.find_var(qname, mode)
             if var:
-                return var
-
-        return None
+                yield var
 
 
 @dataclass
diff --git a/xsdata/formats/dataclass/parsers/utils.py b/xsdata/formats/dataclass/parsers/utils.py
index 3c4ea9c7..0675c75a 100644
--- a/xsdata/formats/dataclass/parsers/utils.py
+++ b/xsdata/formats/dataclass/parsers/utils.py
@@ -28,6 +28,11 @@ class ParserUtils:
 
         return None
 
+    @classmethod
+    def is_nillable(cls, attrs: Dict) -> bool:
+        """Return whether the element attrs has xsi:nil="false"."""
+        return attrs.get(QNames.XSI_NIL) != "false"
+
     @classmethod
     def parse_value(
         cls,


Test patch:
diff --git a/tests/formats/dataclass/parsers/test_nodes.py b/tests/formats/dataclass/parsers/test_nodes.py
index a1fea466..b787c877 100644
--- a/tests/formats/dataclass/parsers/test_nodes.py
+++ b/tests/formats/dataclass/parsers/test_nodes.py
@@ -1,7 +1,9 @@
 from dataclasses import dataclass
 from dataclasses import field
 from dataclasses import make_dataclass
+from dataclasses import replace
 from typing import Any
+from typing import Generator
 from typing import List
 from typing import Union
 from unittest import mock
@@ -230,7 +232,6 @@ class ElementNodeTests(TestCase):
         mock_bind_wildcard_element,
         mock_bind_mixed_content,
     ):
-
         mock_bind_element_attrs.side_effect = add_attr
         mock_bind_wildcard_element.side_effect = add_text
         mock_bind_mixed_content.side_effect = add_content
@@ -260,13 +261,63 @@ class ElementNodeTests(TestCase):
             mock.ANY, node.meta.vars[2], 0, objects
         )
 
+    def test_fetch_vars(self):
+        elem = XmlElement(name="a", qname="a", types=[Foo], dataclass=True)
+        wild = XmlWildcard(name="a", qname="a", types=[Foo], dataclass=True)
+        self.meta.vars.extend((wild, elem))
+
+        matching_vars = self.node.fetch_vars("a")
+        self.assertIsInstance(matching_vars, Generator)
+        self.assertEqual([elem, wild], list(matching_vars))
+
+    @mock.patch.object(ElementNode, "fetch_vars")
+    def test_child(self, mock_match_vars):
+        var = XmlElement(name="a", qname="a", types=[Foo], dataclass=True)
+        attrs = {"a": "b"}
+        ns_map = {"ns0": "xsdata"}
+        position = 1
+        mock_match_vars.return_value = [var]
+
+        actual = self.node.child("a", attrs, ns_map, position)
+        self.assertIsInstance(actual, ElementNode)
+        self.assertEqual(attrs, actual.attrs)
+        self.assertEqual(ns_map, actual.ns_map)
+        self.assertEqual(position, actual.position)
+
+    @mock.patch.object(ElementNode, "build_node")
+    def test_child_when_failed_to_build_next_node(self, mock_build_node):
+        mock_build_node.return_value = None
+        self.meta.vars.append(XmlElement(name="a", qname="a"))
+        self.meta.vars.append(XmlWildcard(name="a", qname="a"))
+
+        with self.assertRaises(ParserError) as cm:
+            self.node.child("a", {}, {}, 0)
+
+        self.assertEqual("Unknown property foo:a", str(cm.exception))
+
+        self.node.config.fail_on_unknown_properties = False
+
+        actual = self.node.child("foobar", {}, {}, 0)
+        self.assertIsInstance(actual, SkipNode)
+
+    def test_build_node_with_dataclass_union_var(self):
+        var = XmlElement(name="a", qname="a", types=[Foo, FooMixed], dataclass=True)
+        attrs = {"a": "b"}
+        ns_map = {"ns0": "xsdata"}
+        actual = self.node.build_node(var, attrs, ns_map, 10)
+
+        self.assertIsInstance(actual, UnionNode)
+        self.assertEqual(10, actual.position)
+        self.assertIs(var, actual.var)
+        self.assertEqual(attrs, actual.attrs)
+        self.assertEqual(ns_map, actual.ns_map)
+        self.assertEqual(0, actual.level)
+        self.assertEqual(0, len(actual.events))
+
     @mock.patch.object(ParserUtils, "parse_xsi_type", return_value="foo")
     @mock.patch.object(XmlContext, "fetch")
-    def test_child_when_given_qname_matches_dataclass_var(
-        self, mock_ctx_fetch, mock_parse_xsi_type
-    ):
+    def test_build_node_with_dataclass_var(self, mock_ctx_fetch, mock_parse_xsi_type):
         var = XmlElement(name="a", qname="a", types=[Foo], dataclass=True)
-        self.meta.vars.append(var)
         xsi_type = "foo"
         namespace = self.meta.namespace
         mock_ctx_fetch.return_value = self.meta
@@ -274,7 +325,7 @@ class ElementNodeTests(TestCase):
 
         attrs = {"a": "b"}
         ns_map = {"ns0": "xsdata"}
-        actual = self.node.child("a", attrs, ns_map, 10)
+        actual = self.node.build_node(var, attrs, ns_map, 10)
 
         self.assertIsInstance(actual, ElementNode)
         self.assertEqual(10, actual.position)
@@ -283,53 +334,41 @@ class ElementNodeTests(TestCase):
         mock_parse_xsi_type.assert_called_once_with(attrs, ns_map)
         mock_ctx_fetch.assert_called_once_with(var.clazz, namespace, xsi_type)
 
-    def test_child_when_given_qname_matches_var_clazz_union(self):
-        var = XmlElement(name="a", qname="a", types=[Foo, FooMixed], dataclass=True)
-        self.meta.vars.append(var)
+    @mock.patch.object(XmlContext, "fetch")
+    def test_build_node_with_dataclass_var_validates_nillable(self, mock_ctx_fetch):
+        var = XmlElement(name="a", qname="a", types=[Foo], dataclass=True)
+        ns_map = {}
+        nillable_meta = replace(self.meta, nillable=True)
+        mock_ctx_fetch.side_effect = [self.meta, self.meta, nillable_meta]
+        attrs = {QNames.XSI_NIL: "false"}
 
-        attrs = {"a": "b"}
-        ns_map = {"ns0": "xsdata"}
-        actual = self.node.child("a", attrs, ns_map, 10)
+        self.assertIsNotNone(self.node.build_node(var, attrs, ns_map, 10))
 
-        self.assertIsInstance(actual, UnionNode)
-        self.assertEqual(10, actual.position)
-        self.assertIs(var, actual.var)
-        self.assertEqual(attrs, actual.attrs)
-        self.assertEqual(ns_map, actual.ns_map)
-        self.assertEqual(0, actual.level)
-        self.assertEqual(0, len(actual.events))
+        attrs = {QNames.XSI_NIL: "true"}
+        self.assertIsNotNone(self.node.build_node(var, attrs, ns_map, 10))
 
-    def test_child_when_given_qname_matches_any_element_var(self):
+        attrs = {QNames.XSI_NIL: "false"}
+        self.assertIsNone(self.node.build_node(var, attrs, ns_map, 10))
+
+    def test_build_node_with_any_type_var(self):
         var = XmlWildcard(name="a", qname="a", types=[], dataclass=False)
-        self.meta.vars.append(var)
 
-        actual = self.node.child("a", {}, {}, 10)
+        actual = self.node.build_node(var, {}, {}, 10)
 
         self.assertIsInstance(actual, WildcardNode)
         self.assertEqual(10, actual.position)
         self.assertEqual(var, actual.var)
 
-    def test_child_when_given_qname_matches_primitive_var(self):
+    def test_build_node_with_primitive_var(self):
         var = XmlText(name="a", qname="a", types=[int], default=100)
-        self.meta.vars.append(var)
         attrs = {"a": "b"}
         ns_map = {"ns0": "xsdata"}
-        actual = self.node.child("a", attrs, ns_map, 10)
+        actual = self.node.build_node(var, attrs, ns_map, 10)
 
         self.assertIsInstance(actual, PrimitiveNode)
         self.assertEqual(var, actual.var)
         self.assertEqual(ns_map, actual.ns_map)
 
-    def test_child_when_given_qname_does_not_match_any_var(self):
-        with self.assertRaises(ParserError) as cm:
-            self.node.child("unknown", {}, {}, 10)
-
-        self.assertEqual("Unknown property foo:unknown", str(cm.exception))
-
-    def test_child_when_config_fail_on_unknown_properties_is_false(self):
-        self.node.config.fail_on_unknown_properties = False
-        self.assertEqual(SkipNode(), self.node.child("unknown", {}, {}, 10))
-
 
 class WildcardNodeTests(TestCase):
     def test_bind(self):
diff --git a/tests/formats/dataclass/serializers/test_mixins.py b/tests/formats/dataclass/serializers/test_mixins.py
index ad67e701..1b390fa1 100644
--- a/tests/formats/dataclass/serializers/test_mixins.py
+++ b/tests/formats/dataclass/serializers/test_mixins.py
@@ -16,7 +16,7 @@ class XmlEventWriterTests(TestCase):
         output = StringIO()
         self.writer = XmlWriter(output=output)
         self.writer.handler = XMLGenerator(
-            output,  # type: ignore
+            output,
             encoding="UTF-8",
             short_empty_elements=True,
         )