Repository: tefra/xsdata

Issue:
Parsing to AnyElement when this is not needed.
I have a case where I have xml looking like this

`<collection xmlns:update="urn:vpro:media:update:2009" xmlns:media="urn:vpro:media:2009" xmlns:shared="urn:vpro:shared:2009" version="7.6.2">
  <update:location publishStop="2012-01-11T18:16:01.287+01:00" urn="urn:vpro:media:location:126275555">
    <update:programUrl>http://www.vpro.nl/123</update:programUrl>
 </update:location>
</collection>
`
In other words its an xml element with below it any number of 'any elements'. It is however known what they are, because the (known) namespace is specified. It would work if I add the appropriate xsi-attributes.


But the thing is that the actually incoming xml does not do that. It is not needed.

legacy pyxb implementation does it do satisfactory. 
See e.g. https://github.com/npo-poms/pyapi/blob/main/tests/unit/pyxb_xml_test.py#LL83C9-L83C9 (e.g. test_images_collection)

Also jaxb (in java) does do it better:
https://github.com/npo-poms/poms-shared/blob/main/media-domain/src/test/java/nl/vpro/domain/media/update/collections/XmlCollectionTest.java

So, the request/issue is the following. Could xsdata in these cases, where an AnyELement is instantiated, use the qname of the AnyElement and that kind of available information to unmarshal to the known type?

I can't even think up some  acceptable work-around. I get an AnyElement but I really need the proper type.  The only thing which I may do, I think,  is just map it completely manually:
```
if source.qname == "'{urn:vpro:media:update:2009}location":
        location = LocationUpdate()
        location.publishStart =  source.attributes["publishStart"]
        location.publishStop =  source.attributes["publishStop"]
   etc..
```
And that for every attribute and sub elements recursively, which gets very cumbersome.



