From 486b6fa70f5943370e51ee2a6e436300d5c830d5 Mon Sep 17 00:00:00 2001 From: Jooyoung Jung Date: Sat, 31 Oct 2026 08:26:26 +0900 Subject: [PATCH] adapter.json, adapter.xml: Retain falsy Extension and Qualifier values Extension and Qualifier values are currently written only if truthy, so 0, Decimal("0"), and False are dropped and read back as None. They are now written whenever not None so that they survive a write/read round trip. Fixes #682 Signed-off-by: Jooyoung Jung Assisted-by: Anthropic Claude Opus 5.5 (claude-opus-5-5) --- .../aas/adapter/json/json_serialization.py | 12 ++++------- .../aas/adapter/xml/xml_serialization.py | 4 ++-- ...test_json_serialization_deserialization.py | 20 +++++++++++++++++++ .../test_xml_serialization_deserialization.py | 18 +++++++++++++++++ 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/sdk/basyx/aas/adapter/json/json_serialization.py b/sdk/basyx/aas/adapter/json/json_serialization.py index 155aced55..2d9045c61 100644 --- a/sdk/basyx/aas/adapter/json/json_serialization.py +++ b/sdk/basyx/aas/adapter/json/json_serialization.py @@ -256,10 +256,8 @@ def _qualifier_to_json(cls, obj: model.Qualifier) -> Dict[str, object]: :return: dict with the serialized attributes of this object """ data = cls._abstract_classes_to_json(obj) - if obj.value: - data["value"] = ( - model.datatypes.xsd_repr(obj.value) if obj.value is not None else None - ) + if obj.value is not None: + data["value"] = model.datatypes.xsd_repr(obj.value) if obj.value_id: data["valueId"] = obj.value_id # Even though kind is optional in the schema, it's better to always serialize it instead of specifying @@ -278,10 +276,8 @@ def _extension_to_json(cls, obj: model.Extension) -> Dict[str, object]: :return: dict with the serialized attributes of this object """ data = cls._abstract_classes_to_json(obj) - if obj.value: - data["value"] = ( - model.datatypes.xsd_repr(obj.value) if obj.value is not None else None - ) + if obj.value is not None: + data["value"] = model.datatypes.xsd_repr(obj.value) if obj.refers_to: data["refersTo"] = list(obj.refers_to) if obj.value_type: diff --git a/sdk/basyx/aas/adapter/xml/xml_serialization.py b/sdk/basyx/aas/adapter/xml/xml_serialization.py index 8570add66..f11ef503b 100644 --- a/sdk/basyx/aas/adapter/xml/xml_serialization.py +++ b/sdk/basyx/aas/adapter/xml/xml_serialization.py @@ -328,7 +328,7 @@ def qualifier_to_xml( NS_AAS + "valueType", text=model.datatypes.XSD_TYPE_NAMES[obj.value_type] ) ) - if obj.value: + if obj.value is not None: et_qualifier.append(_value_to_xml(obj.value, obj.value_type)) if obj.value_id: et_qualifier.append(reference_to_xml(obj.value_id, NS_AAS + "valueId")) @@ -354,7 +354,7 @@ def extension_to_xml( text=model.datatypes.XSD_TYPE_NAMES[obj.value_type], ) ) - if obj.value: + if obj.value is not None: et_extension.append(_value_to_xml(obj.value, obj.value_type)) # type: ignore # (value_type could be None) if len(obj.refers_to) > 0: refers_to = _generate_element(NS_AAS + "refersTo") diff --git a/sdk/test/adapter/json/test_json_serialization_deserialization.py b/sdk/test/adapter/json/test_json_serialization_deserialization.py index 5e815ad92..725719a4a 100644 --- a/sdk/test/adapter/json/test_json_serialization_deserialization.py +++ b/sdk/test/adapter/json/test_json_serialization_deserialization.py @@ -125,3 +125,23 @@ def test_example_all_examples_serialization_deserialization(self) -> None: json_identifiable_store = read_aas_json_file(file, failsafe=False) checker = AASDataChecker(raise_immediately=True) checker.check_identifiable_store(json_identifiable_store, data) + + +class JsonSerializationDeserializationFalsyValueTest(unittest.TestCase): + def test_falsy_extension_and_qualifier_values(self) -> None: + for value_type, value in ((model.datatypes.Int, 0), + (model.datatypes.Decimal, model.datatypes.Decimal("0")), + (model.datatypes.Boolean, False)): + with self.subTest(value_type=value_type): + submodel = model.Submodel( + "https://example.org/falsy", + extension=[model.Extension("e", value_type=value_type, value=value)], + qualifier=[model.Qualifier("q", value_type=value_type, value=value)], + ) + file = io.StringIO() + write_aas_json_file(file=file, data=model.DictIdentifiableStore([submodel])) + file.seek(0) + read = read_aas_json_file(file, failsafe=False).get_item("https://example.org/falsy") + assert isinstance(read, model.Submodel) + self.assertEqual(value, read.get_extension_by_name("e").value) + self.assertEqual(value, read.get_qualifier_by_type("q").value) diff --git a/sdk/test/adapter/xml/test_xml_serialization_deserialization.py b/sdk/test/adapter/xml/test_xml_serialization_deserialization.py index aae0ce214..ec17095f0 100644 --- a/sdk/test/adapter/xml/test_xml_serialization_deserialization.py +++ b/sdk/test/adapter/xml/test_xml_serialization_deserialization.py @@ -90,3 +90,21 @@ def test_submodel_serialization_deserialization(self) -> None: ) checker = AASDataChecker(raise_immediately=True) checker.check_submodel_equal(submodel2, submodel) + + +class XMLSerializationDeserializationFalsyValueTest(unittest.TestCase): + def test_falsy_extension_and_qualifier_values(self) -> None: + for value_type, value in ((model.datatypes.Int, 0), + (model.datatypes.Decimal, model.datatypes.Decimal("0")), + (model.datatypes.Boolean, False)): + with self.subTest(value_type=value_type): + submodel = model.Submodel( + "https://example.org/falsy", + extension=[model.Extension("e", value_type=value_type, value=value)], + qualifier=[model.Qualifier("q", value_type=value_type, value=value)], + ) + read = _serialize_and_deserialize(model.DictIdentifiableStore([submodel])).get_item( + "https://example.org/falsy") + assert isinstance(read, model.Submodel) + self.assertEqual(value, read.get_extension_by_name("e").value) + self.assertEqual(value, read.get_qualifier_by_type("q").value)