Apa gunanya tag di Go?


392

Dalam Spesifikasi Bahasa Go , ini menyebutkan ikhtisar singkat dari tag:

Deklarasi bidang dapat diikuti oleh tag literal string opsional, yang menjadi atribut untuk semua bidang dalam deklarasi bidang terkait. Tag dibuat terlihat melalui antarmuka refleksi tetapi sebaliknya diabaikan.

// A struct corresponding to the TimeStamp protocol buffer.
// The tag strings define the protocol buffer field numbers.
struct {
  microsec  uint64 "field 1"
  serverIP6 uint64 "field 2"
  process   string "field 3"
}

Ini adalah penjelasan IMO yang sangat singkat, dan saya bertanya-tanya apakah ada yang bisa memberi saya apa gunanya tag ini?


Saya memiliki pertanyaan terkait untuk penggunaan komentar 'semantik': stackoverflow.com/questions/53101458/…
Bruce Adams

Jawaban:


641

Tag untuk suatu bidang memungkinkan Anda untuk melampirkan informasi meta ke bidang yang dapat diperoleh menggunakan refleksi. Biasanya ini digunakan untuk memberikan info transformasi tentang bagaimana bidang struct dikodekan ke atau diterjemahkan dari format lain (atau disimpan / diambil dari database), tetapi Anda dapat menggunakannya untuk menyimpan meta-info apa pun yang Anda inginkan, baik yang ditujukan untuk yang lain paket atau untuk penggunaan Anda sendiri.

Seperti disebutkan dalam dokumentasi reflect.StructTag, dengan konvensi nilai string tag adalah daftar key:"value"pasangan yang dipisahkan oleh spasi , misalnya:

type User struct {
    Name string `json:"name" xml:"name"`
}

The keybiasanya menunjukkan paket yang berikutnya "value"adalah untuk, misalnya jsonkunci diproses / digunakan oleh encoding/jsonpaket.

Jika beberapa informasi dilewatkan dalam "value", biasanya ditentukan dengan memisahkannya dengan koma ( ','), misalnya

Name string `json:"name,omitempty" xml:"name"`

Biasanya nilai tanda hubung ( '-') untuk "value"sarana untuk mengecualikan bidang dari proses (misalnya dalam kasus jsonitu berarti tidak menyusun atau menghapuskan bidang itu).

Contoh mengakses tag khusus Anda menggunakan refleksi

Kita dapat menggunakan refleksi ( reflectpaket) untuk mengakses nilai tag bidang struct. Pada dasarnya kita perlu memperoleh Typestruct kita, dan kemudian kita dapat query bidang misalnya dengan Type.Field(i int)atau Type.FieldByName(name string). Metode ini mengembalikan nilai StructFieldyang menggambarkan / mewakili bidang struct; dan StructField.Tagmerupakan nilai tipe StructTagyang menggambarkan / mewakili nilai tag.

Sebelumnya kami berbicara tentang "konvensi" . Konvensi ini berarti bahwa jika Anda mengikutinya, Anda dapat menggunakan StructTag.Get(key string)metode yang mem-parsing nilai tag dan mengembalikan Anda "value"dari yang keyAnda tentukan. The Konvensi diimplementasikan / dibangun ke dalam ini Get()metode. Jika Anda tidak mengikuti konvensi, Get()tidak akan dapat menguraikan key:"value"pasangan dan menemukan apa yang Anda cari. Itu juga bukan masalah, tetapi kemudian Anda perlu mengimplementasikan logika parsing Anda sendiri.

Juga ada StructTag.Lookup()(ditambahkan pada Go 1.7) yang "seperti Get()tetapi membedakan tag yang tidak berisi kunci yang diberikan dari tag yang mengaitkan string kosong dengan kunci yang diberikan" .

Jadi mari kita lihat contoh sederhana:

type User struct {
    Name  string `mytag:"MyName"`
    Email string `mytag:"MyEmail"`
}

u := User{"Bob", "bob@mycompany.com"}
t := reflect.TypeOf(u)

for _, fieldName := range []string{"Name", "Email"} {
    field, found := t.FieldByName(fieldName)
    if !found {
        continue
    }
    fmt.Printf("\nField: User.%s\n", fieldName)
    fmt.Printf("\tWhole tag value : %q\n", field.Tag)
    fmt.Printf("\tValue of 'mytag': %q\n", field.Tag.Get("mytag"))
}

Output (coba di Go Playground ):

Field: User.Name
    Whole tag value : "mytag:\"MyName\""
    Value of 'mytag': "MyName"

Field: User.Email
    Whole tag value : "mytag:\"MyEmail\""
    Value of 'mytag': "MyEmail"

GopherCon 2015 memiliki presentasi tentang tag struct yang disebut:

The Many Faces of Struct Tags (slide) (dan sebuah video )

Berikut adalah daftar kunci tag yang umum digunakan:


28
Jawaban yang sangat bagus. Info jauh lebih bermanfaat di sini daripada di yang sepuluh kali karma ini.
Darth Egregious

2
ringkasan yang sangat bagus!
stevenferrer

2
Sungguh jawaban yang luar biasa
Alberto Megía

1
Jawaban bagus! Terima kasih!
JumpAlways

