diff --git a/scrapy/exporters.py b/scrapy/exporters.py index 5a11df833..633e4414b 100644 --- a/scrapy/exporters.py +++ b/scrapy/exporters.py @@ -74,6 +74,27 @@ class BaseItemExporter(ABC): def finish_exporting(self) -> None: # noqa: B027 pass + def _serialize_nested_items( + self, value: Any, default_value: Any, include_empty: bool | None + ) -> Any: + def serialize_iterable(it: Iterable[Any]) -> list[Any]: + return [ + self._serialize_nested_items(x, default_value, include_empty) + for x in it + ] + + if is_item(value) and not isinstance(value, dict): + return dict( + self._get_serialized_fields(value, default_value, include_empty) + ) + if is_listlike(value): + if isinstance(value, dict): + serialized_list = serialize_iterable(value.items()) + else: + serialized_list = serialize_iterable(value) + return type(value)(serialized_list) + return value + def _get_serialized_fields( self, item: Any, default_value: Any = None, include_empty: bool | None = None ) -> Iterable[tuple[str, Any]]: @@ -106,7 +127,13 @@ class BaseItemExporter(ABC): item_field, output_field = field_name if item_field in item: field_meta = item.get_field_meta(item_field) - value = self.serialize_field(field_meta, output_field, item[item_field]) + value = self.serialize_field( + field_meta, + output_field, + self._serialize_nested_items( + item[item_field], default_value, include_empty + ), + ) else: value = default_value diff --git a/tests/test_exporters.py b/tests/test_exporters.py index 3b997767e..1812571f9 100644 --- a/tests/test_exporters.py +++ b/tests/test_exporters.py @@ -36,6 +36,10 @@ class MyItem(Item): age = Field() +class ItemWithSerializer(Item): + a = Field(serializer=str.upper) + + class CustomFieldItem(Item): name = Field() age = Field(serializer=custom_serializer) @@ -106,6 +110,11 @@ class TestBaseItemExporter(ABC): res = self.ie.serialize_field(a.get_field_meta("age"), "age", a["age"]) assert res == "22" + def test_serialize_nested_items(self): + i = MyItem(name=ItemWithSerializer(a="asdf"), age=42) + res = self.ie._serialize_nested_items(i, None, None)["name"]["a"] + assert res == "ASDF" + def test_fields_to_export(self): ie = self._get_exporter(fields_to_export=["name"]) assert list(ie._get_serialized_fields(self.i)) == [("name", "John\xa3")]