структура карты ежедневной библиотеки Go

Go

Введение

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😄

Ссылаться на

  1. структура карты GitHub:GitHub.com/Митчелл не/не…
  2. Перейти на ежедневный репозиторий GitHub:GitHub.com/Darenjun/go-of…

я

мой блог:darjun.github.io

Добро пожаловать, чтобы обратить внимание на мою общедоступную учетную запись WeChat [GoUpUp], учитесь вместе и добивайтесь прогресса вместе ~