Description
This proposal is for a new syntax for struct tags, one that is formally defined in the grammar and can be validated by the compiler.
Problem
The current struct tag format is defined in the spec as a string literal. It doesn't go into any detail of what the format of that string might look like. If the user somehow stumbles upon the reflect package, a simple space-separated, key:"value" convention is mentioned. It doesn't go into detail about what the value might be, since that format is at the discretion of the package that uses said tag. There will never be a tool that will help the user write the value of a tag, similarly to what gocode does with regular code. The format itself might be poorly documented, or hard to find, leading one to guess what can be put as a value. The reflect package itself is probably not the biggest user-facing package in the standard library as well, leading to a plethora of stackoverflow questions about how multiple tags can be specified. I myself have made the error a few times of using a comma to delimit the different tags.
Proposal
EDIT: the original proposal introduced a new type. After the initial discussion, it was decided that there is no need for a new type, as a struct type or custom types whose underlying types can be constant (string/numeric/bool/...) will do just as well.
A tag value can be either a struct, whose field types can be constant, or custom types, whose underlying types are constant. According to the go spec, that means a field/custom type can be either a string, a boolean, a rune, an integer, a floating-point, or a complex number. Example definition and usage:
package json
type Rules struct {
Name string
OmitEmpty bool
Ignore bool
}
func processTags(f reflect.StructField) {
// reflect.StructField.Tags []interface{}
for _ ,t := range f.Tags {
if jt, ok := t.(Rules); ok {
...
break
}
}
}
package sqlx
type Name string
Users can instantiate values of such types within struct
definitions, surrounded by [
and ]
and delimited by ,
. The type cannot be omitted when the value is instantiated.
package mypackage
import json
import sqlx
type MyStruct struct {
Value string [json.Rules{Name: "value"}, sqlx.Name("value")]
PrivateKey []byte [json.Rules{Ignore: true}]
}
Benefits
Tags are just types, they are clearly defined and are part of a package's types. Tools (such as gocode) may now be made for assisting in using such tags, reducing the cognitive burden on users. Package authors will not need to create "value" parsers for their supported tags. As a type, a tag is now a first-class citizen in godoc. Even if a tag lacks any kind of documentation, a user still has a fighting chance of using it, since they can now easily go to do definition of a tag and just look up its fields, or see the definition in godoc. Finally, if the user has misspelled something, the compiler will now inform them of an error, instead of it occurring either at runtime, or being silently ignored as is the case right now.
Backwards compatibility
To preserve backwards compatibility, string-based tags will not be removed, but merely deprecated. To ensure a unified behavior across libraries, their authors should ignore any string-based tags if any of their recognized structured tags have been included for a field. For example:
type Foo struct {
Bar int `json:"bar" yaml:"bar,omitempty"` [json.OmitEmpty]
}
A hypothetical json library, upon recognizing the presence of the json.OmitEmpty
tag, should not bother looking for any string-based tags. Whereas, the yaml library in this example, will still use the defined string-based tag, since no structured yaml tags it recognizes have been included by the struct author.
Side note
This proposal is strictly for replacing the current stuct tags. While the tag grammar can be extended to be applied to a lot more things that struct tags, this proposal is not suggesting that it should, and such a discussion should be done in a different proposal.
Activity
ianlancetaylor commentedon Jan 31, 2018
Related to #20165, which was recently declined. But this version is better, because it proposes an alternative.
ianlancetaylor commentedon Jan 31, 2018
I don't see any special need for a new
tag
type. You may as well simply say that a struct field may be followed by a comma separated list of values, and that those values are available via reflection on the struct.On the other hand, something this proposal doesn't clearly address is that those values must be entirely computable at compile time. That is not a notion that the language currently defines for anything other than constants, and it would have to be carefully spelled out to decide what is permitted and what is not. For example, can a tag, under either the original definition or this new one, have a field of interface type?
urandom commentedon Jan 31, 2018
@ianlancetaylor
You raise an interesting point. A
struct
will pretty much have the same benefits as a newtag
type would. I imagine it would probably make the implementation a bit simpler. Other types might only be useful if they are the underlying type of a custom one, and as such one would have to use them explicitly, otherwise there might be ambiguity when a constant is provided directly:vs what I would consider an invalid usage:
For your second point, I assumed that it would be clear that any value for any field of a tag has to be a constant. Such a "restriction" makes it clear what can and cannot be a field type, and will rule out having a struct as a field type (or an interface, in your example).
dlsniper commentedon Feb 5, 2018
I wonder if we could solve this without having to change the language, and even better, in Go 1.X rather than waiting for Go 2. As such, I've tried to understand the problem as well as the proposed solution and came up with a different approach to the problem, please see below.
First, the problem.
I think the description starts from the wrong set of assumptions:
There can totally be a tool that understands how these flags work and allow users to define custom tags and have them validated.
One such tool might for example benefit from "magic" comments in the code, for example, the structure proposed could be "annotated" with a comment like
// +tag
.This would of course have the advantage of not having to force the change in the toolchain, with the downside that you'd need to have the tool to validate this instead of the compiler. The values should be json, for example:
More details can be put into this on how this should be a single tag per package, the struct must be exportable, and so on (which the current proposal also does not address).
This sounds like a problem one could be able to fix with a CL / PR to the documentation of the package which specifically improves it by documenting the tag available or how to use these struct tags.
Should the above proposal with "annotating" a struct in a package work, this means that the tools could also fix the problem of navigating to the definition of tag.
Furthermore, the original proposal adds the problem that
tag
now is a keyword and cannot be used as in regular code. Imho, should any new keyword be added in Go 2, there should be a really good reason to do so and it should be kept in mind that it would make the porting of existing Go 1 sources that much harder given how now the code needs to be refactored before being ported over.The downside of my proposal is that this requires people to use non-compiler tools. But given how govet is now partially integrated in go test, this check could also be added to that list.
Tools that offer completion to users can be adapted to fulfill the requirement of assisting the user in writing the tag, all without having the language changed with an extra keyword added.
And should the compiler ever want to validate these tags, the code would already be there in govet,
creker commentedon Feb 5, 2018
@dlsniper
You can always make compiler a bit smarter to understand context and when a keyword is a keyword.
tag
keyword would appear in a very specific context which compiler could easily detect and understand. No code could ever be broken with that new keyword. Other languages do that and have no problem adding new contextual keywords without breaking backwards compatibility.As for your proposal, for me adding another set of magic comments further establishes opinion that there's something wrong with the design of the language. Every time I look at these comments they look out of place. Like someone forgot to add some feature and, in order to not break things, shoved everything into comments. There's plenty of magic comments already. I think we should stop and implement proper Go features and not continue developing another language on top of Go.
ianlancetaylor commentedon Feb 5, 2018
As I said above, though, I see no advantage at all to using
tag
rather thanstruct
.This proposal still needs more clarity on precisely what is permitted in a tag type, whatever we call it. It's not enough to say "it has to be a constant." We need to spell out precisely what that means. Can it be a constant expression? What types are the fields permitted to have?
urandom commentedon Feb 5, 2018
This seems to make the problem worse, to be honest. Instead of making tags easier to write and use, you are now introducing more magic comments. I'm sure I'm not the only one opposed to such solutions, as such comments are very confusing to users (plenty of questions on stackoverflow). Also, what happens when someone puts // +tag before multiple types?
This not only ignores a major part of the problem, illustrated by the proposal (syntax), but also makes it harder to write.
Should we address the obvious? Honest question, I skipped some things as I thought they were too obvious to write.
It's still in the reflect package. Why would an average user ever go and read the reflect package. It's index alone is larger that the documentation of some packages.
I edited my original proposal to remove the inclusion of a new type. This was discussed in the initial discussion with @ianlancetaylor, and I was hoping further discussion would include that as well.
urandom commentedon Feb 5, 2018
I've edited the proposal to add more information as to what types are permitted as tags.
giniedp commentedon Feb 5, 2018
i like the idea of the proposal. I would love to be able to write Metadata that is then checked at compile time, and that i do not need to parse the metadata at runtime.
The updated proposal makes sense to me. A Metadata is simply a struct.
Also the syntax is ok for the tags. It event lets me write the metadata line by line
1. initial proposal
That appears even to be readable (at least to me). Fields and annotations are clearly distinguishable, even without syntax highlighting.
Now, i know this is about tags only but if we would want to add metadata to other things than struct fields, i would suggest to put the metadata above the thing that you annotate instead of having it behind.
2 examples that arise to me, are C# Attributes and Java Annotations. Lets see how they would look like on go struct fields
2. C# like
That is less readable than 1.
3. Java like
That is less readable than 1. but way cleaner than 2.
Now in go we already have a syntax for multiple imports and multiple constants. Lets try that
4. Go like
That is less compact. Removing the
meta
keyword wouldnt help i think. Neither when using square brackets5. with square brackets
The single statement looks like 2. C# like and the multiline statement is still not as compact as i would like it to be.
So far i still like the 3. Java like style best. However, if metadata should not be applied to anything other than struct fields (ever) then i prefer the 1. initial proposal style. Now if there are some legal issues with stealing a syntax from another language (i am not a lawyer) then i could think of following
6. hash
urandom commentedon Feb 5, 2018
Having the field tags before the field declaration is not very readable, compared to having them afterwards. For the same reason that it is more readable to have the type of a variable after the variable. When you start reading the struct definition, you come upon some meta information about something you haven't yet read. Currently, you know that a PrimaryKey is a byte array, and it is ignored for json marshaling. With your suggestioun, You know that something will be ignored for json marshaling, and afterwards you learn that what is ignored will be a primary key, which is a byte array.
giniedp commentedon Feb 5, 2018
I understand your point and i partially agree on that. Having metadata on tags only, your suggestion looks best (i might want to omit the comma in multiline though)
My intention is to suggest a syntax that might work on structs and methods. Those elements have their own body. Adding metadata behind the body might push it out of sight if the implementation is lengthy. I think metadata should live somewhere near to the name of the element that it annotates and my natural choice is above that name, since below is the body.
Syntax highlighting helps to spot the parts of the code you are interested in. So if you are interested in reading the struct definition, your eye will skip the metadata syntax.
71 remaining items
deefdragon commentedon May 12, 2023
One thing that I don't think I have seen addressed in this so far is that of multiple tags in the same package. The current string tags allow for using different tags in the same way. The specific example I know of is the go-playground/validator package. I use this feature because I have some cases where I need to validate parts of a struct in one way, but other times need to validate it differently. (specifically the creation path vs the update path have different requirements.)
urandom commentedon May 15, 2023
@deefdragon
Could you give a specific example with such usage?
deefdragon commentedon May 16, 2023
This is just a one field example, but in this example, the User struct can only be created if the PaymentStatus is set to GoodGraces (preventing forgetting it or using the incorrect value for a new user), while it can be updated to one of the three items listed.
This is what a create might look like.
Giving it some thought, I suspect that a Name/Key/Tag field in the structured tag would be fine to differentiate, and could be set by any library that needs it, but I thought it was important to acknowledge the current lack of discussion around custom tag names.
DeedleFake commentedon May 16, 2023
You could probably do something like
Then, instead of setting the tag name to check, you could do something like
Merovius commentedon May 16, 2023
We could also allow multiple typed tags per type. Allowing you to do
The tags would be exposed by
reflect
as a slice, so thevalidator
package could iterate over them and interpret multiple tags of the same type however it wants.More complex structures are not possible to express (so you couldn't also have a concept of "and", only of "or", for example). But I also don't think we'd want that anyways. TBQH in my opinion what is already done via struct tags is too "magic" anyways.
matt1484 commentedon Jun 9, 2023
not sure what the correct final solution should be, but my library uses structs to validate struct tags, so something to consider or at least a workaround for now. https://github.com/matt1484/spectagular/
BenjamenMeyer commentedon Jul 26, 2023
I've recently gotten into doing some custom marshaling/unmarshaling using struct tags in Go1, and noticed that there is a lot of commonality between the two sides when it comes to parsing the structure. Perhaps the solution here is to define a format for the string and then provide additional capabilities in Go itself so folks don't need to do all the heavy lifting to interact with struct tags. An API like the following could work:
StructTagParser
would then parse the type data to find the struct tags, decode the tags against the specified format, and call the handler for the implementing the final functionality.The compiler would then be able to enforce the formatting of the Struct Tag string data, however it would not be able to validate the internal meanings. So
json:"fieldName,omitempty" bson:"fieldName"
would be able to checked such thatjson
andbson
and thenfieldName,omitempty
andfieldName
portions are valid formats; howeverjosn:"fieldName"
wouldn't be able to be detected thatjson
was mispelled asjosn
.This would maintain backward compatibility since the struct tag data itself wouldn't be changed; while adding some additional functionality that makes using struct tags easier to work with and putting some additional guidelines on the format. The only part that might break is if something utilized the string data differently than expected; however, that should likely be pretty low impact that could be negated with documentation and notices.
trim21 commentedon Jul 26, 2024
this can be included in go 1 actually, this won't break existing go code.
[-]proposal: Go 2: spec: introduce structured tags[/-][+]proposal: spec: introduce structured tags[/+]