You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Support omitting Struct attributes from encoding/decoding #199
Since Struct types force users to declare all Struct attributes, there's currently no way to hold extra state on a Struct without having it also be encoded/decoded.
Two ways to handle this:
Add omit=True to Meta annotations. This could also be packaged up as a parametrized type, so users could write
Excluded attributes with a leading underscore by default? I'm not sure if we should support this one, it may be confusing behavior. As a reference point, golang's json doesn't encode/decode private struct attributes by default.
Reacted by Jack McIvor, Luo Chen, Damien B, Peter Schutt, Rose Davidson, lorien, saolof, Michael, Teddy Sudol, Dave Hirschfeld and 25 more
Thanks for the motivating use case (and apologies for the delayed response)! I hope to tackle this issue in the next few weeks before issuing a new release.
Since you commented here - as a user, would you want omitted attributes to also be excluded from __init__? Giving a concrete example:
Should __init__ include _private_1 and _private_2 arguments? Or should they be excluded from __init__, and only default values filled in? If they're excluded, that would mean that _private_1 would raise an AttributeError until it's explicitly set, and _private_2 would have an initial value of 0 after __init__.
Honestly I haven't been using the constructors much (decoding from a web request, encoding to a disk cache, decoding from the disk cache) so I don't have a strong opinion. I guess given that the object is valid without those values, the constructor shouldn't require them, but it would be nice if you could include them.
Also I would lean towards excluding attributes with leading underscores by default (without the Omit type), but I could see it being more annoying to have to include them if some API you're using has names like that.
Ok, we finally have the internals setup to make this fairly straightforward to implement.
First, I plan to call these "private" attributes, since that's effectively what they are. A private attribute won't be encoded or decoded, but will have space allocated on its respective struct.
There are some open questions though.
How should this be spelled?
There are two places that field configuration like this can go:
The former I find a bit easier to read, but it adds some complications:
The type annotations would have to be parsed at struct definition time, same as they would for ClassVar. This is doable, but to remain efficient can't handle all cases in the case of postponed annotations.
A Private type annotation by its nature must be applied to a field - a type list[Private[int]] is invalid, but python would happily let the user spell such a type. Using a default value with field ties this annotation to struct fields alone, avoiding this issue.
It's much harder to get this approach to play well with dataclass_transform, treating Private fields differently from public ones.
Right now I'm leaning towards the msgspec.field approach. Many users will already be familiar with dataclasses.field, which includes support for omitting fields from the generated __init__ via dataclasses.field(init=False). This is similar enough to private=True that it shouldn't be that wild of a concept.
Should fields with leading underscores be private by default?
Since we're providing a way to set whether a field is public/private explicitly, users can always override any default behavior chosen here. Since fields with leading underscores are usually treated as private in python by convention, I'm tempted to do so here as well. So the following would be equivalent:
classExample1(Struct):
_field: int=0# private, since the leading underscoreclassExample2(Struct):
_field: int=field(default=0, private=True) # explicitly private
On the other hand, I'd expect most users to never need to define private fields, so maybe inferring private/public status from the field name itself is overreaching.
Are private attributes excluded from __init__?
Right now I'm planning on excluding private attributes from basically everything in msgspec. They'll have space allocated in the __slots__ of the generated Struct type (and corresponding attributes defined), but will be excluded from:
__init__
__copy__
__reduce__ (pickling)
__repr__
__eq__ and other comparison methods
__hash__
__match_args__
encode
decode
This makes them most useful for including extra state during runtime that is initialized after the Struct instance is already created. cached_property usage is related here (#204), since you'd need a slot allocated to store the cached value.
If you have a use case for including private attributes in __init__, please let comment below. If needed, worst case we can add another init parameter for including/excluding a field from __init__. private=True will imply init=False by default, but you can manually re-include it as field(private=True, init=True).
Should private fields require a default value/factory?
Right now all msgspec.Struct types require all fields be initialized as part of the generated __init__. The only way to get an AttributeError for a field on a struct is if you manually del the attribute off an existing struct. I think this makes sense. mypy/pyright type annotations are designed to handle types (or unions of types), but not the presence/absence of an attribute.
Keeping with this design (and assuming private fields are excluded from __init__), it may make sense to enforce that all private fields have a default value (since there's no other way a value could be set).
classExample(Struct):
private1: int|None=field(private=True, default=None) # this is ok, there's a default valueprivate2: int|None=field(private=True) # this is invalid, there's no default value
Summary
In summary, my current leanings on these questions are:
Use msgspec.field for configuring if a field is private.
Make fields with a leading underscore private by default.
Exclude private fields from __init__. We can add a way to override this later if needed.
Require all private fields set a default value.
I don't plan on adding this before the next release (hopefully out next week), but after that whatever choices made here should (hopefully) be fairly straightforward to implement 🤞 .
Reacted by saolof, Michael, Teddy Sudol, Doug Morris, Julien Hany, xiaohuanshu, 叶子, Thibaut Decombe, cswimr, tcp and 3 more
This would be of great use to my current project --- like IcyMidnight, we've got a cache that would be really pointless to encode. The current plan is to clear the cache before encoding, which will work as long as we remember to do it.
Use msgspec.field for configuring if a field is private.
Make fields with a leading underscore private by default.
What happens if you do _something = field(private=False)? I think this should be allowed: a private field (in the Python convention sense) that's not private (in the msgspec sense).
Additionally, automatically treating fields with a leading underscore as private would break backwards compatibility. I found a few examples of Structs with _private fields which makes me think that making private fields an explicit, opt-in choice is a better idea.
Really looking forward to this feature, I hope you have time to work on it soon.
Adding a private=True parameter to field() seems fine to me.
Private omitted fields are the only way to have self-referential Structs.
e.g. a nested and occasionally recursive data structure with access to the _root node.
We can also use it to compute and store the path in that data structure because Structs aren't aware of their parent nodes.
Right now I'm leaning towards the msgspec.field approach. Many users will already be familiar with dataclasses.field, which includes support for omitting fields from the generated init via dataclasses.field(init=False). This is similar enough to private=True that it shouldn't be that wild of a concept.
If this is still up for debate, I concur that this is the right approach.
If you have a use case for including private attributes in init, please let comment below. If needed, worst case we can add another init parameter for including/excluding a field from init. private=True will imply init=False by default, but you can manually re-include it as field(private=True, init=True).
classPrimitive(Struct):
iid: int=field(private=True) # internal ID, think auto-incrementing sequence meant to be privateeid: str=field(private=False) # external ID, some sort of public-exposed string identifier
In this use case, the object when fetched from the DB would have both IDs so the application layer has them available. But, when serialized as an HTTP response, we strip out the private iid.
Use msgspec.field for configuring if a field is private.
Agreed.
Make fields with a leading underscore private by default.
This is a nice API 😎
Exclude private fields from init. We can add a way to override this later if needed.
See my comment above. I am not sold on this one. The two paramater approach makes more sense to me as they are different use cases. I am fine if private=True implies init=False. But, I want to have the flexibility for private=True, init=True.
Require all private fields set a default value.
I guess I am okay with this, as long as I can still require it as an __init__. But once that is allowed, it seems to me this requirement falls off.
I came here looking for this functionality so I can store some extra metadata on a Struct that I don't want to be serialized or part of the __init__.
I'm envisaging an api where I can have an alternative constructer which can take optional (metadata) kwargs and store them on the struct without having them be included in the serialized representation - e.g.
My actual use-case is a bit more complex but it boils down to wanting metadata associated with my objects but only for their lifetime in the current interpreter.
Another example, of something I'd like to do is to attach an fsspec.AbstractFileSystem to the class which would then be used in a save method to serialize itself and write to disk - e.g.
msgspec doesn't know how to serialize an AbstractFileSystem, and I wouldn't want it to be serialized along with my actual data, however it would be convenient to be able to store it on the instances.
Assuming fields starting with underscore are going to be private by default, what would happen in the case of _timestamp? Would an explicit name, different from the one declared in the class, be able to differentiate and force a non-private flag by default?
In this case, assuming prior behaviour, without code intervention, there's no way for the user to force _api_type to be serialized without rewriting the class and passing private=False. This would break existing code/software in unexpected ways, I suppose.
Having said all that, I'm eagerly awaiting such a functionality. I've already stumbled upon two or three use cases where it would be useful to disable serialization of specific fields. The current alternative is to just create two different classes, one being specifically for serialization, but that may be too much of a hassle in most cases.
Hi,
This would be very useful as others have said, I am needing it today and that would make my code much cleaner ;)
Just one important thing, please don't do the same kind of shenanigans as attrs by interpreting _xxx has something, in that case 'private'.
It can and will break previous code, lead to unintended behavior when someone is following an external spec that has _xxx and do not know this.
And more generally it is against the python core tenet of "Explicit is better than implicit". Keystroke aversion is a curse :)
Finally. the = field( construct is used everywhere.
And also msgspec is awesome :)
Reacted by 叶子, Adam Hopkins, Noah Bradatanu, beskep, Kero Liang, Nicholas Renninger, inaiur, Shlomo Zalman Rabinowitz, Teddy Sudol, srydz-aiclearing and 2 more
Ultimately, these were extremely simple structs, and the benchmarks didn't include encoding and decoding. Also, I didn't really verify that the benchmark is written well.
Note: WeakrefUser refers to User struct with a weakkeydictionary. Unhashable user uses direct access to self with dict = True, and OuterDictUser is the one that uses a dict with objects ids to keep track. All the structs are above.
This feature is useful and should be implemented like it was being discussed in the start, I don't think we should just do some work around to achieve this, also i don't recommend the idea of private, i would rather have a boolean encode=True/False or exclude_from_encoding=True/False rather than this private thing where you add the additional complexity of variables starting with _. I also have a use case for this that's why i am here
In summary, my current leanings on these questions are:
Use msgspec.field for configuring if a field is private.
Make fields with a leading underscore private by default.
Exclude private fields from __init__. We can add a way to override this later if needed.
Require all private fields set a default value.
@jcrist Very excited to see this proposal happen, and I agree with everything here except "make fields with a leading underscore private by default". A few reasons I think this should not be included, besides being a breaking change:
I think your own counter-argument is 100% correct: "I'd expect most users to never need to define private fields, so maybe inferring private/public status from the field name itself is overreaching."
Explicit is better than implicit: I would expect all cases where a developer is storing cached data on the struct and does not want that field to participate in the normal encoding/decoding process to explicitly add private=True to the field, regardless of the leading underscore, so that it's clear to anyone reading (incl. future self) that this field is not participating in encoding/decoding without having to look up the current convention in the msgspec docs.
In most cases of storing cached data on the field, I suspect that we actually don't want the cached data to be "python-private". For example: we have raw polygon coordinates as the decoded field, and a shapely Polygon as the computed field. In this case, we are actually going to be using the shapely Polygon, not the raw coordinates, throughout the code, so we don't want it "python-private" at all! Which removes all the value of a private convention for most use-cases, I think, as we'd have to specify private=True anyway.
There should be one way to do it: what would be the worst for readability is a mixture of the 2: some fields declared private=True because we don't want them prefixed by underscores, and some not since we do want them prefixed by underscores and then potentially others with private=False to opt out: that would be very weird. Whether or not the field is python-private is totally orthogonal to whether or not we want it decoded/encoded.
A convention like this would inappropriately mix python conventions with json conventions. Some users will nix the python conventions and make the field name the same as the json key to avoid having to rename it (this would be especially true for dynamically generated models), and some JSON keys are prefixed by underscores. This does not necessarily have anything to do with Python conventions.
There are less-surprising ways to reduce boilerplate: we can simply define Private = field(default=None, private=True) once and then do foo: Bar = Private from then on. Or, if there were to be a built-in convention, a default of None on private fields (allowing foo: Bar = field(private=True)) would be far less surprising than a "decoder skips _data" convention.
Reacted by Teddy Sudol, Thibaut Decombe, 叶子, Ofek Lev and Marco Garbelini
Activity
IcyMidnight commentedon Nov 14, 2022
Yes please! I would like to cache some (expensive) computed values in my objects, but not serialize them.
jcrist commentedon Nov 28, 2022
Thanks for the motivating use case (and apologies for the delayed response)! I hope to tackle this issue in the next few weeks before issuing a new release.
Since you commented here - as a user, would you want omitted attributes to also be excluded from
__init__? Giving a concrete example:Should
__init__include_private_1and_private_2arguments? Or should they be excluded from__init__, and only default values filled in? If they're excluded, that would mean that_private_1would raise anAttributeErroruntil it's explicitly set, and_private_2would have an initial value of0after__init__.IcyMidnight commentedon Nov 28, 2022
Honestly I haven't been using the constructors much (decoding from a web request, encoding to a disk cache, decoding from the disk cache) so I don't have a strong opinion. I guess given that the object is valid without those values, the constructor shouldn't require them, but it would be nice if you could include them.
Also I would lean towards excluding attributes with leading underscores by default (without the Omit type), but I could see it being more annoying to have to include them if some API you're using has names like that.
jcrist commentedon Jan 26, 2023
Ok, we finally have the internals setup to make this fairly straightforward to implement.
First, I plan to call these "private" attributes, since that's effectively what they are. A private attribute won't be encoded or decoded, but will have space allocated on its respective struct.
There are some open questions though.
How should this be spelled?
There are two places that field configuration like this can go:
In the type annotation
An option in
msgspec.fieldThe former I find a bit easier to read, but it adds some complications:
ClassVar. This is doable, but to remain efficient can't handle all cases in the case of postponed annotations.Privatetype annotation by its nature must be applied to a field - a typelist[Private[int]]is invalid, but python would happily let the user spell such a type. Using a default value withfieldties this annotation to struct fields alone, avoiding this issue.dataclass_transform, treatingPrivatefields differently from public ones.Right now I'm leaning towards the
msgspec.fieldapproach. Many users will already be familiar withdataclasses.field, which includes support for omitting fields from the generated__init__viadataclasses.field(init=False). This is similar enough toprivate=Truethat it shouldn't be that wild of a concept.Should fields with leading underscores be private by default?
Since we're providing a way to set whether a field is public/private explicitly, users can always override any default behavior chosen here. Since fields with leading underscores are usually treated as private in python by convention, I'm tempted to do so here as well. So the following would be equivalent:
On the other hand, I'd expect most users to never need to define private fields, so maybe inferring private/public status from the field name itself is overreaching.
Are private attributes excluded from
__init__?Right now I'm planning on excluding
privateattributes from basically everything inmsgspec. They'll have space allocated in the__slots__of the generatedStructtype (and corresponding attributes defined), but will be excluded from:__init____copy____reduce__(pickling)__repr____eq__and other comparison methods__hash____match_args__encodedecodeThis makes them most useful for including extra state during runtime that is initialized after the Struct instance is already created.
cached_propertyusage is related here (#204), since you'd need aslotallocated to store the cached value.If you have a use case for including
privateattributes in__init__, please let comment below. If needed, worst case we can add anotherinitparameter for including/excluding a field from__init__.private=Truewill implyinit=Falseby default, but you can manually re-include it asfield(private=True, init=True).Should private fields require a default value/factory?
Right now all
msgspec.Structtypes require all fields be initialized as part of the generated__init__. The only way to get anAttributeErrorfor a field on a struct is if you manuallydelthe attribute off an existingstruct. I think this makes sense.mypy/pyrighttype annotations are designed to handle types (or unions of types), but not the presence/absence of an attribute.Keeping with this design (and assuming private fields are excluded from
__init__), it may make sense to enforce that all private fields have a default value (since there's no other way a value could be set).Summary
In summary, my current leanings on these questions are:
msgspec.fieldfor configuring if a field is private.__init__. We can add a way to override this later if needed.I don't plan on adding this before the next release (hopefully out next week), but after that whatever choices made here should (hopefully) be fairly straightforward to implement 🤞 .
Solumin commentedon Jul 11, 2023
This would be of great use to my current project --- like IcyMidnight, we've got a cache that would be really pointless to encode. The current plan is to clear the cache before encoding, which will work as long as we remember to do it.
What happens if you do
_something = field(private=False)? I think this should be allowed: a private field (in the Python convention sense) that's not private (in themsgspecsense).Additionally, automatically treating fields with a leading underscore as private would break backwards compatibility. I found a few examples of Structs with
_privatefields which makes me think that making private fields an explicit, opt-in choice is a better idea.Really looking forward to this feature, I hope you have time to work on it soon.
illeatmyhat commentedon Aug 10, 2023
Adding a
private=Trueparameter tofield()seems fine to me.Private omitted fields are the only way to have self-referential Structs.
e.g. a nested and occasionally recursive data structure with access to the
_rootnode.We can also use it to compute and store the path in that data structure because Structs aren't aware of their parent nodes.
FeryET commentedon Sep 19, 2023
Hi. Any update on this? I'm confused whether the library has this attribute.
ahopkins commentedon Sep 24, 2023
If this is still up for debate, I concur that this is the right approach.
In this use case, the object when fetched from the DB would have both IDs so the application layer has them available. But, when serialized as an HTTP response, we strip out the private
iid.Agreed.
This is a nice API 😎
See my comment above. I am not sold on this one. The two paramater approach makes more sense to me as they are different use cases. I am fine if
private=Trueimpliesinit=False. But, I want to have the flexibility forprivate=True, init=True.I guess I am okay with this, as long as I can still require it as an
__init__. But once that is allowed, it seems to me this requirement falls off.Solumin commentedon Nov 21, 2023
Would private fields also be excluded from
__struct_fields__?dhirschfeld commentedon Nov 23, 2023
I came here looking for this functionality so I can store some extra metadata on a Struct that I don't want to be serialized or part of the
__init__.I'm envisaging an api where I can have an alternative constructer which can take optional (metadata) kwargs and store them on the struct without having them be included in the serialized representation - e.g.
My actual use-case is a bit more complex but it boils down to wanting metadata associated with my objects but only for their lifetime in the current interpreter.
dhirschfeld commentedon Nov 23, 2023
Another example, of something I'd like to do is to attach an
fsspec.AbstractFileSystemto the class which would then be used in asavemethod to serialize itself and write to disk - e.g.msgspecdoesn't know how to serialize anAbstractFileSystem, and I wouldn't want it to be serialized along with my actual data, however it would be convenient to be able to store it on the instances.c0x65o commentedon Apr 22, 2024
Any updates on this?
oxysoft commentedon Nov 30, 2024
pretty crucial feature to have
flisboac commentedon Dec 18, 2024
@jcrist wouldn't this break existing code? For example:
Assuming fields starting with underscore are going to be private by default, what would happen in the case of
_timestamp? Would an explicitname, different from the one declared in the class, be able to differentiate and force a non-private flag by default?A more extreme example:
In this case, assuming prior behaviour, without code intervention, there's no way for the user to force
_api_typeto be serialized without rewriting the class and passingprivate=False. This would break existing code/software in unexpected ways, I suppose.Having said all that, I'm eagerly awaiting such a functionality. I've already stumbled upon two or three use cases where it would be useful to disable serialization of specific fields. The current alternative is to just create two different classes, one being specifically for serialization, but that may be too much of a hassle in most cases.
dream2333 commentedon Jan 13, 2025
Any updates?
Annakan commentedon Jan 24, 2025
Hi,
This would be very useful as others have said, I am needing it today and that would make my code much cleaner ;)
Just one important thing, please don't do the same kind of shenanigans as attrs by interpreting _xxx has something, in that case 'private'.
It can and will break previous code, lead to unintended behavior when someone is following an external spec that has _xxx and do not know this.
And more generally it is against the python core tenet of "Explicit is better than implicit". Keystroke aversion is a curse :)
Finally. the
= field(construct is used everywhere.And also msgspec is awesome :)
SZRabinowitz commentedon Apr 30, 2025
I spent 3 days stuck on this (including burning $5 asking claude code to implement it). Turns out this is pretty trivial:
This is possible since version 0.14.0.
just use
dict=Truewhen constructing aStruct:for more info on
dictparameter see #331 #324 #204@jcrist Should I write a short guide for the docs?
EDIT:
You can add a
set_secretfunction if you want:This creates an even nicer syntax for initializing the
Struct:Or with a decoder:
Solumin commentedon Apr 30, 2025
dictdoesn't work with frozen structs.SZRabinowitz commentedon Apr 30, 2025
for frozen structs you can use a reference dict. It wont perform is nicely as though.
let me know if anything is unclear.
Cheers,
Shlomo
SZRabinowitz commentedon Apr 30, 2025
Actually, if your Struct is frozen, you can use a weakkeydictionary. and it has some performance gains compared to the regular dictionary
SZRabinowitz commentedon Apr 30, 2025
A very basic, chatgpt written benchmark showed this:
Ultimately, these were extremely simple structs, and the benchmarks didn't include encoding and decoding. Also, I didn't really verify that the benchmark is written well.
Note:
WeakrefUserrefers toUserstruct with a weakkeydictionary.Unhashableuser uses direct access toselfwithdict = True, andOuterDictUseris the one that uses a dict with objectsids to keep track. All the structs are above.iyadzargar commentedon Aug 15, 2025
This feature is useful and should be implemented like it was being discussed in the start, I don't think we should just do some work around to achieve this, also i don't recommend the idea of private, i would rather have a boolean encode=True/False or exclude_from_encoding=True/False rather than this private thing where you add the additional complexity of variables starting with
_. I also have a use case for this that's why i am herei just want to encode tokens and last_refill since that's all i need to save in redis but not the capacity or the refill rate
HansBrende commentedon Oct 18, 2025
@jcrist Very excited to see this proposal happen, and I agree with everything here except "make fields with a leading underscore private by default". A few reasons I think this should not be included, besides being a breaking change:
private=Trueto the field, regardless of the leading underscore, so that it's clear to anyone reading (incl. future self) that this field is not participating in encoding/decoding without having to look up the current convention in the msgspec docs.Polygonas the computed field. In this case, we are actually going to be using the shapely Polygon, not the raw coordinates, throughout the code, so we don't want it "python-private" at all! Which removes all the value of a private convention for most use-cases, I think, as we'd have to specifyprivate=Trueanyway.private=Truebecause we don't want them prefixed by underscores, and some not since we do want them prefixed by underscores and then potentially others withprivate=Falseto opt out: that would be very weird. Whether or not the field is python-private is totally orthogonal to whether or not we want it decoded/encoded.Private = field(default=None, private=True)once and then dofoo: Bar = Privatefrom then on. Or, if there were to be a built-in convention, a default ofNoneon private fields (allowingfoo: Bar = field(private=True)) would be far less surprising than a "decoder skips _data" convention.tbronson commentedon Dec 9, 2025
Just to add in another workaround suggestion for anyone wanting to skip fields during encoding, using some undocumented behavior:
Any field with its value set to msgspec.UNSET will be skipped during encoding. Caveats to note:
theacodes commentedon May 26, 2026
I would also like to add that sometimes its useful to specify which fields to omit when using the
encoder/converters, for example:Pydantic supports this in a similar way: https://pydantic.dev/docs/validation/latest/concepts/serialization/#as-parameters-to-the-serialization-methods