-
Notifications
You must be signed in to change notification settings - Fork 17.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: encoding/json: add "readonly" tag #28143
Comments
Similar #26636 |
Quickly skimming this without reading into details, it's confusing if the |
Intuitively, a I don't really have very string opinions at any rate. At this point I think it's more important to discus the general concept and behaviour; the exact name can be changed easily.
Are there good use cases for that @deanveloper? I can't really think of any myself, but that could be a failure of my imagination. I think it's important to identify practical real-world use cases that can't easily be implemented using exciting features, instead of just saying "hey, it might be nice to have ..." For a
For reasons of future-proofing and security it would be desirable to tag |
I already explained my communication issue, remember that JSON is used in multiple areas, not just in data transmission but also in data storage and data display (which are both arguably "data transmission" but with the JSON ending up in a file/screen rather than a structure) The communication issue in a "readonly" tag comes in when I see it outside the context of a data transmission. A simple example where "readonly" is confusing would be where JSON is used as a storage system, where "reading" is the Unmarshal step, you may think it's safe to read config values into "readonly" tags, especially if you have no intentions of changing your config during execution. This would result in no values being read, as what "readonly" really does is block the Unmarshal step. Although you are correct that naming can be judged later.
This was more meant of a random thought and not really meant to add to our much to the conversation, although it may be useful for a similar case, but where the Unmarshal/Marshal mean Write/Read rather than vice versa.
I definitely agree here. If there's no use, then by all means we don't need a "no-marshal" |
On hold for JSON sweep. |
Hello @rsc, Can you please clarify what you mean by |
@olekukonko I was wondering the same and found this: #27589 (comment) |
I hate pinging you, but is there any chance this can be reviewed @rsc? It's been quite a while and this was created before the current proposal review process. Could it be reviewed with it? I'm not entirely sure what the plans for the mentioned "JSON sweep" are? I still run in to this; and AFAIK there isn't a clear solution, and this change (which should be fairly low-impact as far as I can tell) would be rather helpful 😅 |
A |
Simple cases can be achieved using struct embedding: type UserRW struct {
Name string `json:"name"`
Email string `json:"email"`
}
type UserRO struct {
ID int `json:"id"`
UserRW
CreatedBy int `json:"createdBy"`
CreatedAt time.Time `json:"createdAt"`
} where However, this technique fails at higher orders of nesting. |
Previous discussion: #19423
Note: this proposal focuses on the encoding/json package, but the same could be
applied to other encoding/* packages, especially encoding/xml.
Problem
It is currently hard to marshal json while preventing those fields being set to
arbitrary values by outside input. The
-
tag prevents both unmarshalling andmarshalling.
This is a common requirement in for example REST APIs:
When showing the data to the user (e.g.
GET /user/1
) we want to marshal theID
,CreatedBy
, andCreatedAt
fields; but we don't want to allow users toset these fields on create or update endpoints (e.g.
PUT /user/1
).As far as I can think of, right now it's hard to write a generic fully correct
solution for this:
It's possible to implement the
json.Unmarshaler
interface like so:While this works, it's verbose and difficult to generalize without
reflection complexity. Every type needs to have its own
UnmarshalJSON()
(and/or
UnmarshalXML()
, etc.)A second problem is that in some cases you do want to unmarshal the readonly
fields, for example from tests:
Hence the
DefaultUnmarshal
parameter, which needs to be exported to allowsetting them in other packages.
rsc proposed creating a custom
ReadOnlyString
with a no-opUnmarshalJSON()
in proposal: encoding/json: add "Skip when Unmarshalling" tag #19423. this works well and is not unreasonable, but ifyou want to use e.g.
sql.NullString
; you would need to add a customReadOnlyNullString
as well. Using it in combination with thegithub.com/guregu/null package means
creating two extra types (
readOnlyNullString
andreadOnlyZeroString
).It also doesn't allow easy setting readonly fields from tests.
A third option is to unmarshal the JSON to
interface{}
, modify thedata based on the struct tags, marshal back to JSON, and then unmarshal in to
the type you want.
I have a working implementation of this, and it works for the simple cases.
But the "Unmarshal -> Marshal -> Unmarshal" dance seems needlessly
complicated and inefficient, and dealing with arbitrary JSON is rather
tricky/verbose in Go.
Another solution (which is probably the most common) is to simply use
different structs for different purposes. I am not hugely in favour of this,
as it leads to a lot of duplication.
Proposed solution:
readonly
struct tagWith the
readonly
tags added, the above struct would look like:Regular
Unmarshal()
will not set any of thereadonly
fields; they aresilently ignored:
An option for
json.Decoder
can be added to allow setting thereadonly
fields, similar to
DisallowUnknownFields()
:The
DisallowUnknownFields()
option can be used to error out when readonlyfields are attempted to be set (although this could potentially also be a new
option).
In the previous discussion rsc mentioned that this feature would not "pull its
weight" as it's not common enough of a use case. It's true that it's less
common of a use case, but I don't believe it's terrible uncommon, either.
Implementing this correctly by users is fairly complex, and adding this feature
to the encoding/json package seems – unless I am mistaken – quite simple to the
point of being almost trivial. It will of course increase maintenance burden in
the future, but personally, I think it's a fair trade-off.
Prototype implementation
To test the feasibility and complexity of this change I wrote an implementation
of this proposal, which seems to work well. I can make a CL with an expanded
version of this if this proposal is received well.
The text was updated successfully, but these errors were encountered: