diff --git a/packages/abstractions/kiota_abstractions/multipart_body.py b/packages/abstractions/kiota_abstractions/multipart_body.py index 6f39989e..ec76540e 100644 --- a/packages/abstractions/kiota_abstractions/multipart_body.py +++ b/packages/abstractions/kiota_abstractions/multipart_body.py @@ -154,7 +154,7 @@ def _write_parsable(self, writer, part_value) -> None: part_value[0] ) ) - part_writer.write_object_value("", part_value[1], None) + part_writer.write_object_value(None, part_value[1], None) part_content = part_writer.get_serialized_content() if hasattr(part_content, "seek"): # seekable part_content.seek(0) diff --git a/packages/serialization/json/kiota_serialization_json/json_serialization_writer.py b/packages/serialization/json/kiota_serialization_json/json_serialization_writer.py index f896777c..7e86769e 100644 --- a/packages/serialization/json/kiota_serialization_json/json_serialization_writer.py +++ b/packages/serialization/json/kiota_serialization_json/json_serialization_writer.py @@ -38,7 +38,7 @@ def write_str_value(self, key: Optional[str], value: Optional[str]) -> None: value (Optional[str]): The string value to be written. """ if isinstance(value, str): - if key: + if key is not None: self.writer[key] = value else: self.value = value @@ -50,7 +50,7 @@ def write_bool_value(self, key: Optional[str], value: Optional[bool]) -> None: value (Optional[bool]): The boolean value to be written. """ if isinstance(value, bool): - if key: + if key is not None: self.writer[key] = value else: self.value = value @@ -62,7 +62,7 @@ def write_int_value(self, key: Optional[str], value: Optional[int]) -> None: value (Optional[int]): The integer value to be written. """ if isinstance(value, int): - if key: + if key is not None: self.writer[key] = value else: self.value = value @@ -74,7 +74,7 @@ def write_float_value(self, key: Optional[str], value: Optional[float]) -> None: value (Optional[float]): The float value to be written. """ if isinstance(value, (float, int)): - if key: + if key is not None: self.writer[key] = float(value) else: self.value = float(value) @@ -86,19 +86,19 @@ def write_uuid_value(self, key: Optional[str], value: Optional[UUID]) -> None: value (Optional[UUID]): The uuid value to be written. """ if isinstance(value, UUID): - if key: + if key is not None: self.writer[key] = str(value) else: self.value = str(value) elif isinstance(value, str): try: UUID(value) - if key: + if key is not None: self.writer[key] = value else: self.value = value except ValueError: - if key: + if key is not None: raise ValueError(f"Invalid UUID string value found for property {key}") raise ValueError("Invalid UUID string value found") @@ -109,19 +109,19 @@ def write_datetime_value(self, key: Optional[str], value: Optional[datetime]) -> value (Optional[datetime]): The datetime offset value to be written. """ if isinstance(value, datetime): - if key: + if key is not None: self.writer[key] = value.isoformat() else: self.value = value.isoformat() elif isinstance(value, str): try: datetime.fromisoformat(value) - if key: + if key is not None: self.writer[key] = value else: self.value = value except ValueError: - if key: + if key is not None: raise ValueError(f"Invalid datetime string value found for property {key}") raise ValueError("Invalid datetime string value found") @@ -132,19 +132,19 @@ def write_timedelta_value(self, key: Optional[str], value: Optional[timedelta]) value (Optional[timedelta]): The timedelta value to be written. """ if isinstance(value, timedelta): - if key: + if key is not None: self.writer[key] = str(value) else: self.value = str(value) elif isinstance(value, str): try: parse_timedelta_string(value) - if key: + if key is not None: self.writer[key] = value else: self.value = value except ValueError: - if key: + if key is not None: raise ValueError(f"Invalid timedelta string value found for property {key}") raise ValueError("Invalid timedelta string value found") @@ -155,19 +155,19 @@ def write_date_value(self, key: Optional[str], value: Optional[date]) -> None: value (Optional[date]): The date value to be written. """ if isinstance(value, date): - if key: + if key is not None: self.writer[key] = str(value) else: self.value = str(value) elif isinstance(value, str): try: date.fromisoformat(value) - if key: + if key is not None: self.writer[key] = value else: self.value = value except ValueError: - if key: + if key is not None: raise ValueError(f"Invalid date string value found for property {key}") raise ValueError("Invalid date string value found") @@ -178,19 +178,19 @@ def write_time_value(self, key: Optional[str], value: Optional[time]) -> None: value (Optional[time]): The time value to be written. """ if isinstance(value, time): - if key: + if key is not None: self.writer[key] = str(value) else: self.value = str(value) elif isinstance(value, str): try: time.fromisoformat(value) - if key: + if key is not None: self.writer[key] = value else: self.value = value except ValueError: - if key: + if key is not None: raise ValueError(f"Invalid time string value found for property {key}") raise ValueError("Invalid time string value found") @@ -210,7 +210,7 @@ def write_collection_of_primitive_values( temp_writer.write_any_value(None, val) result.append(temp_writer.value) - if key: + if key is not None: self.writer[key] = result else: self.value = result @@ -231,7 +231,7 @@ def write_collection_of_object_values( temp_writer.write_object_value(None, val) obj_list.append(temp_writer.value) - if key: + if key is not None: self.writer[key] = obj_list else: self.value = obj_list @@ -251,7 +251,7 @@ def write_collection_of_enum_values( temp_writer.write_enum_value(None, val) result.append(temp_writer.value) - if key: + if key is not None: self.writer[key] = result else: self.value = result @@ -273,7 +273,7 @@ def __write_collection_of_dict_values( temp_writer.__write_dict_value(None, val) result.append(temp_writer.value) - if key: + if key is not None: self.writer[key] = result else: self.value = result @@ -288,7 +288,7 @@ def write_bytes_value(self, key: Optional[str], value: Optional[bytes]) -> None: if isinstance(value, bytes): base64_bytes = base64.b64encode(value) base64_string = base64_bytes.decode('utf-8') - if key: + if key is not None: self.writer[key] = base64_string else: self.value = base64_string @@ -323,7 +323,7 @@ def write_object_value( serialized_value = ( temp_writer.value if temp_writer.value is not None else temp_writer.writer ) - if key: + if key is not None: self.writer[key] = serialized_value else: self.value = serialized_value @@ -335,7 +335,7 @@ def write_enum_value(self, key: Optional[str], value: Optional[K]) -> None: value (Optional[K]): The enum value to be written. """ if isinstance(value, Enum): - if key: + if key is not None: self.writer[key] = value.value else: self.value = value.value @@ -345,7 +345,7 @@ def write_null_value(self, key: Optional[str]) -> None: Args: key (Optional[str]): The key to be used for the written value. May be null. """ - if key: + if key is not None: self.writer[key] = None else: self.value = None @@ -360,7 +360,7 @@ def __write_dict_value(self, key: Optional[str], value: dict[str, Any]) -> None: temp_writer: JsonSerializationWriter = self._create_new_writer() for dict_key, dict_value in value.items(): temp_writer.write_any_value(dict_key, dict_value) - if key: + if key is not None: self.writer[key] = temp_writer.writer else: self.value = temp_writer.writer @@ -459,7 +459,7 @@ def write_non_parsable_object_value(self, key: Optional[str], value: T) -> None: value (object): The value to be written. """ if hasattr(value, '__dict__'): - if key: + if key is not None: self.writer[key] = value.__dict__ else: self.value = value.__dict__ diff --git a/packages/serialization/json/tests/unit/test_empty_property_names.py b/packages/serialization/json/tests/unit/test_empty_property_names.py new file mode 100644 index 00000000..b8ada3e9 --- /dev/null +++ b/packages/serialization/json/tests/unit/test_empty_property_names.py @@ -0,0 +1,53 @@ +import json +from datetime import date, datetime, time, timedelta +from uuid import UUID + +import pytest + +from kiota_serialization_json.json_serialization_writer import JsonSerializationWriter + +from ..helpers import OfficeLocation, User + + +@pytest.mark.parametrize("key", ["", "named", None]) +@pytest.mark.parametrize("method,value,expected", [ + ("write_str_value", "text", "text"), + ("write_bool_value", True, True), + ("write_int_value", 42, 42), + ("write_float_value", 1.5, 1.5), + ("write_uuid_value", UUID(int=1), str(UUID(int=1))), + ("write_datetime_value", datetime(2026, 1, 2, 3, 4), "2026-01-02T03:04:00"), + ("write_date_value", date(2026, 1, 2), "2026-01-02"), + ("write_time_value", time(3, 4), "03:04:00"), + ("write_timedelta_value", timedelta(seconds=1), "0:00:01"), + ("write_bytes_value", b"hello", "aGVsbG8="), + ("write_collection_of_primitive_values", [1, 2], [1, 2]), + ("write_enum_value", OfficeLocation.Oval, "oval"), + ("write_collection_of_enum_values", [OfficeLocation.Oval], ["oval"]), +]) +def test_property_name_is_distinct_from_root(key, method, value, expected): + writer = JsonSerializationWriter() + getattr(writer, method)(key, value) + assert json.loads(writer.get_serialized_content()) == ( + expected if key is None else {key: expected} + ) + + +@pytest.mark.parametrize("value", [None, "", False, 0, {}, {"": {}}, [{"": "value"}]]) +def test_nested_additional_data_preserves_empty_names(value): + writer = JsonSerializationWriter() + writer.write_additional_data_value({"properties": {"": value}}) + assert json.loads(writer.get_serialized_content()) == {"properties": {"": value}} + + +@pytest.mark.parametrize("collection", [False, True]) +def test_model_with_empty_property_name(collection): + user = User() + user.mobile_phone = "123" + writer = JsonSerializationWriter() + if collection: + writer.write_collection_of_object_values("", [user]) + else: + writer.write_object_value("", user) + expected = {"mobile_phone": "123"} + assert json.loads(writer.get_serialized_content()) == {"": [expected] if collection else expected}