golang表单数据解码到结构体的高效插件库formam的使用

Golang表单数据解码到结构体的高效插件库formam的使用

formam是一个用于解码HTTP表单和查询参数的Go包,最低要求Go 1.12或更高版本。

安装

go get github.com/monoculum/formam/v3

特性

  • 支持mapsstructsslices的无限嵌套
  • 支持在值和map键中使用UnmarshalText()接口
  • 支持的map键类型包括:stringint及其变体、uint及其变体、uintptrfloat32float64boolstructcustom types
  • 可以访问值为mapstructsliceinterface{}字段
  • 通过UnmarshalText()方法解码格式为2006-01-02time.Time
  • 解码url.URL
  • 无需显式指定索引即可追加到slicearray类型
  • 为自定义类型注册函数

基本使用示例

在HTML表单中

  • 使用.访问结构体字段(如struct.field1
  • 使用[<index>]访问特定的slice/array索引(如struct.array[0]),追加数据时不需要添加索引
  • 使用[<key>]访问map键(如struct.map[es-ES]
<form method="POST">
  <input type="text" name="Name" value="Sony" />
  <input type="text" name="Location.Country" value="Japan" />
  <input type="text" name="Location.City" value="Tokyo" />
  <input type="text" name="Products[0].Name" value="Playstation 4" />
  <input type="text" name="Products[0].Type" value="Video games" />
  <input type="text" name="Products[1].Name" value="TV Bravia 32" />
  <input type="text" name="Products[1].Type" value="TVs" />
  <input type="text" name="Founders[0]" value="Masaru Ibuka" />
  <input type="text" name="Founders[0]" value="Akio Morita" />
  <input type="text" name="Employees" value="90000" />
  <input type="text" name="public" value="true" />
  <input type="url" name="website" value="http://www.sony.net" />
  <input type="date" name="foundation" value="1946-05-07" />
  <input type="text" name="Interface.ID" value="12" />
  <input type="text" name="Interface.Name" value="Go Programming Language" />
  <input type="submit" />
</form>

在Go中

可以使用formam结构体标签确保表单值正确解组到结构体字段中。

type InterfaceStruct struct {
    ID   int
    Name string
}

type Company struct {
  Public     bool      `formam:"public"`
  Website    url.URL   `formam:"website"`
  Foundation time.Time `formam:"foundation"`
  Name       string
  Location   struct {
    Country  string
    City     string
  }
  Products   []struct {
    Name string
    Type string
  }
  Founders   []string
  Employees  int64

  Interface interface{}
}

func MyHandler(w http.ResponseWriter, r *http.Request) error {
  r.ParseForm()

  m := Company{
      // 尽管是接口字段,也可以访问其字段!
      Interface: &InterfaceStruct{},
  }
  dec := formam.NewDecoder(&formam.DecoderOptions{TagName: "formam"})
  return dec.Decode(r.Form, &m)
}

支持的类型

目标结构体中支持的类型包括:

  • string
  • bool
  • int, int8, int16, int32, int64
  • uint, uint8, uint16, uint32, uint64
  • float32, float64
  • slice, array
  • structstruct anonymous
  • map
  • interface{}
  • time.Time
  • url.URL
  • custom types 到上述类型之一
  • 上述类型的pointer

自定义解组

可以通过实现encoding.TextUnmarshaler接口来解组数据和map键。

如果表单发送多个值,则只有第一个值传递给UnmarshalText(),但如果名称以[]结尾,则会为所有值调用它。

自定义类型

可以使用RegisterCustomType()方法为自定义类型注册函数。这将适用于给定数量的字段或具有给定类型的所有字段。

除非使用PrefUnmarshalText选项,否则注册类型优先于UnmarshalText方法。

所有字段

decoder.RegisterCustomType(func(vals []string) (interface{}, error) {
        return time.Parse("2006-01-02", vals[0])
}, []interface{}{time.Time{}}, nil)

特定字段

package main

type Times struct {
    Timestamp   time.Time
    Time        time.Time
    TimeDefault time.Time
}

func main() {
    var t Timestamp

    dec := NewDecoder(nil)

    // for Timestamp field
    dec.RegisterCustomType(func(vals []string) (interface{}, error) {
            return time.Parse("2006-01-02T15:04:05Z07:00", vals[0])
    }, []interface{}{time.Time{}}, []interface{}{&t.Timestamp{}})

    // for Time field
    dec.RegisterCustomType(func(vals []string) (interface{}, error) {
                return time.Parse("Mon, 02 Jan 2006 15:04:05 MST", vals[0])
    }, []interface{}{time.Time{}}, []interface{}{&t.Time{}})

    // for field that not be Time or Timestamp, e.g. in this example, TimeDefault.
    dec.RegisterCustomType(func(vals []string) (interface{}, error) {
                return time.Parse("2006-01-02", vals[0])
    }, []interface{}{time.Time{}}, nil)

    dec.Decode(url.Values{}, &t)
}

注意事项

版本2兼容旧的map访问语法(map.key),但括号是访问map的首选方式(map[key])。


更多关于golang表单数据解码到结构体的高效插件库formam的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang表单数据解码到结构体的高效插件库formam的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang表单数据解码到结构体的高效插件库 - formam

formam 是一个轻量级的 Go 库,用于将 HTML 表单数据解码到 Go 结构体中。它比标准库的 encoding/jsonencoding/xml 更专注于表单数据的处理,提供了更灵活的解码方式。

formam 的主要特点

  1. 支持将表单数据解码到结构体、切片和数组
  2. 支持嵌套结构体
  3. 支持自定义类型转换
  4. 支持忽略字段
  5. 简单易用的 API

安装 formam

go get github.com/monoculum/formam

基本使用示例

package main

import (
	"fmt"
	"net/url"

	"github.com/monoculum/formam"
)

type User struct {
	Name    string
	Age     int
	Email   string
	Active  bool
	Hobbies []string
}

func main() {
	// 模拟表单数据
	values := url.Values{}
	values.Set("Name", "John Doe")
	values.Set("Age", "30")
	values.Set("Email", "john@example.com")
	values.Set("Active", "true")
	values.Add("Hobbies", "reading")
	values.Add("Hobbies", "coding")
	values.Add("Hobbies", "swimming")

	var user User

	// 创建 formam 解码器
	decoder := formam.NewDecoder(&formam.DecoderOptions{
		TagName: "formam", // 自定义标签名,默认为 "form"
	})

	// 解码表单数据到结构体
	err := decoder.Decode(values, &user)
	if err != nil {
		fmt.Println("解码错误:", err)
		return
	}

	fmt.Printf("%+v\n", user)
	// 输出: {Name:John Doe Age:30 Email:john@example.com Active:true Hobbies:[reading coding swimming]}
}

高级功能示例

1. 嵌套结构体

type Address struct {
	Street  string
	City    string
	Country string
}

type Person struct {
	Name    string
	Age     int
	Address Address `formam:"address"` // 使用自定义标签
}

func nestedStructExample() {
	values := url.Values{}
	values.Set("Name", "Alice")
	values.Set("Age", "25")
	values.Set("address.Street", "123 Main St")
	values.Set("address.City", "New York")
	values.Set("address.Country", "USA")

	var person Person
	decoder := formam.NewDecoder(nil)
	err := decoder.Decode(values, &person)
	if err != nil {
		fmt.Println("解码错误:", err)
		return
	}

	fmt.Printf("%+v\n", person)
	// 输出: {Name:Alice Age:25 Address:{Street:123 Main St City:New York Country:USA}}
}

2. 忽略字段

type Config struct {
	Username string `formam:"username"`
	Password string `formam:"-"` // 使用 "-" 忽略该字段
	Token    string
}

func ignoreFieldExample() {
	values := url.Values{}
	values.Set("username", "admin")
	values.Set("Password", "secret")
	values.Set("Token", "abc123")

	var config Config
	decoder := formam.NewDecoder(nil)
	err := decoder.Decode(values, &config)
	if err != nil {
		fmt.Println("解码错误:", err)
		return
	}

	fmt.Printf("%+v\n", config)
	// 输出: {Username:admin Password: Token:abc123}
}

3. 自定义类型转换

type CustomTime struct {
	time.Time
}

func (t *CustomTime) Set(value interface{}) error {
	if str, ok := value.(string); ok {
		parsed, err := time.Parse("2006-01-02", str)
		if err != nil {
			return err
		}
		t.Time = parsed
		return nil
	}
	return fmt.Errorf("无法转换类型")
}

type Event struct {
	Title string     `formam:"title"`
	Date  CustomTime `formam:"date"`
}

func customTypeExample() {
	values := url.Values{}
	values.Set("title", "Go Conference")
	values.Set("date", "2023-11-15")

	var event Event
	decoder := formam.NewDecoder(nil)
	err := decoder.Decode(values, &event)
	if err != nil {
		fmt.Println("解码错误:", err)
		return
	}

	fmt.Printf("Event: %s on %v\n", event.Title, event.Date.Format("January 2, 2006"))
	// 输出: Event: Go Conference on November 15, 2023
}

性能考虑

formam 在设计时就考虑了性能因素,它比标准库的 url.Values 解析更高效,特别是在处理复杂嵌套结构时。以下是一些性能优化建议:

  1. 复用解码器实例而不是每次都创建新的
  2. 对于频繁使用的结构体,可以考虑预先生成解码器
  3. 避免在热路径中频繁创建和销毁解码器

与标准库的比较

与标准库的 net/urlencoding/json 相比,formam 提供了更直观的表单数据处理方式:

  1. 更简单的嵌套结构支持
  2. 更灵活的类型转换
  3. 更直观的表单字段到结构体字段的映射
  4. 更好的错误处理

总结

formam 是一个简单而强大的 Go 库,专门用于处理 HTML 表单数据到 Go 结构体的解码。它提供了比标准库更灵活和直观的 API,特别适合 Web 开发中处理表单数据。通过支持嵌套结构、自定义类型转换和字段忽略等功能,formam 可以大大简化表单处理的代码。

对于需要处理复杂表单数据的 Go Web 应用,formam 是一个值得考虑的选择。

回到顶部