Skip to content

Allow omit_defaults to exclude fields when encoded value is {} (empty dict) #645

Closed

Description

@wikiped

Description

The following code:

import msgspec as ms


class Sub(ms.Struct, omit_defaults=True):
    f1: str = ""
    f2: int = 0


class Struct(ms.Struct, omit_defaults=True, tag=True):
    f1: int = 1
    sub: Sub = ms.field(default_factory=Sub)


print(ms.yaml.encode(Struct()).decode())
print(ms.json.encode(Struct()).decode())

y = """
type: Struct

"""
j = '{"type":"Struct"}'

print()
print(ms.yaml.decode(y, type=Struct))
print(ms.json.decode(j, type=Struct))

Will output:

type: Struct
sub: {}

{"type":"Struct","sub":{}}

Struct(f1=1, sub=Sub(f1='', f2=0))
Struct(f1=1, sub=Sub(f1='', f2=0))

Although it is clear why the empty dict is being encoded, it is still desirable to avoid that, given that decoding absent fields does not cause any errors when the empty constructor (i.e. empty dict) is producing the expected default.

It would seem that recursive check with matches_default on subclasses of msgspec.Struct might do the job, but this would probably mean some hit on perfomance (?).

Perhaps there is a way to check the resulting '{}' value and omit it instead?

Thank you for considering this.

Activity

  1. Siyet commented on Jun 16, 2026

    @Siyet
    Contributor

    omit_defaults does not recursively match nested struct defaults, and it does not call a default_factory (here default_factory=Sub) during detection. This is by design: the check stays O(1) on the encode hot path and never invokes user code or walks nested structures. Recursively encoding a value and comparing it against the default would add real per-encode cost, which is exactly what this optimization avoids (same constraint as #1032).

    Clarifying the default_factory limitation in the docs in #1076, closing as working-as-intended.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions