-
Notifications
You must be signed in to change notification settings - Fork 18.3k
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.