1
Jawaban yang luar biasa, terima kasih atas semua informasi ini!
Sam Holmes

157

Berikut adalah contoh tag yang benar-benar sederhana yang digunakan dengan encoding/jsonpaket untuk mengontrol bagaimana bidang ditafsirkan selama penyandian dan penguraian:

Coba langsung: http://play.golang.org/p/BMeR8p1cKf

package main

import (
    "fmt"
    "encoding/json"
)

type Person struct {
    FirstName  string `json:"first_name"`
    LastName   string `json:"last_name"`
    MiddleName string `json:"middle_name,omitempty"`
}

func main() {
    json_string := `
    {
        "first_name": "John",
        "last_name": "Smith"
    }`

    person := new(Person)
    json.Unmarshal([]byte(json_string), person)
    fmt.Println(person)

    new_json, _ := json.Marshal(person)
    fmt.Printf("%s\n", new_json)
}

// *Output*
// &{John Smith }
// {"first_name":"John","last_name":"Smith"}

Paket json dapat melihat tag untuk bidang tersebut dan diberi tahu cara memetakan bidang json <=> struct, dan juga opsi tambahan seperti apakah harus mengabaikan bidang kosong saat membuat serial kembali ke json.

Pada dasarnya, paket apa pun dapat menggunakan refleksi pada bidang untuk melihat nilai tag dan bertindak berdasarkan nilai-nilai itu. Ada sedikit lebih banyak info tentang mereka dalam paket refleksi
http://golang.org/pkg/reflect/#StructTag :

Secara konvensional, tag tag adalah gabungan dari kunci yang dipisahkan spasi opsional: pasangan "nilai". Setiap kunci adalah string non-kosong yang terdiri dari karakter non-kontrol selain spasi (U + 0020 ''), kutipan (U + 0022 '"'), dan titik dua (U + 003A ':'). Setiap nilai dikutip menggunakan karakter U + 0022 '"' dan sintaks literal Go string.


6
Jenis seperti anotasi Java?
Ismail Badawi

7
@ isbadawi: Saya bukan orang Jawa, tetapi sekilas definisi dari penjelasan java, ya sepertinya mereka mencapai tujuan yang sama; melampirkan metadata ke elemen yang dapat diperiksa saat runtime.
jdi

15
Sebenarnya bukan anotasi java. Anotasi Java adalah tipe aman dan waktu kompilasi diperiksa - bukan string literal seperti go. Penjelasan Java jauh lebih kuat dan kuat daripada ketentuan metadata dasar golang.
sat

2
Sebagai bagian dari driver MongoDB untuk Go, mgo, juga menggunakan tag dalam paket bson-nya (yang juga dapat digunakan dengan sendirinya). Ini memberi Anda kontrol yang tepat atas apa yang dihasilkan BSON. Lihat godoc.org/labix.org/v2/mgo/bson#pkg-files
Eno

1
Apakah ada contoh lain selain JSON dan BSON?
Max Heiber

1

Ini semacam spesifikasi yang menentukan bagaimana paket memperlakukan dengan bidang yang ditandai.

sebagai contoh:

type User struct {
    FirstName string `json:"first_name"`
    LastName string `json:"last_name"`
}

tag json menginformasikan jsonpaket yang menyusun output dari pengguna berikut

u := User{
        FirstName: "some first name",
        LastName:  "some last name",
    }

akan seperti ini:

{"first_name":"some first name","last_name":"some last name"}

contoh lain adalah gormtag paket menyatakan bagaimana migrasi basis data harus dilakukan:

type User struct {
  gorm.Model
  Name         string
  Age          sql.NullInt64
  Birthday     *time.Time
  Email        string  `gorm:"type:varchar(100);unique_index"`
  Role         string  `gorm:"size:255"` // set field size to 255
  MemberNumber *string `gorm:"unique;not null"` // set member number to unique and not null
  Num          int     `gorm:"AUTO_INCREMENT"` // set num to auto incrementable
  Address      string  `gorm:"index:addr"` // create index with name `addr` for address
  IgnoreMe     int     `gorm:"-"` // ignore this field
}

Dalam contoh ini untuk bidang Emaildengan tag gorm kami menyatakan bahwa kolom yang sesuai dalam basis data untuk email bidang harus bertipe varchar dan panjang maksimum 100 dan juga harus memiliki indeks unik.

contoh lain adalah bindingtag yang digunakan sebagian besar dalam ginpaket.

type Login struct {
    User     string `form:"user" json:"user" xml:"user"  binding:"required"`
    Password string `form:"password" json:"password" xml:"password" binding:"required"`
}


var json Login
if err := c.ShouldBindJSON(&json); err != nil {
     c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
     return
}

tag yang mengikat dalam contoh ini memberikan petunjuk kepada paket gin bahwa data yang dikirim ke API harus memiliki bidang pengguna dan kata sandi karena bidang ini ditandai seperti yang diperlukan.

Jadi tag umum adalah data yang perlu diketahui paket yang harus diperlakukan dengan data bertipe struct yang berbeda dan cara terbaik untuk membiasakan diri dengan tag yang dibutuhkan paket adalah MEMBACA DOKUMENTASI PAKET SEPENUHNYA.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.