Discussion:
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=tefra_xsdata&pullRequest=839)

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

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=839) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=839&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=839&metric=new_duplicated_lines_density&view=list)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/tefra/xsdata/pull/839?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) Report
Patch coverage: **`88.46`**% and project coverage change: **`-0.07`** :warning:
> Comparison is base [(`0e24156`)](https://app.codecov.io/gh/tefra/xsdata/commit/0e24156da77a04c2923ab02b9235067fa7a99efa?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) 99.95% compared to head [(`bb5c910`)](https://app.codecov.io/gh/tefra/xsdata/pull/839?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) 99.89%.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #839      +/-   ##
==========================================
- Coverage   99.95%   99.89%   -0.07%     
==========================================
  Files         104      104              
  Lines        9269     9280      +11     
  Branches     2072     2076       +4     
==========================================
+ Hits         9265     9270       +5     
- Misses          0        3       +3     
- Partials        4        7       +3     
```


| [Impacted Files](https://app.codecov.io/gh/tefra/xsdata/pull/839?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) | Coverage Δ | |
|---|---|---|
| [xsdata/formats/dataclass/parsers/tree.py](https://app.codecov.io/gh/tefra/xsdata/pull/839?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL2Zvcm1hdHMvZGF0YWNsYXNzL3BhcnNlcnMvdHJlZS5weQ==) | `100.00% <ø> (ø)` | |
| [xsdata/utils/testing.py](https://app.codecov.io/gh/tefra/xsdata/pull/839?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL3V0aWxzL3Rlc3RpbmcucHk=) | `100.00% <ø> (ø)` | |
| [xsdata/codegen/models.py](https://app.codecov.io/gh/tefra/xsdata/pull/839?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL2NvZGVnZW4vbW9kZWxzLnB5) | `99.43% <33.33%> (-0.57%)` | :arrow_down: |
| [xsdata/formats/dataclass/parsers/nodes/element.py](https://app.codecov.io/gh/tefra/xsdata/pull/839?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL2Zvcm1hdHMvZGF0YWNsYXNzL3BhcnNlcnMvbm9kZXMvZWxlbWVudC5weQ==) | `99.03% <92.30%> (-0.49%)` | :arrow_down: |
| [xsdata/formats/dataclass/models/builders.py](https://app.codecov.io/gh/tefra/xsdata/pull/839?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL2Zvcm1hdHMvZGF0YWNsYXNzL21vZGVscy9idWlsZGVycy5weQ==) | `100.00% <100.00%> (ø)` | |
| [xsdata/formats/dataclass/models/elements.py](https://app.codecov.io/gh/tefra/xsdata/pull/839?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL2Zvcm1hdHMvZGF0YWNsYXNzL21vZGVscy9lbGVtZW50cy5weQ==) | `100.00% <100.00%> (ø)` | |
| [xsdata/formats/dataclass/serializers/xml.py](https://app.codecov.io/gh/tefra/xsdata/pull/839?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL2Zvcm1hdHMvZGF0YWNsYXNzL3NlcmlhbGl6ZXJzL3htbC5weQ==) | `99.57% <100.00%> (+0.01%)` | :arrow_up: |
| [xsdata/models/xsd.py](https://app.codecov.io/gh/tefra/xsdata/pull/839?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL21vZGVscy94c2QucHk=) | `100.00% <100.00%> (ø)` | |

... and [1 file with indirect coverage changes](https://app.codecov.io/gh/tefra/xsdata/pull/839/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou)


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/tefra/xsdata/pull/839?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou).

tefra: Obviously the json tests suffer from this one, but I am wondering if they even make sense any more... xml -> json -> xml roundtrips

Patch:
diff --git a/docs/models.rst b/docs/models.rst
index 49938ffc..76f582cb 100644
--- a/docs/models.rst
+++ b/docs/models.rst
@@ -176,7 +176,10 @@ binding procedures.
    * - format
      - str
      - Format option for types like datetime, or bytes, see :ref:`Data Types`
-
+   * - process_contents
+     - str
+     - Specify the level of validation for wildcard fields:
+       ``strict | skip``, default: ``strict`` will avoid using generics.
 
 The code generator adds also the field restrictions like `minLength` or `required` flag
 but currently they are only used to troubleshoot the code generator.
diff --git a/xsdata/codegen/models.py b/xsdata/codegen/models.py
index 76d7b1b7..a054a653 100644
--- a/xsdata/codegen/models.py
+++ b/xsdata/codegen/models.py
@@ -37,29 +37,8 @@ GLOBAL_TYPES = (Tag.ELEMENT, Tag.BINDING_OPERATION, Tag.BINDING_MESSAGE, Tag.MES
 
 @dataclass
 class Restrictions:
-    """
-    Model representation of a dataclass field validation and type metadata.
-
-    :param min_occurs:
-    :param max_occurs:
-    :param min_exclusive:
-    :param min_inclusive:
-    :param min_length:
-    :param max_exclusive:
-    :param max_inclusive:
-    :param max_length:
-    :param total_digits:
-    :param fraction_digits:
-    :param length:
-    :param white_space:
-    :param pattern:
-    :param explicit_timezone:
-    :param nillable:
-    :param sequence:
-    :param tokens:
-    :param format:
-    :param choice:
-    """
+    """Model representation of a dataclass field validation and type
+    metadata."""
 
     min_occurs: Optional[int] = field(default=None)
     max_occurs: Optional[int] = field(default=None)
@@ -81,6 +60,7 @@ class Restrictions:
     format: Optional[str] = field(default=None)
     choice: Optional[int] = field(default=None)
     group: Optional[int] = field(default=None)
+    process_contents: Optional[str] = field(default=None)
     path: List[Tuple[str, int, int, int]] = field(default_factory=list)
 
     @property
@@ -128,6 +108,7 @@ class Restrictions:
             "white_space",
             "pattern",
             "explicit_timezone",
+            "process_contents",
         )
 
         for key in keys:
@@ -163,6 +144,9 @@ class Restrictions:
             ):
                 continue
 
+            if key == "process_contents" and value != "skip":
+                continue
+
             if key.endswith("clusive") and types:
                 value = converter.deserialize(value, sorted_types)
 
@@ -188,17 +172,8 @@ class AttrCategory(IntEnum):
 
 @dataclass(unsafe_hash=True)
 class AttrType:
-    """
-    Model representation for the typing information for fields and extensions.
-
-    :param qname:
-    :param alias:
-    :param reference:
-    :param native:
-    :param forward:
-    :param circular:
-    :param substituted:
-    """
+    """Model representation for the typing information for fields and
+    extensions."""
 
     qname: str
     alias: Optional[str] = field(default=None, compare=False)
@@ -232,22 +207,7 @@ class AttrType:
 
 @dataclass
 class Attr:
-    """
-    Model representation for a dataclass field.
-
-    :param tag:
-    :param name:
-    :param local_name:
-    :param index:
-    :param default:
-    :param fixed:
-    :param mixed:
-    :param types:
-    :param choices:
-    :param namespace:
-    :param help:
-    :param restrictions:
-    """
+    """Model representation for a dataclass field."""
 
     tag: str
     name: str = field(compare=False)
@@ -386,13 +346,7 @@ class Attr:
 
 @dataclass(unsafe_hash=True)
 class Extension:
-    """
-    Model representation of a dataclass base class.
-
-    :param tag:
-    :param type:
-    :param restrictions:
-    """
+    """Model representation of a dataclass base class."""
 
     tag: str
     type: AttrType
@@ -423,32 +377,8 @@ class Status(IntEnum):
 
 @dataclass
 class Class:
-    """
-    Model representation of a dataclass with fields, base/inner classes and
-    additional metadata settings.
-
-    :param qname:
-    :param tag:
-    :param location:
-    :param mixed:
-    :param abstract:
-    :param nillable:
-    :param local_type:
-    :param status:
-    :param container:
-    :param package:
-    :param module:
-    :param namespace:
-    :param help:
-    :param meta_name:
-    :param default:
-    :param fixed:
-    :param substitutions:
-    :param extensions:
-    :param attrs:
-    :param inner:
-    :param ns_map:
-    """
+    """Model representation of a dataclass with fields, base/inner classes and
+    additional metadata settings."""
 
     qname: str
     tag: str
diff --git a/xsdata/formats/dataclass/models/builders.py b/xsdata/formats/dataclass/models/builders.py
index 110f814d..e07b277e 100644
--- a/xsdata/formats/dataclass/models/builders.py
+++ b/xsdata/formats/dataclass/models/builders.py
@@ -272,6 +272,7 @@ class XmlVarBuilder:
         namespace = metadata.get("namespace")
         choices = metadata.get("choices", EMPTY_SEQUENCE)
         mixed = metadata.get("mixed", False)
+        process_contents = metadata.get("process_contents", "strict")
         required = metadata.get("required", False)
         nillable = metadata.get("nillable", False)
         format_str = metadata.get("format", None)
@@ -331,6 +332,7 @@ class XmlVarBuilder:
             format=format_str,
             clazz=clazz,
             any_type=any_type,
+            process_contents=process_contents,
             required=required,
             nillable=nillable,
             sequence=sequence,
diff --git a/xsdata/formats/dataclass/models/elements.py b/xsdata/formats/dataclass/models/elements.py
index 3eb03ea0..4ba505f5 100644
--- a/xsdata/formats/dataclass/models/elements.py
+++ b/xsdata/formats/dataclass/models/elements.py
@@ -90,6 +90,7 @@ class XmlVar(MetaMixin):
         "format",
         "derived",
         "any_type",
+        "process_contents",
         "required",
         "nillable",
         "sequence",
@@ -126,6 +127,7 @@ class XmlVar(MetaMixin):
         format: Optional[str],
         derived: bool,
         any_type: bool,
+        process_contents: str,
         required: bool,
         nillable: bool,
         sequence: Optional[int],
@@ -148,6 +150,7 @@ class XmlVar(MetaMixin):
         self.format = format
         self.derived = derived
         self.any_type = any_type
+        self.process_contents = process_contents
         self.required = required
         self.nillable = nillable
         self.sequence = sequence
diff --git a/xsdata/formats/dataclass/parsers/nodes/element.py b/xsdata/formats/dataclass/parsers/nodes/element.py
index d9c97cc3..41bc22ae 100644
--- a/xsdata/formats/dataclass/parsers/nodes/element.py
+++ b/xsdata/formats/dataclass/parsers/nodes/element.py
@@ -17,7 +17,6 @@ from xsdata.formats.dataclass.parsers.utils import ParserUtils
 from xsdata.formats.dataclass.parsers.utils import PendingCollection
 from xsdata.logger import logger
 from xsdata.models.enums import DataType
-from xsdata.utils import namespaces
 
 
 class ElementNode(XmlNode):
@@ -222,8 +221,9 @@ class ElementNode(XmlNode):
                 items.append(value)
         elif var.name in params:
             previous = params[var.name]
-            if previous.qname:
-                factory = self.context.class_type.any_element
+            factory = self.context.class_type.any_element
+
+            if not isinstance(previous, factory) or previous.qname:
                 params[var.name] = factory(children=[previous])
 
             params[var.name].children.append(value)
@@ -248,18 +248,9 @@ class ElementNode(XmlNode):
     ) -> Any:
         """Prepare parsed value before binding to a wildcard field."""
 
-        if qname:
+        if qname and not self.context.class_type.is_model(value):
             any_factory = self.context.class_type.any_element
-            derived_factory = self.context.class_type.derived_element
-
-            if not self.context.class_type.is_model(value):
-                value = any_factory(qname=qname, text=converter.serialize(value))
-            elif not isinstance(
-                value, (any_factory, derived_factory)
-            ) and not var.find_choice(qname):
-                meta = self.context.fetch(type(value))
-                xsi_type = namespaces.real_xsi_type(qname, meta.target_qname)
-                value = derived_factory(qname=qname, value=value, type=xsi_type)
+            value = any_factory(qname=qname, text=converter.serialize(value))
 
         return value
 
@@ -336,7 +327,7 @@ class ElementNode(XmlNode):
         for var in self.meta.find_children(qname):
             unique = 0 if not var.is_element or var.list_element else var.index
             if not unique or unique not in self.assigned:
-                node = self.build_node(var, attrs, ns_map, position)
+                node = self.build_node(qname, var, attrs, ns_map, position)
 
                 if node:
                     if unique:
@@ -350,7 +341,7 @@ class ElementNode(XmlNode):
         return nodes.SkipNode()
 
     def build_node(
-        self, var: XmlVar, attrs: Dict, ns_map: Dict, position: int
+        self, qname: str, var: XmlVar, attrs: Dict, ns_map: Dict, position: int
     ) -> Optional[XmlNode]:
         if var.is_clazz_union:
             return nodes.UnionNode(
@@ -370,6 +361,7 @@ class ElementNode(XmlNode):
             return self.build_element_node(
                 var.clazz,
                 var.derived,
+                var.nillable,
                 attrs,
                 ns_map,
                 position,
@@ -399,6 +391,7 @@ class ElementNode(XmlNode):
             node = self.build_element_node(
                 clazz,
                 derived,
+                var.nillable,
                 attrs,
                 ns_map,
                 position,
@@ -410,6 +403,25 @@ class ElementNode(XmlNode):
         if node:
             return node
 
+        if var.process_contents != "skip":
+            clazz = self.context.find_type(qname)
+
+        if clazz:
+            node = self.build_element_node(
+                clazz,
+                False,
+                var.nillable,
+                attrs,
+                ns_map,
+                position,
+                None,
+                xsi_type,
+                xsi_nil,
+            )
+
+        if node:
+            return node
+
         return nodes.WildcardNode(
             var=var,
             attrs=attrs,
@@ -422,6 +434,7 @@ class ElementNode(XmlNode):
         self,
         clazz: Type,
         derived: bool,
+        nillable: bool,
         attrs: Dict,
         ns_map: Dict,
         position: int,
@@ -430,8 +443,9 @@ class ElementNode(XmlNode):
         xsi_nil: Optional[bool] = None,
     ) -> Optional[XmlNode]:
         meta = self.context.fetch(clazz, self.meta.namespace, xsi_type)
+        nillable = nillable or meta.nillable
 
-        if not meta or (meta.nillable and xsi_nil is False):
+        if not meta or (xsi_nil is not None and nillable != xsi_nil):
             return None
 
         if xsi_type and not derived and not issubclass(meta.clazz, clazz):
diff --git a/xsdata/formats/dataclass/parsers/tree.py b/xsdata/formats/dataclass/parsers/tree.py
index 57ed1adc..ed8fbd32 100644
--- a/xsdata/formats/dataclass/parsers/tree.py
+++ b/xsdata/formats/dataclass/parsers/tree.py
@@ -52,6 +52,7 @@ class TreeParser(NodeParser):
                 format=None,
                 derived=False,
                 any_type=False,
+                process_contents="strict",
                 required=False,
                 nillable=False,
                 sequence=None,
diff --git a/xsdata/formats/dataclass/serializers/xml.py b/xsdata/formats/dataclass/serializers/xml.py
index 58331597..652fc524 100644
--- a/xsdata/formats/dataclass/serializers/xml.py
+++ b/xsdata/formats/dataclass/serializers/xml.py
@@ -130,11 +130,15 @@ class XmlSerializer(AbstractSerializer):
                 yield from self.write_value(value, choice, namespace)
             else:
                 yield from self.write_dataclass(value, namespace)
-        else:
+        elif var.is_element:
             xsi_type = self.xsi_type(var, value, namespace)
             yield from self.write_dataclass(
                 value, namespace, var.qname, var.nillable, xsi_type
             )
+        else:
+            # var elements
+            meta = self.context.fetch(value.__class__, namespace)
+            yield from self.write_dataclass(value, qname=meta.target_qname)
 
     def write_value(self, value: Any, var: XmlVar, namespace: NoneStr) -> Generator:
         """
@@ -251,7 +255,7 @@ class XmlSerializer(AbstractSerializer):
         clazz = var.clazz
         if clazz is None or self.context.is_derived(value, clazz):
             meta = self.context.fetch(value.__class__, namespace)
-            return meta.target_qname
+            return namespaces.real_xsi_type(var.qname, meta.target_qname)
 
         raise SerializerError(
             f"{value.__class__.__name__} is not derived from {clazz.__name__}"
@@ -290,6 +294,10 @@ class XmlSerializer(AbstractSerializer):
             choice = var.find_value_choice(value, check_subclass)
             func = self.write_value
 
+            if not choice and check_subclass:
+                func = self.write_xsi_type
+                choice = var
+
         if not choice:
             raise SerializerError(
                 f"XmlElements undefined choice: `{var.name}` for `{type(value)}`"
diff --git a/xsdata/models/xsd.py b/xsdata/models/xsd.py
index 90f802ad..759cc5d5 100644
--- a/xsdata/models/xsd.py
+++ b/xsdata/models/xsd.py
@@ -389,7 +389,9 @@ class Any(AnnotationBase):
     namespace: str = attribute(default="##any")
     min_occurs: int = attribute(default=1, name="minOccurs")
     max_occurs: UnionType[int, str] = attribute(default=1, name="maxOccurs")
-    process_contents: Optional[ProcessType] = attribute(name="processContents")
+    process_contents: ProcessType = attribute(
+        default=ProcessType.STRICT, name="processContents"
+    )
 
     def __post_init__(self):
         self.namespace = " ".join(unique_sequence(self.namespace.split()))
@@ -417,6 +419,7 @@ class Any(AnnotationBase):
         return {
             "min_occurs": 0,
             "max_occurs": max_occurs,
+            "process_contents": self.process_contents.value,
         }
 
 


Test patch:
diff --git a/tests/codegen/models/test_restrictions.py b/tests/codegen/models/test_restrictions.py
index 7532a47a..00476aab 100644
--- a/tests/codegen/models/test_restrictions.py
+++ b/tests/codegen/models/test_restrictions.py
@@ -23,6 +23,7 @@ class RestrictionsTests(TestCase):
             explicit_timezone="+1",
             nillable=True,
             path=[("s", 0, 1, 1)],
+            process_contents="skip",
         )
 
     def test_property_is_list(self):
@@ -89,6 +90,7 @@ class RestrictionsTests(TestCase):
             "min_length": 1,
             "nillable": True,
             "pattern": "[A-Z]",
+            "process_contents": "skip",
             "total_digits": 333,
             "white_space": "collapse",
         }
@@ -113,6 +115,7 @@ class RestrictionsTests(TestCase):
             "min_length": 1,
             "nillable": True,
             "pattern": "[A-Z]",
+            "process_contents": "skip",
             "total_digits": 333,
             "white_space": "collapse",
         }
diff --git a/tests/formats/dataclass/parsers/nodes/test_element.py b/tests/formats/dataclass/parsers/nodes/test_element.py
index 4a52f63a..878c5e7a 100644
--- a/tests/formats/dataclass/parsers/nodes/test_element.py
+++ b/tests/formats/dataclass/parsers/nodes/test_element.py
@@ -235,15 +235,16 @@ class ElementNodeTests(FactoryTestCase):
 
     def test_bind_wild_var(self):
         self.node.meta = self.context.build(ExtendedType)
+        x = make_dataclass("x", [("value", int)])
 
         params = {}
-        objects = [("x", 1), ("x", 2), ("z", 3.0)]
+        objects = [("x", x(1)), ("y", 2), ("z", 3.0)]
         self.node.bind_objects(params, objects)
         expected = {
             "wildcard": AnyElement(
                 children=[
-                    AnyElement(qname="x", text="1"),
-                    AnyElement(qname="x", text="2"),
+                    x(1),
+                    AnyElement(qname="y", text="2"),
                     AnyElement(qname="z", text="3.0"),
                 ]
             )
@@ -287,20 +288,8 @@ class ElementNodeTests(FactoryTestCase):
 
         fixture = make_dataclass("Fixture", [("content", str)])
         actual = self.node.prepare_generic_value("a", fixture("foo"), var)
-        expected = DerivedElement(qname="a", value=fixture("foo"), type="Fixture")
-        self.assertEqual(expected, actual)
-
-        fixture = make_dataclass("Fixture", [("content", str)])
-        actual = self.node.prepare_generic_value("known", fixture("foo"), var)
         self.assertEqual(fixture("foo"), actual)
 
-        actual = self.node.prepare_generic_value("a", expected, var)
-        self.assertIs(expected, actual)
-
-        actual = self.node.prepare_generic_value("Fixture", fixture("foo"), var)
-        expected = DerivedElement(qname="Fixture", value=fixture("foo"))
-        self.assertEqual(expected, actual)
-
     def test_child(self):
         var = XmlVarFactory.create(xml_type=XmlType.ELEMENT, qname="a", types=(TypeC,))
         attrs = {"a": "b"}
@@ -316,10 +305,10 @@ class ElementNodeTests(FactoryTestCase):
 
     def test_child_with_unique_element(self):
         single = XmlVarFactory.create(
-            index=1, xml_type=XmlType.ELEMENT, qname="a", types=(TypeC,)
+            index=1, xml_type=XmlType.ELEMENT, qname="cc", types=(TypeC,)
         )
         wildcard = XmlVarFactory.create(
-            index=2, xml_type=XmlType.WILDCARD, qname="a", types=(object,)
+            index=2, xml_type=XmlType.WILDCARD, qname="cc", types=(object,)
         )
         self.meta.elements[single.qname] = [single]
         self.meta.wildcards.append(wildcard)
@@ -328,11 +317,11 @@ class ElementNodeTests(FactoryTestCase):
         ns_map = {"ns0": "xsdata"}
         position = 1
 
-        actual = self.node.child("a", attrs, ns_map, position)
+        actual = self.node.child("cc", attrs, ns_map, position)
         self.assertIsInstance(actual, ElementNode)
         self.assertIn(single.index, self.node.assigned)
 
-        actual = self.node.child("a", attrs, ns_map, position)
+        actual = self.node.child("cc", attrs, ns_map, position)
         self.assertIsInstance(actual, WildcardNode)
         self.assertNotIn(wildcard.index, self.node.assigned)
 
@@ -364,7 +353,7 @@ class ElementNodeTests(FactoryTestCase):
         )
         attrs = {"a": "b"}
         ns_map = {"ns0": "xsdata"}
-        actual = self.node.build_node(var, attrs, ns_map, 10)
+        actual = self.node.build_node(var.qname, var, attrs, ns_map, 10)
 
         self.assertIsInstance(actual, UnionNode)
         self.assertEqual(10, actual.position)
@@ -391,7 +380,7 @@ class ElementNodeTests(FactoryTestCase):
 
         attrs = {"a": "b"}
         ns_map = {"ns0": "xsdata"}
-        actual = self.node.build_node(var, attrs, ns_map, 10)
+        actual = self.node.build_node(var.qname, var, attrs, ns_map, 10)
 
         self.assertIsInstance(actual, ElementNode)
         self.assertEqual(10, actual.position)
@@ -420,7 +409,7 @@ class ElementNodeTests(FactoryTestCase):
 
         attrs = {"a": "b"}
         ns_map = {"ns0": "xsdata"}
-        actual = self.node.build_node(var, attrs, ns_map, 10)
+        actual = self.node.build_node(var.qname, var, attrs, ns_map, 10)
 
         self.assertIsInstance(actual, ElementNode)
         self.assertEqual(10, actual.position)
@@ -439,13 +428,13 @@ class ElementNodeTests(FactoryTestCase):
         mock_ctx_fetch.side_effect = [self.meta, self.meta, nillable_meta]
         attrs = {QNames.XSI_NIL: "false"}
 
-        self.assertIsNotNone(self.node.build_node(var, attrs, ns_map, 10))
+        self.assertIsNotNone(self.node.build_node(var.qname, var, attrs, ns_map, 10))
 
         attrs = {QNames.XSI_NIL: "true"}
-        self.assertIsNotNone(self.node.build_node(var, attrs, ns_map, 10))
+        self.assertIsNone(self.node.build_node(var.qname, var, attrs, ns_map, 10))
 
         attrs = {QNames.XSI_NIL: "false"}
-        self.assertIsNone(self.node.build_node(var, attrs, ns_map, 10))
+        self.assertIsNone(self.node.build_node(var.qname, var, attrs, ns_map, 10))
 
     def test_build_node_with_any_type_var_with_matching_xsi_type(self):
         var = XmlVarFactory.create(
@@ -457,7 +446,7 @@ class ElementNodeTests(FactoryTestCase):
         )
         attrs = {QNames.XSI_TYPE: "bk:books"}
         ns_map = {"bk": "urn:books"}
-        actual = self.node.build_node(var, attrs, ns_map, 10)
+        actual = self.node.build_node(var.qname, var, attrs, ns_map, 10)
 
         self.assertIsInstance(actual, ElementNode)
         self.assertEqual(10, actual.position)
@@ -476,7 +465,7 @@ class ElementNodeTests(FactoryTestCase):
         )
         attrs = {QNames.XSI_TYPE: "xs:hexBinary"}
         ns_map = {Namespace.XS.prefix: Namespace.XS.uri}
-        actual = self.node.build_node(var, attrs, ns_map, 10)
+        actual = self.node.build_node(var.qname, var, attrs, ns_map, 10)
 
         self.assertIsInstance(actual, StandardNode)
         self.assertEqual(ns_map, actual.ns_map)
@@ -487,12 +476,12 @@ class ElementNodeTests(FactoryTestCase):
         var = XmlVarFactory.create(
             xml_type=XmlType.ELEMENT,
             name="a",
-            qname="a",
+            qname="aaa",
             types=(object,),
             any_type=True,
         )
         attrs = {QNames.XSI_TYPE: "noMatch"}
-        actual = self.node.build_node(var, attrs, {}, 10)
+        actual = self.node.build_node(var.qname, var, attrs, {}, 10)
 
         self.assertIsInstance(actual, WildcardNode)
         self.assertEqual(10, actual.position)
@@ -504,12 +493,12 @@ class ElementNodeTests(FactoryTestCase):
         var = XmlVarFactory.create(
             xml_type=XmlType.ELEMENT,
             name="a",
-            qname="a",
+            qname="aaaa",
             types=(object,),
             any_type=True,
         )
         attrs = {}
-        actual = self.node.build_node(var, attrs, {}, 10)
+        actual = self.node.build_node(var.qname, var, attrs, {}, 10)
 
         self.assertIsInstance(actual, WildcardNode)
         self.assertEqual(10, actual.position)
@@ -517,10 +506,29 @@ class ElementNodeTests(FactoryTestCase):
         self.assertEqual(attrs, actual.attrs)
         self.assertEqual({}, actual.ns_map)
 
+    def test_build_node_with_any_type_var_with_no_xsi_type_and_type_Exists(self):
+        var = XmlVarFactory.create(
+            xml_type=XmlType.ELEMENT,
+            name="a",
+            qname="a",
+            types=(object,),
+            any_type=True,
+        )
+        attrs = {}
+        a = make_dataclass("a", [("a", int)])
+        actual = self.node.build_node(var.qname, var, attrs, {}, 10)
+
+        self.assertIsInstance(actual, ElementNode)
+        self.assertEqual(10, actual.position)
+        self.assertEqual(self.context.build(a), actual.meta)
+        self.assertEqual(attrs, actual.attrs)
+        self.assertEqual({}, actual.ns_map)
+        self.assertFalse(actual.mixed)
+
     def test_build_node_with_wildcard_var(self):
-        var = XmlVarFactory.create(xml_type=XmlType.WILDCARD, qname="a")
+        var = XmlVarFactory.create(xml_type=XmlType.WILDCARD, qname="aaaaa")
 
-        actual = self.node.build_node(var, {}, {}, 10)
+        actual = self.node.build_node(var.qname, var, {}, {}, 10)
 
         self.assertIsInstance(actual, WildcardNode)
         self.assertEqual(10, actual.position)
@@ -532,7 +540,7 @@ class ElementNodeTests(FactoryTestCase):
         )
         attrs = {"a": "b"}
         ns_map = {"ns0": "xsdata"}
-        actual = self.node.build_node(var, attrs, ns_map, 10)
+        actual = self.node.build_node(var.qname, var, attrs, ns_map, 10)
 
         self.assertIsInstance(actual, PrimitiveNode)
         self.assertEqual(ns_map, actual.ns_map)
diff --git a/tests/formats/dataclass/serializers/test_xml.py b/tests/formats/dataclass/serializers/test_xml.py
index 45af76cd..ee0146ce 100644
--- a/tests/formats/dataclass/serializers/test_xml.py
+++ b/tests/formats/dataclass/serializers/test_xml.py
@@ -534,6 +534,29 @@ class XmlSerializerTests(TestCase):
         self.assertIsInstance(result, Generator)
         self.assertEqual(expected, list(result))
 
+    def test_write_choice_when_no_matching_choice_exists_but_value_is_model(self):
+        var = XmlVarFactory.create(
+            xml_type=XmlType.ELEMENTS,
+            name="compound",
+            qname="compound",
+            elements={
+                "a": XmlVarFactory.create(
+                    xml_type=XmlType.WILDCARD, qname="a", types=(object,)
+                )
+            },
+        )
+
+        ebook = make_dataclass("eBook", [], bases=(BookForm,))
+        expected = [
+            (XmlWriterEvent.START, "eBook"),
+            (XmlWriterEvent.ATTR, "lang", "en"),
+            (XmlWriterEvent.END, "eBook"),
+        ]
+
+        result = self.serializer.write_value(ebook(), var, None)
+        self.assertIsInstance(result, Generator)
+        self.assertEqual(expected, list(result))
+
     def test_write_choice_when_no_matching_choice_exists(self):
         var = XmlVarFactory.create(
             xml_type=XmlType.ELEMENTS,
diff --git a/tests/models/test_config.py b/tests/models/test_config.py
index 742f4a3a..2ed8cece 100644
--- a/tests/models/test_config.py
+++ b/tests/models/test_config.py
@@ -167,7 +167,8 @@ class GeneratorConfigTests(TestCase):
             )
 
         else:
-            self.assertTrue(GeneratorOutput(union_type=True).union_type)
+            output = GeneratorOutput(union_type=True, postponed_annotations=True)
+            self.assertTrue(output.union_type)
 
     def test_format_slots_requires_310(self):
         if sys.version_info < (3, 10):
diff --git a/tests/models/xsd/test_any.py b/tests/models/xsd/test_any.py
index 835c024f..2d625526 100644
--- a/tests/models/xsd/test_any.py
+++ b/tests/models/xsd/test_any.py
@@ -39,4 +39,5 @@ class AnyTests(TestCase):
 
     def test_get_restrictions(self):
         obj = Any(min_occurs=1, max_occurs=2)
-        self.assertEqual({"max_occurs": 2, "min_occurs": 0}, obj.get_restrictions())
+        expected = {"max_occurs": 2, "min_occurs": 0, "process_contents": "strict"}
+        self.assertEqual(expected, obj.get_restrictions())
diff --git a/xsdata/utils/testing.py b/xsdata/utils/testing.py
index 1f6db409..9187009a 100644
--- a/xsdata/utils/testing.py
+++ b/xsdata/utils/testing.py
@@ -421,6 +421,7 @@ class XmlVarFactory(Factory):
             required=required,
             nillable=nillable,
             sequence=sequence,
+            process_contents="strict",
             list_element=list_element,
             default=default,
             xml_type=xml_type,