diff --git a/haystack/dataclasses/document.py b/haystack/dataclasses/document.py index 62fda47a8f..3c2fc57ed8 100644 --- a/haystack/dataclasses/document.py +++ b/haystack/dataclasses/document.py @@ -155,6 +155,12 @@ def from_dict(cls, data: dict[str, Any]) -> "Document": # a document field is a metadata key. We treat legacy fields as document fields, so that # `_RemoveLegacyFields` drops them instead of them ending up in `meta`. flatten_meta = {} + # A non-mapping value under the "meta" key can't be the `meta` parameter (which must be a + # dictionary). It can only be a flattened metadata key literally named "meta", produced by + # to_dict(flatten=True) for a document whose metadata contains a "meta" key. + if not isinstance(meta, dict): + flatten_meta["meta"] = meta + meta = {} document_fields = _LEGACY_FIELDS + [f.name for f in fields(cls)] for key in list(data.keys()): if key not in document_fields: diff --git a/releasenotes/notes/document-meta-key-collision-1330a997893d9f34.yaml b/releasenotes/notes/document-meta-key-collision-1330a997893d9f34.yaml new file mode 100644 index 0000000000..b6fe06b409 --- /dev/null +++ b/releasenotes/notes/document-meta-key-collision-1330a997893d9f34.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + Fixed ``Document.from_dict()`` raising ``TypeError: 'str' object is not a mapping`` when + deserializing a document whose metadata contained a non-mapping key named ``meta`` + (for example ``Document(content="hi", meta={"meta": "value"})``). Such a document is now + correctly reconstructed from the default flattened ``to_dict()`` output, so the + ``to_dict()``/``from_dict()`` round-trip no longer crashes. diff --git a/test/dataclasses/test_document.py b/test/dataclasses/test_document.py index 85cce38df5..67a675fe0e 100644 --- a/test/dataclasses/test_document.py +++ b/test/dataclasses/test_document.py @@ -346,6 +346,17 @@ def test_from_dict_with_flat_meta(): ) +def test_to_dict_from_dict_roundtrip_with_meta_key_named_meta(): + """ + A metadata key literally named "meta" must survive the default + to_dict(flatten=True)/from_dict() round-trip. + """ + doc = Document(content="hi", meta={"meta": "value"}) + + assert Document.from_dict(doc.to_dict()) == doc + assert Document.from_dict(doc.to_dict()).meta == {"meta": "value"} + + def test_from_dict_with_flat_and_non_flat_meta(): with pytest.raises(ValueError, match="Pass either the 'meta' parameter or flattened metadata keys"): Document.from_dict(