structtags
provides straightforward ways to parse, read, or modify struct tags.
- Some projects need a full parsing (key, values)
- Some others only need the key and the raw value.
- Other projects need to escape the comma.
- Etc.
Instead of rewriting the wheel for each project, I also provided a package with the plumbing:
parser.Tag()
: extracted fromreflect.StructTag
for the base parsing.parser.Value()
: to parse the value (support optional comma escaping).
This is the first version of the module, and I want to extend it based on feedback so that the API can evolve and break.
---
title: What is the best parsing function for your context?
---
flowchart TB
A([Need to keep the keys in order?]) -- yes --> B([Need the values to be split on commas?])
B -- yes --> G(ParseToSliceValues)
B -- no --> F(ParseToSlice)
B -- both --> L(ParseToStructured)
A -- no --> C([Need the values to be split on commas?])
C -- yes --> I(ParseToMapValues)
C -- no --> H([Using duplicated keys?])
H -- yes --> J(ParseToMapMultikeys)
H -- no --> K(ParseToMap)
click G "https://github.com/ldez/structtags?tab=readme-ov-file#structtagsparsetoslicevaluestag-options" "ParseToSliceValues"
click F "https://github.com/ldez/structtags?tab=readme-ov-file#structtagsparsetoslicetag-options" "ParseToSlice"
click L "https://github.com/ldez/structtags?tab=readme-ov-file#structtagsparsetostructuredtag-options" "ParseToStructured"
click I "https://github.com/ldez/structtags?tab=readme-ov-file#structtagsparsetomapvaluestag-options" "ParseToMapValues"
click J "https://github.com/ldez/structtags?tab=readme-ov-file#structtagsparsetomapmultikeystag" "ParseToMapMultikeys"
click K "https://github.com/ldez/structtags?tab=readme-ov-file#structtagsparsetomaptag-options" "ParseToMap"
Parses a struct tag to a map[string]string
.
Options:
WithDuplicateKeysMode
:DuplicateKeysIgnore
(default)DuplicateKeysDeny
Parses a struct tag to a map[string][]string
.
The value is split on a comma.
Options:
WithEscapeComma
: Comma escaped by backslash.WithDuplicateKeysMode
:DuplicateKeysIgnore
(default)DuplicateKeysDeny
DuplicateKeysAllow
(non-conventional, so not recommended)
NOT RECOMMENDED. For non-conventional tags where the key is repeated.
Parses a struct tag to a map[string][]string
.
Parses a struct tag to a slice of type Tag struct { Key, Value string }
.
Options:
WithDuplicateKeysMode
:DuplicateKeysIgnore
(default)DuplicateKeysDeny
DuplicateKeysAllow
(non-conventional, so not recommended)
Parses a struct tag to a slice of type Tag struct { Key string, Value []string }
.
The value is split on a comma.
Options:
WithEscapeComma
: Comma escaped by backslash.WithDuplicateKeysMode
:DuplicateKeysIgnore
(default)DuplicateKeysDeny
DuplicateKeysAllow
(non-conventional, so not recommended)
Parses a struct tag to a *structured.Tag
.
The value is split on a comma.
The value is parsed lazily: only if you call Entry.Values()
Options:
WithEscapeComma
: Comma escaped by backslash.WithDuplicateKeysMode
:DuplicateKeysIgnore
(default)DuplicateKeysDeny
DuplicateKeysAllow
(non-conventional, so not recommended)
Compatibility layer with fatih/structtag
.
Parses a struct tag to a *structtag.Tags
.
The value is split on a comma.
Option: comma escaped by backslash.
The parser
package provides the tooling to parse a struct tag and its associated value.
To implement a custom parser, you can implement the parser.Filler
interface.
reflect.StructTag
is great but:
- It doesn't allow extracting all keys or iterating on them.
- It doesn't allow modifying the struct tags.
- It doesn't provide methods to split the values on comma.
- It doesn't allow getting non-conventional duplicated keys.
fatih/structtag
is a great library, but it's not extensible, it was built around JSON tags, and it was designed to modify tags.
For example, the Tag
struct inside fatih/structtag
that represents a tag is:
type Tag struct {
// Key is the tag key, such as json, xml, etc..
// i.e: `json:"foo,omitempty". Here key is: "json"
Key string
// Name is a part of the value
// i.e: `json:"foo,omitempty". Here name is: "foo"
Name string
// Options is a part of the value. It contains a slice of tag options i.e:
// `json:"foo,omitempty". Here options is: ["omitempty"]
Options []string
}
Name
and Options
are related to JSON tags (and other marshaling/unmarshalling libraries).
But the first element in a struct tag value is not necessarily a name.
Example
type Foo struct {
Field1 float64 `minimum:"10.5" example:"20.6" required:"true"`
Field2 string `jsonschema:"required"`
Field3 string `description:"This is a description"`
}
Also, most projects don't need to modify the struct tags, but only to read them.
There are some limitations with fatih/structtag
when there is comma inside the value.
Example
type Foo struct {
Field1 string `regexp:"[a-z\\,.]"`
}
The struct tag specifications say that struct tags can be any string.
The key/value syntax, the comma separator, and the space separator are conventions based on reflect.StructTag
and json
implementation.
reflect.StructTag
behaves like the struct tags are map[string]string
, but with one difference:
The first key always wins if there are multiple keys with the same name.
We can say that the reflect.StructTag
doesn't support multiple keys with the same name.
But some rare projects/libraries use multiple keys with the same name.
Also, the specification doesn't talk about comma escaping inside the value.
Maybe the specification should clarify those points.
- https://go.dev/ref/spec#Struct_types
- https://go.dev/ref/spec#string_lit
- https://pkg.go.dev/reflect#StructTag
- https://github.com/golang/go/blob/411c250d64304033181c46413a6e9381e8fe9b82/src/reflect/type.go#L1030-L1108
- https://github.com/golang/tools/blob/master/go/analysis/passes/structtag/structtag.go
- https://github.com/fatih/structtag/blob/master/tags.go