Skip to content

Document differences between structs.asdict/astuple and to_builtins#1025

Open
Siyet wants to merge 4 commits intomainfrom
778-document-asdict-vs-to-builtins
Open

Document differences between structs.asdict/astuple and to_builtins#1025
Siyet wants to merge 4 commits intomainfrom
778-document-asdict-vs-to-builtins

Conversation

@Siyet
Copy link
Copy Markdown
Collaborator

@Siyet Siyet commented Apr 20, 2026

Summary

Clarify how msgspec.structs.asdict / msgspec.structs.astuple differ from msgspec.to_builtins. The three functions all produce "plain Python" output from a Struct, but they serve different roles and apply different settings:

  • structs.asdict / structs.astuple: Python-level, modeled on dataclasses.asdict / dataclasses.astuple. One-to-one conversion, no recursion into nested struct-like types, omit_defaults and UNSET are ignored.
  • to_builtins: part of the encoder pipeline. Applies omit_defaults, omits UNSET fields, recursively converts nested Struct / dataclass / attrs / TypedDict / NamedTuple values, and applies enc_hook / str_keys / order.

The existing to_builtins docstring said it is "mainly useful for adding msgspec support for other protocols", which underplays its role when partial-processing a message (e.g. handing a dict to httpx, which is the motivating case in #778).

Motivation

Users repeatedly trip over this distinction, reach for asdict as the natural API, and are surprised that encoder settings don't apply. The discoverability of to_builtins as the right tool is also low. A non-exhaustive sample of closed issues:

Behavior-only change would be a breaking change to asdict / astuple, and #748 / #817 both confirmed the current behavior is intentional. So this PR is docs-only: make the boundary explicit so users stop bouncing off asdict and onto to_builtins via stackoverflow / github search.

Changes

  • docs/usage.rst: new Converting to and from Builtin Types section that pairs to_builtins / convert with encode / decode, and contrasts them with structs.asdict / astuple. Also adds a to-builtins-vs-asdict cross-reference label.
  • docs/structs.rst: omit_defaults section now notes that the setting only affects encoders and to_builtins.
  • docs/supported-types.rst: UNSET section now notes the same for UNSET omission.
  • src/msgspec/_core.c: docstrings for structs.asdict, structs.astuple, and to_builtins updated to state these semantics explicitly and to cross-link each other.

Notes

Fixes #778.

Siyet added 4 commits April 20, 2026 20:01
Add a usage docs section explaining that to_builtins is the encoding half
(respects omit_defaults, UNSET, and recurses into nested struct-like types)
while structs.asdict/astuple perform a one-to-one conversion following the
dataclasses.asdict/astuple contract.

Cross-reference the new section from the omit_defaults and UNSET sections,
and update the C docstrings for asdict, astuple, and to_builtins.

Fixes #778. Supersedes #780.
The previous wording only called out omit_defaults and UNSET, but
to_builtins differs from asdict/astuple in more ways: rename, tag
injection, array_like, recursion into nested Struct/dataclass/attrs/
TypedDict/NamedTuple values, and all the value-level conversions
(bytes to base64, datetime to ISO 8601, UUID/Decimal to string, set
to list, Enum to member value).

This matters because prior confused-user reports cover all of these
axes: #748 tripped on rename, #830 on array_like plus recursion.
The msgspec docs use default_role='obj', so single-backtick references
like `msgspec.to_builtins` and `dataclasses.dataclass` auto-link to
the api page or (via intersphinx) to the Python stdlib docs. Switch
Python-object references from double backticks (literal) to single
backticks so they link, and use fully-qualified names so they resolve.
Double backticks are kept for kwarg names and setting values that
aren't importable objects (rename, tag, omit_defaults, array_like,
enc_hook, str_keys, order, builtin_types).

Also add an attrs_ target in usage.rst for the attrs library link.
- Unify the order of Struct-level settings across docstrings and usage
  docs to `rename, omit_defaults, array_like, tag`.
- Split the 60-word sentence at the end of the usage section into three
  shorter paragraphs (what asdict/astuple do, what they don't do, when
  to prefer to_builtins).
- Condense the value-level conversions bullet in the `to_builtins`
  docstring; the full list now lives in the usage docs via a
  cross-reference.
- Soften the closing sentence of the `to_builtins` docstring: 'do none
  of this' -> 'perform a plain one-to-one conversion, applying none of
  the above'.
- In the usage bullet list, split 'struct-level settings' and 'UNSET
  omission' into separate bullets so UNSET isn't lumped in with settings.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cover omit_defaults handling in asdict and to_builtins documentation

1 participant