Skip to content

Default not omitted with custom default factory #1032

Description

@Leo-Marbehan

Description

In my project, I have structs that contains list fields. I would like to omit these fields when the list is empty to reduce the size of the saved files.

When using the list constructor as the default factory or a default empty list, it works fine. However, the former conflicts with the type linter, and I want to avoid the latter (shared across all instances).

When I use a custom factory function, the field always appear in the output.

It may be the correct behavior, but I did not find any mention of this in the docs.

Reproduction

I ran this using Python 3.14 and msgspec 0.21.1.

from __future__ import annotations

import msgspec


class Fruit(msgspec.Struct):
    name: str = msgspec.field(name="name")


def fruits_factory() -> list[Fruit]:
    return []


class Basket(msgspec.Struct, omit_defaults=True):
    size: int = msgspec.field(name="size")
    material: str = msgspec.field(name="material", default="wicker")
    fruits: list[Fruit] = msgspec.field(name="fruits", default_factory=fruits_factory)


class BasketWithListConstructor(msgspec.Struct, omit_defaults=True):
    size: int = msgspec.field(name="size")
    material: str = msgspec.field(name="material", default="wicker")
    fruits: list[Fruit] = msgspec.field(name="fruits", default_factory=list)  # Type error because list is untyped


class BasketWithDefaultValue(msgspec.Struct, omit_defaults=True):
    size: int = msgspec.field(name="size")
    material: str = msgspec.field(name="material", default="wicker")
    fruits: list[Fruit] = msgspec.field(name="fruits", default=[])  # Shared mutable default value, should be avoided


if __name__ == "__main__":
    basket = Basket(size=5)

    print(msgspec.json.encode(basket))
    # Expected output: b'{"size":5}'
    # Actual output: b'{"size":5,"fruits":[]}'

    basket_with_list_constructor = BasketWithListConstructor(size=5)
    print(msgspec.json.encode(basket_with_list_constructor))
    # Output: b'{"size":5}'

    basket_with_default_value = BasketWithDefaultValue(size=5)
    print(msgspec.json.encode(basket_with_default_value))
    # Output: b'{"size":5}'

    print(f"Using msgspec {msgspec.__version__}")

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