Skip to content

fix: round-trip serialization of mixed-type lists in schema-based serialization - #12202

Open
davidpavlovschi wants to merge 1 commit into
deepset-ai:mainfrom
davidpavlovschi:fix/mixed-type-lists-schema-serialization
Open

fix: round-trip serialization of mixed-type lists in schema-based serialization#12202
davidpavlovschi wants to merge 1 commit into
deepset-ai:mainfrom
davidpavlovschi:fix/mixed-type-lists-schema-serialization

Conversation

@davidpavlovschi

Copy link
Copy Markdown

Related Issues

Proposed Changes:

_serialize_value_with_schema derived the array schema from the first element only, so any list/tuple/set holding mixed types was described by a schema that lied about every element after the first. Two concrete failures on main (commit 5add74e):

_deserialize_value_with_schema(_serialize_value_with_schema({"x": [Document(content="a"), "plain string", 3]}))
# AttributeError: 'str' object has no attribute 'copy'

_serialize_value_with_schema({"y": [1, "a", {"k": 2}]})["serialization_schema"]
# {'type': 'object', 'properties': {'y': {'type': 'array', 'items': {'type': 'integer'}}}}
# -> every element typed as integer, payload silently mis-typed

This hits real runtime values: Agent State fields and pipeline-breakpoint inputs holding heterogeneous lists crash or corrupt on resume.

Changes in haystack/utils/base_serialization.py:

  • Serializer: compute each element's schema during the single existing pass instead of re-serializing the first element. When all element schemas are equal, emit the current {"type": "array", "items": {...}} envelope unchanged. Only when they differ, emit the JSON-Schema-standard {"type": "array", "prefixItems": [schema_per_item, ...]}. The uniqueItems / frozen / minItems / maxItems markers are attached exactly as before, so set/frozenset/tuple reconstruction is untouched.
  • Deserializer: in the array branch, prefer prefixItems when present (each item is zipped with its own schema) and otherwise fall back to schema["items"] exactly as today. A prefixItems length that disagrees with the data length raises a DeserializationError instead of a raw zip error.
  • Updated the two docstring notes that stated mixed-type lists are unsupported.
  • Added a release note under releasenotes/notes/.

Backward compatibility: homogeneous arrays serialize to the byte-for-byte same dict as before, and payloads written by older versions (which only ever carry items) still deserialize through the unchanged items path. Both are asserted by dedicated tests.

How did you test it?

Unit tests added in test/utils/test_base_serialization.py (new TestMixedTypeArrays class): the exact serialized schema for mixed primitives, round-trips for mixed primitives, primitives mixed with a to_dict object (Document), a mixed tuple, mixed sets and frozensets (with container-type assertions), nested mixed lists, the empty list, plus a backward-compat assertion that a homogeneous list/tuple still serializes to the pre-change dict, that an old-format items-only payload still deserializes, and that a prefixItems/data length mismatch raises DeserializationError.

Verified red-before / green-after: with the new tests against the unmodified base_serialization.py, 5 of them fail (including the AttributeError crash); with the fix all pass.

Ran locally (Python 3.12, editable install):

  • pytest test/utils/test_base_serialization.py test/core/pipeline/test_breakpoint.py test/core/pipeline/test_component_checks.py — the three call sites of these helpers: 160 passed.
  • pytest test/utils test/core test/dataclasses -m "not integration"2534 passed, 4 failures all pre-existing and unrelated to this change (missing optional torch/ipython in this environment).
  • ruff check and ruff format --check on both touched files — clean.
  • mypy on haystack/utils/base_serialization.py — clean.

Notes for the reviewer

The issue offers "implement or document" as alternatives. This PR takes the implement route because the current behaviour is a hard crash (AttributeError leaking out of deserialization), not just a documented gap. The mechanism is deliberately conservative: nothing about homogeneous output changes, so existing snapshots and stored State payloads keep loading unchanged, and prefixItems is the standard JSON Schema keyword for positional element schemas rather than a Haystack-specific marker.

One behavioural detail worth a look: a set or frozenset with mixed types now round-trips too. The per-position schemas are produced from the same single iteration that produces the data, so the pairing is always consistent even though set iteration order is arbitrary.

This PR was fully generated with an AI assistant. I have reviewed the changes and run the relevant tests.

Checklist

…ialization

_serialize_value_with_schema derived the array schema from the first element
only, so a list/tuple/set holding mixed types either crashed on deserialization
with a raw AttributeError or silently came back mis-typed.

Mixed-type arrays now record one schema per position under the JSON Schema
prefixItems keyword, and _deserialize_value_with_schema prefers prefixItems when
present, falling back to items otherwise. Homogeneous arrays keep the exact same
output as before, so existing snapshots keep loading.
@davidpavlovschi
davidpavlovschi requested a review from a team as a code owner July 30, 2026 21:06
@davidpavlovschi
davidpavlovschi requested review from davidsbatista and removed request for a team July 30, 2026 21:06
@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

@davidpavlovschi is attempting to deploy a commit to the deepset Team on Vercel.

A member of the Team first needs to authorize it.

@CLAassistant

CLAassistant commented Jul 30, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@sjrl
sjrl requested review from sjrl and removed request for davidsbatista July 31, 2026 06:33
@sjrl

sjrl commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Hey @davidsbatista I can take review of this one. I was recently working on this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants