Введение
mapstructure
для общегоmap[string]interface{}
Декодируйте в соответствующую структуру Go или сделайте наоборот. Часто при анализе потоков данных из нескольких источников мы, как правило, заранее не знаем их конкретные типы. Решение может быть принято только после прочтения некоторых полей. В этом случае мы можем сначала использовать стандартныйencoding/json
Библиотека декодирует данные какmap[string]interface{}
Тип, затем в соответствии с идентификационным полем, используяmapstructure
Библиотеки преобразуются в соответствующие структуры Go для использования.
быстрый в использовании
Код в этой статье использует модули Go.
Сначала создайте каталог и инициализируйте его:
$ mkdir mapstructure && cd mapstructure
$ go mod init github.com/darjun/go-daily-lib/mapstructure
скачатьmapstructure
Библиотеки:
$ go get github.com/mitchellh/mapstructure
использовать:
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/mitchellh/mapstructure"
)
type Person struct {
Name string
Age int
Job string
}
type Cat struct {
Name string
Age int
Breed string
}
func main() {
datas := []string{`
{
"type": "person",
"name":"dj",
"age":18,
"job": "programmer"
}
`,
`
{
"type": "cat",
"name": "kitty",
"age": 1,
"breed": "Ragdoll"
}
`,
}
for _, data := range datas {
var m map[string]interface{}
err := json.Unmarshal([]byte(data), &m)
if err != nil {
log.Fatal(err)
}
switch m["type"].(string) {
case "person":
var p Person
mapstructure.Decode(m, &p)
fmt.Println("person", p)
case "cat":
var cat Cat
mapstructure.Decode(m, &cat)
fmt.Println("cat", cat)
}
}
}
результат операции:
$ go run main.go
person {dj 18 programmer}
cat {kitty 1 Ragdoll}
Мы определяем две структурыPerson
иCat
, их поля немного отличаются. Теперь одна из строк JSON, которую мы согласились передатьtype
поле. когдаtype
значениеperson
, строка JSON представляетPerson
тип данных. когдаtype
значениеcat
, строка JSON представляетCat
тип данных.
В приведенном выше коде мы сначала используемjson.Unmarshal
Декодируйте поток байтов какmap[string]interface{}
тип. затем прочитайтеtype
поле. в соответствии сtype
значение поля, затем используйтеmapstructure.Decode
Декодируйте строку JSON какPerson
иCat
значение типа и выход.
Фактически, Google Protobuf также обычно использует этот метод. Добавьте идентификатор сообщения в протокол илиПолное имя сообщения.接收方收到数据后,先读取协议 ID 或Полное имя сообщения. Затем вызовите метод декодирования Protobuf, чтобы декодировать его в соответствующийMessage
структура. с этого ракурса,mapstructure
Его также можно использовать для декодирования сетевых сообщений,если не думать о производительности😄.
метка поля
по умолчанию,mapstructure
Сделайте это сопоставление, используя имена полей в структуре, например, наша структура имеетName
поле,mapstructure
при декодированииmap[string]interface{}
Найдите имя ключа вname
. Обратите внимание, что здесьname
нечувствителен к регистру!
type Person struct {
Name string
}
Конечно, мы также можем указать имя поля отображения. Для этого нам нужно установить полеmapstructure
Этикетка. Например, используйте нижеusername
вместо приведенного выше примераname
:
type Person struct {
Name string `mapstructure:"username"`
}
См. пример:
type Person struct {
Name string `mapstructure:"username"`
Age int
Job string
}
type Cat struct {
Name string
Age int
Breed string
}
func main() {
datas := []string{`
{
"type": "person",
"username":"dj",
"age":18,
"job": "programmer"
}
`,
`
{
"type": "cat",
"name": "kitty",
"Age": 1,
"breed": "Ragdoll"
}
`,
`
{
"type": "cat",
"Name": "rooooose",
"age": 2,
"breed": "shorthair"
}
`,
}
for _, data := range datas {
var m map[string]interface{}
err := json.Unmarshal([]byte(data), &m)
if err != nil {
log.Fatal(err)
}
switch m["type"].(string) {
case "person":
var p Person
mapstructure.Decode(m, &p)
fmt.Println("person", p)
case "cat":
var cat Cat
mapstructure.Decode(m, &cat)
fmt.Println("cat", cat)
}
}
}
В приведенном выше коде мы используем тегmapstructure:"username"
будетPerson
изName
Полевые карты наusername
, в строке JSON нам нужно установитьusername
правильно разобрать. Также обратите внимание, что мы поместили вторую строку JSON вAge
и третья строка JSON вName
Первая буква заглавная, но на результат декодирования это не влияет.mapstructure
Обработка сопоставлений полей не зависит от регистра.
Встроенная структура
Структуры могут быть вложены произвольно, а вложенной структурой считается еще одно поле с именем структуры. Например, следующие дваFriend
определяется таким образом, чтоmapstructure
это то же самое:
type Person struct {
Name string
}
// 方式一
type Friend struct {
Person
}
// 方式二
type Friend struct {
Person Person
}
Чтобы правильно декодировать,Person
Данные структуры должны быть вperson
Под ключ:
map[string]interface{} {
"person": map[string]interface{}{"name": "dj"},
}
Мы также можем установитьmapstructure:",squash"
Упомяните поля структуры в родительской структуре:
type Friend struct {
Person `mapstructure:",squash"`
}
Для этого требуется только такая строка JSON, недопустимая вложенностьperson
ключ:
map[string]interface{}{
"name": "dj",
}
См. пример:
type Person struct {
Name string
}
type Friend1 struct {
Person
}
type Friend2 struct {
Person `mapstructure:",squash"`
}
func main() {
datas := []string{`
{
"type": "friend1",
"person": {
"name":"dj"
}
}
`,
`
{
"type": "friend2",
"name": "dj2"
}
`,
}
for _, data := range datas {
var m map[string]interface{}
err := json.Unmarshal([]byte(data), &m)
if err != nil {
log.Fatal(err)
}
switch m["type"].(string) {
case "friend1":
var f1 Friend1
mapstructure.Decode(m, &f1)
fmt.Println("friend1", f1)
case "friend2":
var f2 Friend2
mapstructure.Decode(m, &f2)
fmt.Println("friend2", f2)
}
}
}
Обратите внимание на сравнениеFriend1
иFriend2
Используемая строка JSON отличается.
Другое, что следует отметить, что если есть поле с тем же именем в родительской структуре, тоmapstructure
преобразует соответствующее значение в JSONУстановите оба поля одновременно, то есть два поля имеют одинаковое значение.
несопоставленное значение
Если в исходных данных есть несопоставленные значения (то есть в структуре нет соответствующих полей),mapstructure
По умолчанию игнорируется.
Мы можем определить поле в структуре и установить егоmapstructure:",remain"
Этикетка. Таким образом, в это поле добавляются несопоставленные значения. Обратите внимание, что тип этого поля может быть толькоmap[string]interface{}
илиmap[interface{}]interface{}
.
См. пример:
type Person struct {
Name string
Age int
Job string
Other map[string]interface{} `mapstructure:",remain"`
}
func main() {
data := `
{
"name": "dj",
"age":18,
"job":"programmer",
"height":"1.8m",
"handsome": true
}
`
var m map[string]interface{}
err := json.Unmarshal([]byte(data), &m)
if err != nil {
log.Fatal(err)
}
var p Person
mapstructure.Decode(m, &p)
fmt.Println("other", p.Other)
}
В приведенном выше коде мы определяем структуру для структурыOther
Поле для хранения несопоставленных ключевых значений. Выходной результат:
other map[handsome:true height:1.8m]
обратное преобразование
Прежде чем мыmap[string]interface{}
Декодировать в структуру Go.mapstructure
Конечно, структуру Go также можно декодировать в обратном порядке:map[string]interface{}
. При обратном декодировании мы можем установить для некоторых полейmapstructure:",omitempty"
. Таким образом, когда эти поля являются значениями по умолчанию, они не будут отображаться в структуре.map[string]interface{}
середина:
type Person struct {
Name string
Age int
Job string `mapstructure:",omitempty"`
}
func main() {
p := &Person{
Name: "dj",
Age: 18,
}
var m map[string]interface{}
mapstructure.Decode(p, &m)
data, _ := json.Marshal(m)
fmt.Println(string(data))
}
В приведенном выше коде мы имеемJob
поле установленоmapstructure:",omitempty"
, а объектp
изJob
Поле не задано. результат операции:
$ go run main.go
{"Age":18,"Name":"dj"}
Metadata
Некоторая полезная информация получается при декодировании,mapstructure
можно использоватьMetadata
собрать эту информацию.Metadata
Структура выглядит следующим образом:
// mapstructure.go
type Metadata struct {
Keys []string
Unused []string
}
Metadata
Экспортируемых полей всего два:
-
Keys
: имя ключа успешного декодирования; -
Unused
: Имя ключа, которое существует в исходных данных, но отсутствует в целевой структуре.
Чтобы собрать эти данные, нам нужно использоватьDecodeMetadata
заменитьDecode
метод:
type Person struct {
Name string
Age int
}
func main() {
m := map[string]interface{}{
"name": "dj",
"age": 18,
"job": "programmer",
}
var p Person
var metadata mapstructure.Metadata
mapstructure.DecodeMetadata(m, &p, &metadata)
fmt.Printf("keys:%#v unused:%#v\n", metadata.Keys, metadata.Unused)
}
сначала определитеMetadata
структура, входящиеDecodeMetadata
Соберите расшифрованную информацию. результат операции:
$ go run main.go
keys:[]string{"Name", "Age"} unused:[]string{"job"}
обработка ошибок
mapstructure
В процессе преобразования неизбежно будут возникать ошибки, например, тип ключа в JSON не соответствует типу поля в соответствующей структуре Go.Decode/DecodeMetadata
вернет эти ошибки:
type Person struct {
Name string
Age int
Emails []string
}
func main() {
m := map[string]interface{}{
"name": 123,
"age": "bad value",
"emails": []int{1, 2, 3},
}
var p Person
err := mapstructure.Decode(m, &p)
if err != nil {
fmt.Println(err.Error())
}
}
В приведенном выше коде в структуреPerson
Среднее полеName
заstring
тип, но вводname
заint
тип; полеAge
заint
тип, но вводage
заstring
тип; полеEmails
за[]string
тип, но вводemails
за[]int
тип. СледовательноDecode
Возвращает ошибку. результат операции:
$ go run main.go
5 error(s) decoding:
* 'Age' expected type 'int', got unconvertible type 'string'
* 'Emails[0]' expected type 'string', got unconvertible type 'int'
* 'Emails[1]' expected type 'string', got unconvertible type 'int'
* 'Emails[2]' expected type 'string', got unconvertible type 'int'
* 'Name' expected type 'string', got unconvertible type 'int'
Из сообщения об ошибке легко понять, что пошло не так.
Слабо типизированный ввод
Иногда мы не хотимmap[string]interface{}
Выполните строгую проверку согласованности типов для соответствующего значения ключа. В это время вы можете использоватьWeakDecode/WeakDecodeMetadata
методы, они попытаются выполнить преобразование типов:
type Person struct {
Name string
Age int
Emails []string
}
func main() {
m := map[string]interface{}{
"name": 123,
"age": "18",
"emails": []int{1, 2, 3},
}
var p Person
err := mapstructure.WeakDecode(m, &p)
if err == nil {
fmt.Println("person:", p)
} else {
fmt.Println(err.Error())
}
}
Хотя ключname
соответствующее значение123
даint
типа, но вWeakDecode
преобразует его вstring
введите, чтобы соответствоватьPerson.Name
Тип поля. такой же,age
значение"18"
даstring
введитеWeakDecode
преобразует его вint
введите, чтобы соответствоватьPerson.Age
Тип поля.
Следует отметить одну вещь: если преобразование типа не удается,WeakDecode
Также возвращает ошибку. Например, в приведенном выше примереage
Установить как"bad value"
, его нельзя преобразовать вint
типа, поэтому возвращается ошибка.
декодер
Помимо методов, описанных выше,mapstructure
Также предоставляет более гибкий декодер (Decoder
).可以通过配置DecoderConfig
Реализуйте любую из функций, описанных выше:
// mapstructure.go
type DecoderConfig struct {
ErrorUnused bool
ZeroFields bool
WeaklyTypedInput bool
Metadata *Metadata
Result interface{}
TagName string
}
Значение каждого поля следующее:
-
ErrorUnused
:заtrue
, если значение ключа во входных данных не имеет соответствующего поля, возвращается ошибка; -
ZeroFields
:заtrue
когда, вDecode
Перед опорожнением целиmap
. заfalse
, то выполнениеmap
слияние. используется вstruct
прибытьmap
в конверсии; -
WeaklyTypedInput
:выполнитьWeakDecode/WeakDecodeMetadata
функция; -
Metadata
: не дляnil
при сбореMetadata
данные; -
Result
: объект результата, вmap
прибытьstruct
в конверсии,Result
заstruct
тип. существуетstruct
прибытьmap
в конверсии,Result
заmap
тип; -
TagName
: используется по умолчаниюmapstructure
В качестве имени тега структуры его можно задать через это поле.
См. пример:
type Person struct {
Name string
Age int
}
func main() {
m := map[string]interface{}{
"name": 123,
"age": "18",
"job": "programmer",
}
var p Person
var metadata mapstructure.Metadata
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
WeaklyTypedInput: true,
Result: &p,
Metadata: &metadata,
})
if err != nil {
log.Fatal(err)
}
err = decoder.Decode(m)
if err == nil {
fmt.Println("person:", p)
fmt.Printf("keys:%#v, unused:%#v\n", metadata.Keys, metadata.Unused)
} else {
fmt.Println(err.Error())
}
}
использовать здесьDecoder
реализует пример кода из раздела Weakly Typed Input выше. ФактическиWeakDecode
Внутренне это достигается таким образом, следующееWeakDecode
Исходный код:
// mapstructure.go
func WeakDecode(input, output interface{}) error {
config := &DecoderConfig{
Metadata: nil,
Result: output,
WeaklyTypedInput: true,
}
decoder, err := NewDecoder(config)
if err != nil {
return err
}
return decoder.Decode(input)
}
По факту,Decode/DecodeMetadata/WeakDecodeMetadata
Сначала устанавливается внутриDecoderConfig
соответствующие поля , а затем создайтеDecoder
объект и, наконец, вызвать егоDecode
метод реализован.
Суммировать
mapstructure
Элегантная реализация, богатый функционал, понятная структура кода, настоятельно рекомендую ознакомиться!
Если вы найдете забавную и простую в использовании языковую библиотеку Go, вы можете отправить сообщение о проблеме в ежедневной библиотеке Go GitHub😄
Ссылаться на
- структура карты GitHub:GitHub.com/Митчелл не/не…
- Перейти на ежедневный репозиторий GitHub:GitHub.com/Darenjun/go-of…
я
мой блог:darjun.github.io
Добро пожаловать, чтобы обратить внимание на мою общедоступную учетную запись WeChat [GoUpUp], учитесь вместе и добивайтесь прогресса вместе ~