// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package query implements encoding of structs into URL query parameters. // // As a simple example: // // type Options struct { // Query string `url:"q"` // ShowAll bool `url:"all"` // Page int `url:"page"` // } // // opt := Options{ "foo", true, 2 } // v, _ := query.Values(opt) // fmt.Print(v.Encode()) // will output: "q=foo&all=true&page=2" // // The exact mapping between Go values and url.Values is described in the // documentation for the Values() function.
package query import ( ) var timeType = reflect.TypeOf(time.Time{}) var encoderType = reflect.TypeOf(new(Encoder)).Elem() // Encoder is an interface implemented by any type that wishes to encode // itself into URL values in a non-standard way. type Encoder interface { EncodeValues(key string, v *url.Values) error } // Values returns the url.Values encoding of v. // // Values expects to be passed a struct, and traverses it recursively using the // following encoding rules. // // Each exported struct field is encoded as a URL parameter unless // // - the field's tag is "-", or // - the field is empty and its tag specifies the "omitempty" option // // The empty values are false, 0, any nil pointer or interface value, any array // slice, map, or string of length zero, and any type (such as time.Time) that // returns true for IsZero(). // // The URL parameter name defaults to the struct field name but can be // specified in the struct field's tag value. The "url" key in the struct // field's tag value is the key name, followed by an optional comma and // options. For example: // // // Field is ignored by this package. // Field int `url:"-"` // // // Field appears as URL parameter "myName". // Field int `url:"myName"` // // // Field appears as URL parameter "myName" and the field is omitted if // // its value is empty // Field int `url:"myName,omitempty"` // // // Field appears as URL parameter "Field" (the default), but the field // // is skipped if empty. Note the leading comma. // Field int `url:",omitempty"` // // For encoding individual field values, the following type-dependent rules // apply: // // Boolean values default to encoding as the strings "true" or "false". // Including the "int" option signals that the field should be encoded as the // strings "1" or "0". // // time.Time values default to encoding as RFC3339 timestamps. Including the // "unix" option signals that the field should be encoded as a Unix time (see // time.Unix()). The "unixmilli" and "unixnano" options will encode the number // of milliseconds and nanoseconds, respectively, since January 1, 1970 (see // time.UnixNano()). Including the "layout" struct tag (separate from the // "url" tag) will use the value of the "layout" tag as a layout passed to // time.Format. For example: // // // Encode a time.Time as YYYY-MM-DD // Field time.Time `layout:"2006-01-02"` // // Slice and Array values default to encoding as multiple URL values of the // same name. Including the "comma" option signals that the field should be // encoded as a single comma-delimited value. Including the "space" option // similarly encodes the value as a single space-delimited string. Including // the "semicolon" option will encode the value as a semicolon-delimited string. // Including the "brackets" option signals that the multiple URL values should // have "[]" appended to the value name. "numbered" will append a number to // the end of each incidence of the value name, example: // name0=value0&name1=value1, etc. Including the "del" struct tag (separate // from the "url" tag) will use the value of the "del" tag as the delimiter. // For example: // // // Encode a slice of bools as ints ("1" for true, "0" for false), // // separated by exclamation points "!". // Field []bool `url:",int" del:"!"` // // Anonymous struct fields are usually encoded as if their inner exported // fields were fields in the outer struct, subject to the standard Go // visibility rules. An anonymous struct field with a name given in its URL // tag is treated as having that name, rather than being anonymous. // // Non-nil pointer values are encoded as the value pointed to. // // Nested structs have their fields processed recursively and are encoded // including parent fields in value names for scoping. For example, // // "user[name]=acme&user[addr][postcode]=1234&user[addr][city]=SFO" // // All other values are encoded using their default string representation. // // Multiple fields that encode to the same URL parameter name will be included // as multiple URL values of the same name. func ( interface{}) (url.Values, error) { := make(url.Values) if == nil { return , nil } := reflect.ValueOf() for .Kind() == reflect.Ptr { if .IsNil() { return , nil } = .Elem() } if .Kind() != reflect.Struct { return nil, fmt.Errorf("query: Values() expects struct input. Got %v", .Kind()) } := reflectValue(, , "") return , } // reflectValue populates the values parameter from the struct fields in val. // Embedded structs are followed recursively (using the rules defined in the // Values function documentation) breadth-first. func ( url.Values, reflect.Value, string) error { var []reflect.Value := .Type() for := 0; < .NumField(); ++ { := .Field() if .PkgPath != "" && !.Anonymous { // unexported continue } := .Field() := .Tag.Get("url") if == "-" { continue } , := parseTag() if == "" { if .Anonymous { := reflect.Indirect() if .IsValid() && .Kind() == reflect.Struct { // save embedded struct for later processing = append(, ) continue } } = .Name } if != "" { = + "[" + + "]" } if .Contains("omitempty") && isEmptyValue() { continue } if .Type().Implements(encoderType) { // if sv is a nil pointer and the custom encoder is defined on a non-pointer // method receiver, set sv to the zero value of the underlying type if !reflect.Indirect().IsValid() && .Type().Elem().Implements(encoderType) { = reflect.New(.Type().Elem()) } := .Interface().(Encoder) if := .EncodeValues(, &); != nil { return } continue } // recursively dereference pointers. break on nil pointers for .Kind() == reflect.Ptr { if .IsNil() { break } = .Elem() } if .Kind() == reflect.Slice || .Kind() == reflect.Array { if .Len() == 0 { // skip if slice or array is empty continue } var string if .Contains("comma") { = "," } else if .Contains("space") { = " " } else if .Contains("semicolon") { = ";" } else if .Contains("brackets") { = + "[]" } else { = .Tag.Get("del") } if != "" { := new(strings.Builder) := true for := 0; < .Len(); ++ { if { = false } else { .WriteString() } .WriteString(valueString(.Index(), , )) } .Add(, .String()) } else { for := 0; < .Len(); ++ { := if .Contains("numbered") { = fmt.Sprintf("%s%d", , ) } .Add(, valueString(.Index(), , )) } } continue } if .Type() == timeType { .Add(, valueString(, , )) continue } if .Kind() == reflect.Struct { if := (, , ); != nil { return } continue } .Add(, valueString(, , )) } for , := range { if := (, , ); != nil { return } } return nil } // valueString returns the string representation of a value. func ( reflect.Value, tagOptions, reflect.StructField) string { for .Kind() == reflect.Ptr { if .IsNil() { return "" } = .Elem() } if .Kind() == reflect.Bool && .Contains("int") { if .Bool() { return "1" } return "0" } if .Type() == timeType { := .Interface().(time.Time) if .Contains("unix") { return strconv.FormatInt(.Unix(), 10) } if .Contains("unixmilli") { return strconv.FormatInt((.UnixNano() / 1e6), 10) } if .Contains("unixnano") { return strconv.FormatInt(.UnixNano(), 10) } if := .Tag.Get("layout"); != "" { return .Format() } return .Format(time.RFC3339) } return fmt.Sprint(.Interface()) } // isEmptyValue checks if a value should be considered empty for the purposes // of omitting fields with the "omitempty" option. func ( reflect.Value) bool { switch .Kind() { case reflect.Array, reflect.Map, reflect.Slice, reflect.String: return .Len() == 0 case reflect.Bool: return !.Bool() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return .Int() == 0 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return .Uint() == 0 case reflect.Float32, reflect.Float64: return .Float() == 0 case reflect.Interface, reflect.Ptr: return .IsNil() } type interface { () bool } if , := .Interface().(); { return .() } return false } // tagOptions is the string following a comma in a struct field's "url" tag, or // the empty string. It does not include the leading comma. type tagOptions []string // parseTag splits a struct field's url tag into its name and comma-separated // options. func ( string) (string, tagOptions) { := strings.Split(, ",") return [0], [1:] } // Contains checks whether the tagOptions contains the specified option. func ( tagOptions) ( string) bool { for , := range { if == { return true } } return false }