diff --git a/docs/source/pages/misc.rst b/docs/source/pages/misc.rst
index 7912e87..58925e5 100644
--- a/docs/source/pages/misc.rst
+++ b/docs/source/pages/misc.rst
@@ -71,6 +71,16 @@ The following example illustrate how to serialize ``xs:list`` element:
:language: python
+JSON only field
+~~~~~~~~~~~~~~~
+
+To prevent a field from serialization/deserialization only for xml format
+mark it with :py:class:`pydantic_xml.NoXml` annotation:
+
+.. literalinclude:: ../../../examples/snippets/py3.9/noxml.py
+ :language: python
+
+
Optional type encoding
~~~~~~~~~~~~~~~~~~~~~~
diff --git a/examples/snippets/py3.9/noxml.py b/examples/snippets/py3.9/noxml.py
new file mode 100644
index 0000000..dbd462d
--- /dev/null
+++ b/examples/snippets/py3.9/noxml.py
@@ -0,0 +1,29 @@
+from typing import Annotated, Optional
+from xml.etree.ElementTree import canonicalize
+
+from pydantic_xml import BaseXmlModel, NoXml, element
+
+
+class Company(BaseXmlModel):
+ title: str = element()
+ website: Annotated[Optional[str], NoXml] = element(default=None)
+
+
+xml_doc = '''
+
+ SpaceX
+
+'''
+
+company = Company.from_xml(xml_doc)
+
+assert canonicalize(company.to_xml(), strip_text=True) == canonicalize(xml_doc, strip_text=True)
+
+json_doc = '''
+{
+ "title": "SpaceX",
+ "website": "https://spacex.com/"
+}
+'''
+company = Company.model_validate_json(json_doc)
+assert company.model_dump_json(indent=4) == json_doc.strip()
diff --git a/pydantic_xml/__init__.py b/pydantic_xml/__init__.py
index ca5e165..f749be4 100644
--- a/pydantic_xml/__init__.py
+++ b/pydantic_xml/__init__.py
@@ -4,8 +4,8 @@
from . import config, errors, model
from .errors import ModelError, ParsingError
-from .fields import XmlFieldSerializer, XmlFieldValidator, attr, computed_attr, computed_element, element, wrapped
-from .fields import xml_field_serializer, xml_field_validator
+from .fields import NoXml, XmlFieldSerializer, XmlFieldValidator, attr, computed_attr, computed_element, element
+from .fields import wrapped, xml_field_serializer, xml_field_validator
from .model import BaseXmlModel, RootXmlModel, create_model
__all__ = (
@@ -23,6 +23,7 @@
'model',
'xml_field_serializer',
'xml_field_validator',
+ 'NoXml',
'XmlFieldValidator',
'XmlFieldSerializer',
)
diff --git a/pydantic_xml/fields.py b/pydantic_xml/fields.py
index 3139662..9f916b7 100644
--- a/pydantic_xml/fields.py
+++ b/pydantic_xml/fields.py
@@ -21,6 +21,7 @@
'xml_field_serializer',
'xml_field_validator',
'ComputedXmlEntityInfo',
+ 'NoXml',
'XmlEntityInfo',
'XmlEntityInfoP',
'XmlFieldSerializer',
@@ -28,6 +29,12 @@
)
+class NoXml:
+ """
+ Annotation to prevent field encoding or decoding for xml format only.
+ """
+
+
class XmlEntityInfoP(typing.Protocol):
location: Optional[EntityLocation]
path: Optional[str]
diff --git a/pydantic_xml/serializers/factories/model.py b/pydantic_xml/serializers/factories/model.py
index dcb77df..5c4ecb5 100644
--- a/pydantic_xml/serializers/factories/model.py
+++ b/pydantic_xml/serializers/factories/model.py
@@ -9,7 +9,7 @@
import pydantic_xml as pxml
from pydantic_xml import errors, utils
from pydantic_xml.element import XmlElementReader, XmlElementWriter, is_element_nill, make_element_nill
-from pydantic_xml.fields import ComputedXmlEntityInfo, XmlEntityInfoP, extract_field_xml_entity_info
+from pydantic_xml.fields import ComputedXmlEntityInfo, NoXml, XmlEntityInfoP, extract_field_xml_entity_info
from pydantic_xml.serializers.serializer import SearchMode, Serializer
from pydantic_xml.typedefs import EntityLocation, Location, NsMap
from pydantic_xml.utils import QName, merge_nsmaps, select_ns
@@ -76,6 +76,10 @@ def from_core_schema(cls, schema: pcs.ModelSchema, ctx: Serializer.Context) -> '
fields_validation_aliases: Dict[str, str] = {}
fields_serializers: Dict[str, Serializer] = {}
for field_name, model_field in fields_schema['fields'].items():
+ field_info = model_cls.model_fields[field_name]
+ if NoXml in field_info.metadata:
+ continue
+
if model_field.get('serialization_exclude', False):
fields_serialization_exclude.add(field_name)
@@ -84,7 +88,6 @@ def from_core_schema(cls, schema: pcs.ModelSchema, ctx: Serializer.Context) -> '
if isinstance(validation_alias, str):
fields_validation_aliases[field_name] = validation_alias
- field_info = model_cls.model_fields[field_name]
field_ctx = ctx.child(
field_name=field_name,
field_alias=field_alias,
diff --git a/tests/test_misc.py b/tests/test_misc.py
index 52b910c..88b83bc 100644
--- a/tests/test_misc.py
+++ b/tests/test_misc.py
@@ -6,7 +6,7 @@
import pytest
from helpers import assert_xml_equal
-from pydantic_xml import BaseXmlModel, RootXmlModel, attr, element, errors, wrapped
+from pydantic_xml import BaseXmlModel, NoXml, RootXmlModel, attr, element, errors, wrapped
def test_xml_declaration():
@@ -441,3 +441,27 @@ class TestModel(BaseXmlModel, tag="model"):
assert not error_value_input, error_str
else:
assert error_value_input, error_str
+
+
+@pytest.mark.skipif(sys.version_info < (3, 9), reason="requires python 3.9 and above")
+def test_no_xml_annotation():
+ from typing import Annotated
+
+ class TestModel(BaseXmlModel, tag="model"):
+ field1: str = element()
+ field2: Annotated[Optional[str], NoXml] = element(default=None)
+
+ expected_xml = 'data1'
+ actual_obj = TestModel.from_xml(expected_xml)
+ expected_obj = TestModel(field1='data1')
+
+ assert actual_obj == expected_obj
+ actual_xml = actual_obj.to_xml(skip_empty=True)
+ assert_xml_equal(actual_xml, expected_xml.encode())
+
+ expected_json = {'field1': 'data1', 'field2': 'data2'}
+ actual_obj = TestModel.model_validate(expected_json)
+ expected_obj = TestModel(field1='data1', field2='data2')
+ assert actual_obj == expected_obj
+
+ assert actual_obj.model_dump() == expected_json