prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  `PyPDFToDocument` 2.0 is not JSON-serializable due to its `DefaultConverter`
  **Describe the bug**
  Pipelines containing a PyPDFConverter are not JSON-serializable due to the `DefaultConverter` set in its init. 
  
  **Error message**
  `TypeError: Object of type DefaultConverter is not JSON serializable`
  
  **Expected behavior**
  JSON-serializing and deserializing a pipeline containing a PyPDFConverter should be possible. 
  
  **Additional context**
  Similarly, the following should be possible:
  ```python
  from haystack.preview.components.file_converters import PyPDFToDocument
  from haystack.preview import Pipeline
  p = Pipeline()
  p.add_component(instance=PyPDFToDocument(), name="pdf_file_converter")
  x = Pipeline.loads(p.dumps())
  ```
  but it currently raises:
  ```
  yaml.constructor.ConstructorError: could not determine a constructor for the tag 'tag:yaml.org,2002:python/object:haystack.preview.components.file_converters.pypdf.DefaultConverter'
    in "<unicode string>", line 4, column 18:
            converter: !!python/object:haystack.preview ...
  ```
  
  **To Reproduce**
  ```python
  from haystack.preview.components.file_converters import PyPDFToDocument
  from haystack.preview import Pipeline
  import json
  p = Pipeline()
  p.add_component(instance=PyPDFToDocument(), name="pdf_file_converter")
  print(json.dumps(p.to_dict(), indent=4))
  
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "/opt/homebrew/Caskroom/miniforge/base/envs/py310-Oct/lib/python3.10/json/__init__.py", line 238, in dumps
      **kw).encode(obj)
    File "/opt/homebrew/Caskroom/miniforge/base/envs/py310-Oct/lib/python3.10/json/encoder.py", line 201, in encode
      chunks = list(chunks)
    File "/opt/homebrew/Caskroom/miniforge/base/envs/py310-Oct/lib/python3.10/json/encoder.py", line 431, in _iterencode
      yield from _iterencode_dict(o, _current_indent_level)
    File "/opt/homebrew/Caskroom/miniforge/base/envs/py310-Oct/lib/python3.10/json/encoder.py", line 405, in _iterencode_dict
      yield from chunks
    File "/opt/homebrew/Caskroom/miniforge/base/envs/py310-Oct/lib/python3.10/json/encoder.py", line 405, in _iterencode_dict
      yield from chunks
    File "/opt/homebrew/Caskroom/miniforge/base/envs/py310-Oct/lib/python3.10/json/encoder.py", line 405, in _iterencode_dict
      yield from chunks
    [Previous line repeated 1 more time]
    File "/opt/homebrew/Caskroom/miniforge/base/envs/py310-Oct/lib/python3.10/json/encoder.py", line 438, in _iterencode
      o = _default(o)
    File "/opt/homebrew/Caskroom/miniforge/base/envs/py310-Oct/lib/python3.10/json/encoder.py", line 179, in default
      raise TypeError(f'Object of type {o.__class__.__name__} '
  TypeError: Object of type DefaultConverter is not JSON serializable
  ```
  
  **FAQ Check**
  - [ ] Have you had a look at [our new FAQ page](https://docs.haystack.deepset.ai/docs/faq)?
  
  **System:**
   - OS:
   - GPU/CPU:
   - Haystack version (commit or version number):
   - DocumentStore:
   - Reader:
   - Retriever:
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `haystack/preview/components/converters/pypdf.py`
  Content:
  ```
  1 import io
  2 import logging
  3 from typing import List, Union, Optional, Protocol
  4 from pathlib import Path
  5 
  6 from haystack.preview.dataclasses import ByteStream
  7 from haystack.preview.lazy_imports import LazyImport
  8 from haystack.preview import Document, component
  9 
  10 with LazyImport("Run 'pip install pypdf'") as pypdf_import:
  11     from pypdf import PdfReader
  12 
  13 
  14 logger = logging.getLogger(__name__)
  15 
  16 
  17 class PyPDFConverter(Protocol):
  18     """
  19     A protocol that defines a converter which takes a PdfReader object and converts it into a Document object.
  20     """
  21 
  22     def convert(self, reader: "PdfReader") -> Document:
  23         ...
  24 
  25 
  26 class DefaultConverter:
  27     """
  28     The default converter class that extracts text from a PdfReader object's pages and returns a Document.
  29     """
  30 
  31     def convert(self, reader: "PdfReader") -> Document:
  32         """Extract text from the PDF and return a Document object with the text content."""
  33         text = "".join(page.extract_text() for page in reader.pages if page.extract_text())
  34         return Document(content=text)
  35 
  36 
  37 @component
  38 class PyPDFToDocument:
  39     """
  40     Converts PDF files to Document objects.
  41     It uses a converter that follows the PyPDFConverter protocol to perform the conversion.
  42     A default text extraction converter is used if no custom converter is provided.
  43     """
  44 
  45     def __init__(self, converter: Optional[PyPDFConverter] = None):
  46         """
  47         Initializes the PyPDFToDocument component with an optional custom converter.
  48         :param converter: A converter instance that adheres to the PyPDFConverter protocol.
  49                           If None, the DefaultConverter is used.
  50         """
  51         pypdf_import.check()
  52         self.converter: PyPDFConverter = converter or DefaultConverter()
  53 
  54     @component.output_types(documents=List[Document])
  55     def run(self, sources: List[Union[str, Path, ByteStream]]):
  56         """
  57         Converts a list of PDF sources into Document objects using the configured converter.
  58 
  59         :param sources: A list of PDF data sources, which can be file paths or ByteStream objects.
  60         :return: A dictionary containing a list of Document objects under the 'documents' key.
  61         """
  62         documents = []
  63         for source in sources:
  64             try:
  65                 pdf_reader = self._get_pdf_reader(source)
  66                 document = self.converter.convert(pdf_reader)
  67             except Exception as e:
  68                 logger.warning("Could not read %s and convert it to Document, skipping. %s", source, e)
  69                 continue
  70             documents.append(document)
  71 
  72         return {"documents": documents}
  73 
  74     def _get_pdf_reader(self, source: Union[str, Path, ByteStream]) -> "PdfReader":
  75         """
  76         Creates a PdfReader object from a given source, which can be a file path or a ByteStream object.
  77 
  78         :param source: The source of the PDF data.
  79         :return: A PdfReader instance initialized with the PDF data from the source.
  80         :raises ValueError: If the source type is not supported.
  81         """
  82         if isinstance(source, (str, Path)):
  83             return PdfReader(str(source))
  84         elif isinstance(source, ByteStream):
  85             return PdfReader(io.BytesIO(source.data))
  86         else:
  87             raise ValueError(f"Unsupported source type: {type(source)}")
  88
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/haystack/preview/components/converters/pypdf.py b/haystack/preview/components/converters/pypdf.py
  --- a/haystack/preview/components/converters/pypdf.py
  +++ b/haystack/preview/components/converters/pypdf.py
  @@ -1,11 +1,11 @@
   import io
   import logging
  -from typing import List, Union, Optional, Protocol
  +from typing import List, Union, Protocol, Dict
   from pathlib import Path
   
   from haystack.preview.dataclasses import ByteStream
   from haystack.preview.lazy_imports import LazyImport
  -from haystack.preview import Document, component
  +from haystack.preview import Document, component, default_to_dict
   
   with LazyImport("Run 'pip install pypdf'") as pypdf_import:
       from pypdf import PdfReader
  @@ -34,6 +34,11 @@
           return Document(content=text)
   
   
  +# This registry is used to store converters names and instances.
  +# It can be used to register custom converters.
  +CONVERTERS_REGISTRY: Dict[str, PyPDFConverter] = {"default": DefaultConverter()}
  +
  +
   @component
   class PyPDFToDocument:
       """
  @@ -42,14 +47,27 @@
       A default text extraction converter is used if no custom converter is provided.
       """
   
  -    def __init__(self, converter: Optional[PyPDFConverter] = None):
  +    def __init__(self, converter_name: str = "default"):
           """
           Initializes the PyPDFToDocument component with an optional custom converter.
  -        :param converter: A converter instance that adheres to the PyPDFConverter protocol.
  -                          If None, the DefaultConverter is used.
  +        :param converter_name: A converter name that is registered in the CONVERTERS_REGISTRY.
  +            Defaults to 'default'.
           """
           pypdf_import.check()
  -        self.converter: PyPDFConverter = converter or DefaultConverter()
  +
  +        try:
  +            converter = CONVERTERS_REGISTRY[converter_name]
  +        except KeyError:
  +            msg = (
  +                f"Invalid converter_name: {converter_name}.\n Available converters: {list(CONVERTERS_REGISTRY.keys())}"
  +            )
  +            raise ValueError(msg) from KeyError
  +        self.converter_name = converter_name
  +        self._converter: PyPDFConverter = converter
  +
  +    def to_dict(self):
  +        # do not serialize the _converter instance
  +        return default_to_dict(self, converter_name=self.converter_name)
   
       @component.output_types(documents=List[Document])
       def run(self, sources: List[Union[str, Path, ByteStream]]):
  @@ -63,7 +81,7 @@
           for source in sources:
               try:
                   pdf_reader = self._get_pdf_reader(source)
  -                document = self.converter.convert(pdf_reader)
  +                document = self._converter.convert(pdf_reader)
               except Exception as e:
                   logger.warning("Could not read %s and convert it to Document, skipping. %s", source, e)
                   continue
  
verification_info: {"golden_diff": "diff --git a/haystack/preview/components/converters/pypdf.py b/haystack/preview/components/converters/pypdf.py\n--- a/haystack/preview/components/converters/pypdf.py\n+++ b/haystack/preview/components/converters/pypdf.py\n@@ -1,11 +1,11 @@\n import io\n import logging\n-from typing import List, Union, Optional, Protocol\n+from typing import List, Union, Protocol, Dict\n from pathlib import Path\n \n from haystack.preview.dataclasses import ByteStream\n from haystack.preview.lazy_imports import LazyImport\n-from haystack.preview import Document, component\n+from haystack.preview import Document, component, default_to_dict\n \n with LazyImport(\"Run 'pip install pypdf'\") as pypdf_import:\n     from pypdf import PdfReader\n@@ -34,6 +34,11 @@\n         return Document(content=text)\n \n \n+# This registry is used to store converters names and instances.\n+# It can be used to register custom converters.\n+CONVERTERS_REGISTRY: Dict[str, PyPDFConverter] = {\"default\": DefaultConverter()}\n+\n+\n @component\n class PyPDFToDocument:\n     \"\"\"\n@@ -42,14 +47,27 @@\n     A default text extraction converter is used if no custom converter is provided.\n     \"\"\"\n \n-    def __init__(self, converter: Optional[PyPDFConverter] = None):\n+    def __init__(self, converter_name: str = \"default\"):\n         \"\"\"\n         Initializes the PyPDFToDocument component with an optional custom converter.\n-        :param converter: A converter instance that adheres to the PyPDFConverter protocol.\n-                          If None, the DefaultConverter is used.\n+        :param converter_name: A converter name that is registered in the CONVERTERS_REGISTRY.\n+            Defaults to 'default'.\n         \"\"\"\n         pypdf_import.check()\n-        self.converter: PyPDFConverter = converter or DefaultConverter()\n+\n+        try:\n+            converter = CONVERTERS_REGISTRY[converter_name]\n+        except KeyError:\n+            msg = (\n+                f\"Invalid converter_name: {converter_name}.\\n Available converters: {list(CONVERTERS_REGISTRY.keys())}\"\n+            )\n+            raise ValueError(msg) from KeyError\n+        self.converter_name = converter_name\n+        self._converter: PyPDFConverter = converter\n+\n+    def to_dict(self):\n+        # do not serialize the _converter instance\n+        return default_to_dict(self, converter_name=self.converter_name)\n \n     @component.output_types(documents=List[Document])\n     def run(self, sources: List[Union[str, Path, ByteStream]]):\n@@ -63,7 +81,7 @@\n         for source in sources:\n             try:\n                 pdf_reader = self._get_pdf_reader(source)\n-                document = self.converter.convert(pdf_reader)\n+                document = self._converter.convert(pdf_reader)\n             except Exception as e:\n                 logger.warning(\"Could not read %s and convert it to Document, skipping. %s\", source, e)\n                 continue\n", "issue": "`PyPDFToDocument` 2.0 is not JSON-serializable due to its `DefaultConverter`\n**Describe the bug**\r\nPipelines containing a PyPDFConverter are not JSON-serializable due to the `DefaultConverter` set in its init. \r\n\r\n**Error message**\r\n`TypeError: Object of type DefaultConverter is not JSON serializable`\r\n\r\n**Expected behavior**\r\nJSON-serializing and deserializing a pipeline containing a PyPDFConverter should be possible. \r\n\r\n**Additional context**\r\nSimilarly, the following should be possible:\r\n```python\r\nfrom haystack.preview.components.file_converters import PyPDFToDocument\r\nfrom haystack.preview import Pipeline\r\np = Pipeline()\r\np.add_component(instance=PyPDFToDocument(), name=\"pdf_file_converter\")\r\nx = Pipeline.loads(p.dumps())\r\n```\r\nbut it currently raises:\r\n```\r\nyaml.constructor.ConstructorError: could not determine a constructor for the tag 'tag:yaml.org,2002:python/object:haystack.preview.components.file_converters.pypdf.DefaultConverter'\r\n  in \"<unicode string>\", line 4, column 18:\r\n          converter: !!python/object:haystack.preview ...\r\n```\r\n\r\n**To Reproduce**\r\n```python\r\nfrom haystack.preview.components.file_converters import PyPDFToDocument\r\nfrom haystack.preview import Pipeline\r\nimport json\r\np = Pipeline()\r\np.add_component(instance=PyPDFToDocument(), name=\"pdf_file_converter\")\r\nprint(json.dumps(p.to_dict(), indent=4))\r\n\r\nTraceback (most recent call last):\r\n  File \"<stdin>\", line 1, in <module>\r\n  File \"/opt/homebrew/Caskroom/miniforge/base/envs/py310-Oct/lib/python3.10/json/__init__.py\", line 238, in dumps\r\n    **kw).encode(obj)\r\n  File \"/opt/homebrew/Caskroom/miniforge/base/envs/py310-Oct/lib/python3.10/json/encoder.py\", line 201, in encode\r\n    chunks = list(chunks)\r\n  File \"/opt/homebrew/Caskroom/miniforge/base/envs/py310-Oct/lib/python3.10/json/encoder.py\", line 431, in _iterencode\r\n    yield from _iterencode_dict(o, _current_indent_level)\r\n  File \"/opt/homebrew/Caskroom/miniforge/base/envs/py310-Oct/lib/python3.10/json/encoder.py\", line 405, in _iterencode_dict\r\n    yield from chunks\r\n  File \"/opt/homebrew/Caskroom/miniforge/base/envs/py310-Oct/lib/python3.10/json/encoder.py\", line 405, in _iterencode_dict\r\n    yield from chunks\r\n  File \"/opt/homebrew/Caskroom/miniforge/base/envs/py310-Oct/lib/python3.10/json/encoder.py\", line 405, in _iterencode_dict\r\n    yield from chunks\r\n  [Previous line repeated 1 more time]\r\n  File \"/opt/homebrew/Caskroom/miniforge/base/envs/py310-Oct/lib/python3.10/json/encoder.py\", line 438, in _iterencode\r\n    o = _default(o)\r\n  File \"/opt/homebrew/Caskroom/miniforge/base/envs/py310-Oct/lib/python3.10/json/encoder.py\", line 179, in default\r\n    raise TypeError(f'Object of type {o.__class__.__name__} '\r\nTypeError: Object of type DefaultConverter is not JSON serializable\r\n```\r\n\r\n**FAQ Check**\r\n- [ ] Have you had a look at [our new FAQ page](https://docs.haystack.deepset.ai/docs/faq)?\r\n\r\n**System:**\r\n - OS:\r\n - GPU/CPU:\r\n - Haystack version (commit or version number):\r\n - DocumentStore:\r\n - Reader:\r\n - Retriever:\r\n\n", "before_files": [{"content": "import io\nimport logging\nfrom typing import List, Union, Optional, Protocol\nfrom pathlib import Path\n\nfrom haystack.preview.dataclasses import ByteStream\nfrom haystack.preview.lazy_imports import LazyImport\nfrom haystack.preview import Document, component\n\nwith LazyImport(\"Run 'pip install pypdf'\") as pypdf_import:\n    from pypdf import PdfReader\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass PyPDFConverter(Protocol):\n    \"\"\"\n    A protocol that defines a converter which takes a PdfReader object and converts it into a Document object.\n    \"\"\"\n\n    def convert(self, reader: \"PdfReader\") -> Document:\n        ...\n\n\nclass DefaultConverter:\n    \"\"\"\n    The default converter class that extracts text from a PdfReader object's pages and returns a Document.\n    \"\"\"\n\n    def convert(self, reader: \"PdfReader\") -> Document:\n        \"\"\"Extract text from the PDF and return a Document object with the text content.\"\"\"\n        text = \"\".join(page.extract_text() for page in reader.pages if page.extract_text())\n        return Document(content=text)\n\n\n@component\nclass PyPDFToDocument:\n    \"\"\"\n    Converts PDF files to Document objects.\n    It uses a converter that follows the PyPDFConverter protocol to perform the conversion.\n    A default text extraction converter is used if no custom converter is provided.\n    \"\"\"\n\n    def __init__(self, converter: Optional[PyPDFConverter] = None):\n        \"\"\"\n        Initializes the PyPDFToDocument component with an optional custom converter.\n        :param converter: A converter instance that adheres to the PyPDFConverter protocol.\n                          If None, the DefaultConverter is used.\n        \"\"\"\n        pypdf_import.check()\n        self.converter: PyPDFConverter = converter or DefaultConverter()\n\n    @component.output_types(documents=List[Document])\n    def run(self, sources: List[Union[str, Path, ByteStream]]):\n        \"\"\"\n        Converts a list of PDF sources into Document objects using the configured converter.\n\n        :param sources: A list of PDF data sources, which can be file paths or ByteStream objects.\n        :return: A dictionary containing a list of Document objects under the 'documents' key.\n        \"\"\"\n        documents = []\n        for source in sources:\n            try:\n                pdf_reader = self._get_pdf_reader(source)\n                document = self.converter.convert(pdf_reader)\n            except Exception as e:\n                logger.warning(\"Could not read %s and convert it to Document, skipping. %s\", source, e)\n                continue\n            documents.append(document)\n\n        return {\"documents\": documents}\n\n    def _get_pdf_reader(self, source: Union[str, Path, ByteStream]) -> \"PdfReader\":\n        \"\"\"\n        Creates a PdfReader object from a given source, which can be a file path or a ByteStream object.\n\n        :param source: The source of the PDF data.\n        :return: A PdfReader instance initialized with the PDF data from the source.\n        :raises ValueError: If the source type is not supported.\n        \"\"\"\n        if isinstance(source, (str, Path)):\n            return PdfReader(str(source))\n        elif isinstance(source, ByteStream):\n            return PdfReader(io.BytesIO(source.data))\n        else:\n            raise ValueError(f\"Unsupported source type: {type(source)}\")\n", "path": "haystack/preview/components/converters/pypdf.py"}], "after_files": [{"content": "import io\nimport logging\nfrom typing import List, Union, Protocol, Dict\nfrom pathlib import Path\n\nfrom haystack.preview.dataclasses import ByteStream\nfrom haystack.preview.lazy_imports import LazyImport\nfrom haystack.preview import Document, component, default_to_dict\n\nwith LazyImport(\"Run 'pip install pypdf'\") as pypdf_import:\n    from pypdf import PdfReader\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass PyPDFConverter(Protocol):\n    \"\"\"\n    A protocol that defines a converter which takes a PdfReader object and converts it into a Document object.\n    \"\"\"\n\n    def convert(self, reader: \"PdfReader\") -> Document:\n        ...\n\n\nclass DefaultConverter:\n    \"\"\"\n    The default converter class that extracts text from a PdfReader object's pages and returns a Document.\n    \"\"\"\n\n    def convert(self, reader: \"PdfReader\") -> Document:\n        \"\"\"Extract text from the PDF and return a Document object with the text content.\"\"\"\n        text = \"\".join(page.extract_text() for page in reader.pages if page.extract_text())\n        return Document(content=text)\n\n\n# This registry is used to store converters names and instances.\n# It can be used to register custom converters.\nCONVERTERS_REGISTRY: Dict[str, PyPDFConverter] = {\"default\": DefaultConverter()}\n\n\n@component\nclass PyPDFToDocument:\n    \"\"\"\n    Converts PDF files to Document objects.\n    It uses a converter that follows the PyPDFConverter protocol to perform the conversion.\n    A default text extraction converter is used if no custom converter is provided.\n    \"\"\"\n\n    def __init__(self, converter_name: str = \"default\"):\n        \"\"\"\n        Initializes the PyPDFToDocument component with an optional custom converter.\n        :param converter_name: A converter name that is registered in the CONVERTERS_REGISTRY.\n            Defaults to 'default'.\n        \"\"\"\n        pypdf_import.check()\n\n        try:\n            converter = CONVERTERS_REGISTRY[converter_name]\n        except KeyError:\n            msg = (\n                f\"Invalid converter_name: {converter_name}.\\n Available converters: {list(CONVERTERS_REGISTRY.keys())}\"\n            )\n            raise ValueError(msg) from KeyError\n        self.converter_name = converter_name\n        self._converter: PyPDFConverter = converter\n\n    def to_dict(self):\n        # do not serialize the _converter instance\n        return default_to_dict(self, converter_name=self.converter_name)\n\n    @component.output_types(documents=List[Document])\n    def run(self, sources: List[Union[str, Path, ByteStream]]):\n        \"\"\"\n        Converts a list of PDF sources into Document objects using the configured converter.\n\n        :param sources: A list of PDF data sources, which can be file paths or ByteStream objects.\n        :return: A dictionary containing a list of Document objects under the 'documents' key.\n        \"\"\"\n        documents = []\n        for source in sources:\n            try:\n                pdf_reader = self._get_pdf_reader(source)\n                document = self._converter.convert(pdf_reader)\n            except Exception as e:\n                logger.warning(\"Could not read %s and convert it to Document, skipping. %s\", source, e)\n                continue\n            documents.append(document)\n\n        return {\"documents\": documents}\n\n    def _get_pdf_reader(self, source: Union[str, Path, ByteStream]) -> \"PdfReader\":\n        \"\"\"\n        Creates a PdfReader object from a given source, which can be a file path or a ByteStream object.\n\n        :param source: The source of the PDF data.\n        :return: A PdfReader instance initialized with the PDF data from the source.\n        :raises ValueError: If the source type is not supported.\n        \"\"\"\n        if isinstance(source, (str, Path)):\n            return PdfReader(str(source))\n        elif isinstance(source, ByteStream):\n            return PdfReader(io.BytesIO(source.data))\n        else:\n            raise ValueError(f\"Unsupported source type: {type(source)}\")\n", "path": "haystack/preview/components/converters/pypdf.py"}]}