欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

golang使用mapstructure解析json

 更新時間:2023年12月27日 09:47:26   作者:愛發(fā)白日夢的后端  
mapstructure?是一個?Go?庫,用于將通用映射值解碼為結(jié)構(gòu),這篇文章主要來和大家介紹一下golang如何使用mapstructure解析json,需要的可以參考下

背景

前幾天群里的小伙伴問了一個這樣的問題:

其實質(zhì)就是在面對 value 類型不確定的情況下,怎么解析這個 json?

我下意識就想到了 [mapstructure](https://github.com/mitchellh/mapstructure) 這個庫,它可以幫助我們類似 PHP 那樣去處理弱類型的結(jié)構(gòu)。

介紹

先來介紹一下 mapstructure 這個庫主要用來做什么的吧,官網(wǎng)是這么介紹的:

mapstructure 是一個 Go 庫,用于將通用映射值解碼為結(jié)構(gòu),反之亦然,同時提供有用的錯誤處理。

該庫在解碼數(shù)據(jù)流(JSON、Gob 等)中的值時最為有用,因為在讀取部分?jǐn)?shù)據(jù)之前,您并不十分清楚底層數(shù)據(jù)的結(jié)構(gòu)。因此,您可以讀取 map[string]interface{} 并使用此庫將其解碼為適當(dāng)?shù)谋镜?Go 底層結(jié)構(gòu)。

簡單來說,它擅長解析一些我們并不十分清楚底層數(shù)據(jù)結(jié)構(gòu)的數(shù)據(jù)流到我們定義的結(jié)構(gòu)體中。

下面我們通過幾個例子來簡單介紹一下 mapstructure 怎么使用。

例子

普通形式

func normalDecode() {
 type Person struct {
  Name   string
  Age    int
  Emails []string
  Extra  map[string]string
 }

 // 此輸入可以來自任何地方,但通常來自諸如解碼 JSON 之類的東西,我們最初不太確定結(jié)構(gòu)。
 input := map[string]interface{}{
  "name":   "Tim",
  "age":    31,
  "emails": []string{"one@gmail.com", "two@gmail.com", "three@gmail.com"},
  "extra": map[string]string{
   "twitter": "Tim",
  },
 }

 var result Person
 err := mapstructure.Decode(input, &result)
 if err != nil {
  panic(err)
 }

 fmt.Printf("%#v\n", result)
}

輸出:

main.Person{Name:"Tim", Age:31, Emails:[]string{"one@gmail.com", "two@gmail.com", "three@gmail.com"}, Extra:map[string]string{"twitter":"Tim"}}

這個方式應(yīng)該是我們最經(jīng)常使用的,非常簡單的將 map[string]interface{} 映射到我們的結(jié)構(gòu)體中。

在這里,我們并沒有指定每個 field 的 tag,讓 mapstructure 自動去映射。

如果我們的 input 是一個 json 字符串,那么我們需要將 json 字符串解析為 map[string]interface{} 之后,再將其映射到我們的結(jié)構(gòu)體中。

func jsonDecode() {
 var jsonStr = `{
 "name": "Tim",
 "age": 31,
 "gender": "male"
}`

 type Person struct {
  Name   string
  Age    int
  Gender string
 }
 m := make(map[string]interface{})
 err := json.Unmarshal([]byte(jsonStr), &m)
 if err != nil {
  panic(err)
 }

 var result Person
 err = mapstructure.Decode(m, &result)
 if err != nil {
  panic(err.Error())
 }
 fmt.Printf("%#v\n", result)
}

輸出:

main.Person{Name:"Tim", Age:31, Gender:"male"}

嵌入式結(jié)構(gòu)

mapstructure 允許我們壓縮多個嵌入式結(jié)構(gòu),并通過 squash 標(biāo)簽進(jìn)行處理。

func embeddedStructDecode() {
 // 使用 squash 標(biāo)簽允許壓縮多個嵌入式結(jié)構(gòu)。通過創(chuàng)建多種類型的復(fù)合結(jié)構(gòu)并對其進(jìn)行解碼來演示此功能。
 type Family struct {
  LastName string
 }
 type Location struct {
  City string
 }
 type Person struct {
  Family    `mapstructure:",squash"`
  Location  `mapstructure:",squash"`
  FirstName string
 }

 input := map[string]interface{}{
  "FirstName": "Tim",
  "LastName":  "Liu",
  "City":      "China, Guangdong",
 }

 var result Person
 err := mapstructure.Decode(input, &result)
 if err != nil {
  panic(err)
 }

 fmt.Printf("%s %s, %s\n", result.FirstName, result.LastName, result.City)
}

輸出:

Tim Liu, China, Guangdong

在這個例子中, Person 里面有著 Location 和 Family 的嵌入式結(jié)構(gòu)體,通過 squash 標(biāo)簽進(jìn)行壓縮,從而達(dá)到平鋪的作用。

元數(shù)據(jù)

func metadataDecode() {
 type Person struct {
  Name   string
  Age    int
  Gender string
 }

 // 此輸入可以來自任何地方,但通常來自諸如解碼 JSON 之類的東西,我們最初不太確定結(jié)構(gòu)。
 input := map[string]interface{}{
  "name":  "Tim",
  "age":   31,
  "email": "one@gmail.com",
 }

 // 對于元數(shù)據(jù),我們制作了一個更高級的 DecoderConfig,以便我們可以更細(xì)致地配置所使用的解碼器。在這種情況下,我們只是告訴解碼器我們想要跟蹤元數(shù)據(jù)。
 var md mapstructure.Metadata
 var result Person
 config := &mapstructure.DecoderConfig{
  Metadata: &md,
  Result:   &result,
 }

 decoder, err := mapstructure.NewDecoder(config)
 if err != nil {
  panic(err)
 }

 if err = decoder.Decode(input); err != nil {
  panic(err)
 }

 fmt.Printf("value: %#v, keys: %#v, Unused keys: %#v, Unset keys: %#v\n", result, md.Keys, md.Unused, md.Unset)
}

輸出:

value: main.Person{Name:"Tim", Age:31, Gender:""}, keys: []string{"Name", "Age"}, Unused keys: []string{"email"}, Unset keys: []string{"Gender"}

從這個例子我們可以看出,使用 Metadata 可以記錄我們結(jié)構(gòu)體以及 map[string]interface{} 的差異,相同的部分會正確映射到對應(yīng)的字段中,而差異則使用了 Unused 和 Unset 來表達(dá)。

  • Unused:map 中有著結(jié)構(gòu)體所沒有的字段。
  • Unset:結(jié)構(gòu)體中有著 map 中所沒有的字段。

避免空值的映射

這里的使用其實和內(nèi)置的 json 庫使用方式是一樣的,都是借助 omitempty 標(biāo)簽來解決。

func omitemptyDecode() {
 // 添加 omitempty 注釋以避免空值的映射鍵
 type Family struct {
  LastName string
 }
 type Location struct {
  City string
 }
 type Person struct {
  *Family   `mapstructure:",omitempty"`
  *Location `mapstructure:",omitempty"`
  Age       int
  FirstName string
 }

 result := &map[string]interface{}{}
 input := Person{FirstName: "Somebody"}
 err := mapstructure.Decode(input, &result)
 if err != nil {
  panic(err)
 }

 fmt.Printf("%+v\n", result)
}

輸出:

&map[Age:0 FirstName:Somebody]

這里我們可以看到 *Family 和 *Location 都被設(shè)置了 omitempty,所以在解析過程中會忽略掉空值。而 Age 沒有設(shè)置,并且 input 中沒有對應(yīng)的 value,所以在解析中使用對應(yīng)類型的零值來表達(dá),而 int 類型的零值就是 0。

剩余字段

func remainDataDecode() {
 type Person struct {
  Name  string
  Age   int
  Other map[string]interface{} `mapstructure:",remain"`
 }

 input := map[string]interface{}{
  "name":   "Tim",
  "age":    31,
  "email":  "one@gmail.com",
  "gender": "male",
 }

 var result Person
 err := mapstructure.Decode(input, &result)
 if err != nil {
  panic(err)
 }

 fmt.Printf("%#v\n", result)
}

輸出:

main.Person{Name:"Tim", Age:31, Other:map[string]interface {}{"email":"one@gmail.com", "gender":"male"}}

從代碼可以看到 Other 字段被設(shè)置了 remain,這意味著 input 中沒有正確映射的字段都會被放到 Other 中,從輸出可以看到,email 和 gender 已經(jīng)被正確的放到 Other 中了。

自定義標(biāo)簽

func tagDecode() {
 // 請注意,結(jié)構(gòu)類型中定義的 mapstructure 標(biāo)簽可以指示將值映射到哪些字段。
 type Person struct {
  Name string `mapstructure:"person_name"`
  Age  int    `mapstructure:"person_age"`
 }

 input := map[string]interface{}{
  "person_name": "Tim",
  "person_age":  31,
 }

 var result Person
 err := mapstructure.Decode(input, &result)
 if err != nil {
  panic(err)
 }

 fmt.Printf("%#v\n", result)
}

輸出:

main.Person{Name:"Tim", Age:31}

在 Person 結(jié)構(gòu)中,我們將 person_name 和 person_age 分別映射到 Name 和 Age 中,從而達(dá)到在不破壞結(jié)構(gòu)的基礎(chǔ)上,去正確的解析。

弱類型解析

正如前面所說,mapstructure 提供了類似 PHP 解析弱類型結(jié)構(gòu)的方法。

func weaklyTypedInputDecode() {
 type Person struct {
  Name   string
  Age    int
  Emails []string
 }

 // 此輸入可以來自任何地方,但通常來自諸如解碼 JSON 之類的東西,由 PHP 等弱類型語言生成。
 input := map[string]interface{}{
  "name":   123,  // number => string
  "age":    "31", // string => number
  "emails": map[string]interface{}{}, // empty map => empty array
 }

 var result Person
 config := &mapstructure.DecoderConfig{
  WeaklyTypedInput: true,
  Result:           &result,
 }

 decoder, err := mapstructure.NewDecoder(config)
 if err != nil {
  panic(err)
 }

 err = decoder.Decode(input)
 if err != nil {
  panic(err)
 }

 fmt.Printf("%#v\n", result)
}

輸出:

main.Person{Name:"123", Age:31, Emails:[]string{}}

從代碼可以看到,input 中的 nameage 和 Person 結(jié)構(gòu)體中的 Name、Age 類型不一致,而 email 更是離譜,一個字符串?dāng)?shù)組,一個是 map。

但是我們通過自定義 DecoderConfig,將 WeaklyTypedInput 設(shè)置成 true 之后,mapstructure 很容易幫助我們解決這類弱類型的解析問題。

但是也不是所有問題都能解決,通過源碼我們可以知道有如下限制:

//   - bools to string (true = "1", false = "0")
//   - numbers to string (base 10)
//   - bools to int/uint (true = 1, false = 0)
//   - strings to int/uint (base implied by prefix)
//   - int to bool (true if value != 0)
//   - string to bool (accepts: 1, t, T, TRUE, true, True, 0, f, F,
//     FALSE, false, False. Anything else is an error)
//   - empty array = empty map and vice versa
//   - negative numbers to overflowed uint values (base 10)
//   - slice of maps to a merged map
//   - single values are converted to slices if required. Each
//     element is weakly decoded. For example: "4" can become []int{4}
//     if the target type is an int slice.

大家使用這種弱類型解析的時候也需要注意。

錯誤處理

mapstructure 錯誤提示非常的友好,下面我們來看看遇到錯誤時,它是怎么提示的。

func decodeErrorHandle() {
 type Person struct {
  Name   string
  Age    int
  Emails []string
  Extra  map[string]string
 }

 input := map[string]interface{}{
  "name":   123,
  "age":    "bad value",
  "emails": []int{1, 2, 3},
 }

 var result Person
 err := mapstructure.Decode(input, &result)
 if err != nil {
  fmt.Println(err.Error())
 }
}

輸出:

5 error(s) decoding:

* 'Age' expected type 'int', got unconvertible type 'string', value: 'bad value'
* 'Emails[0]' expected type 'string', got unconvertible type 'int', value: '1'
* 'Emails[1]' expected type 'string', got unconvertible type 'int', value: '2'
* 'Emails[2]' expected type 'string', got unconvertible type 'int', value: '3'
* 'Name' expected type 'string', got unconvertible type 'int', value: '123'

這里的錯誤提示會告訴我們每個字段,字段里的值應(yīng)該需要怎么表達(dá),我們可以通過這些錯誤提示,比較快的去修復(fù)問題。

總結(jié)

從上面這些例子看看到 mapstructure 的強大之處,很好的幫我們解決了實實在在的問題,也在節(jié)省我們的開發(fā)成本。

但是從源碼來看,內(nèi)部使用了大量的反射,這可能會對一些特殊場景帶來性能隱患。所以大家在使用的時候,一定要充分考慮產(chǎn)品邏輯以及場景。

以下貼一小段刪減過的源碼:

// Decode decodes the given raw interface to the target pointer specified
// by the configuration.
func (d *Decoder) Decode(input interface{}) error {
 return d.decode("", input, reflect.ValueOf(d.config.Result).Elem())
}

// Decodes an unknown data type into a specific reflection value.
func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) error {
 ....

 var err error
 outputKind := getKind(outVal)
 addMetaKey := true
 switch outputKind {
 case reflect.Bool:
  err = d.decodeBool(name, input, outVal)
 case reflect.Interface:
  err = d.decodeBasic(name, input, outVal)
 case reflect.String:
  err = d.decodeString(name, input, outVal)
 case reflect.Int:
  err = d.decodeInt(name, input, outVal)
 case reflect.Uint:
  err = d.decodeUint(name, input, outVal)
 case reflect.Float32:
  err = d.decodeFloat(name, input, outVal)
 case reflect.Struct:
  err = d.decodeStruct(name, input, outVal)
 case reflect.Map:
  err = d.decodeMap(name, input, outVal)
 case reflect.Ptr:
  addMetaKey, err = d.decodePtr(name, input, outVal)
 case reflect.Slice:
  err = d.decodeSlice(name, input, outVal)
 case reflect.Array:
  err = d.decodeArray(name, input, outVal)
 case reflect.Func:
  err = d.decodeFunc(name, input, outVal)
 default:
  // If we reached this point then we weren't able to decode it
  return fmt.Errorf("%s: unsupported type: %s", name, outputKind)
 }

 // If we reached here, then we successfully decoded SOMETHING, so
 // mark the key as used if we're tracking metainput.
 if addMetaKey && d.config.Metadata != nil && name != "" {
  d.config.Metadata.Keys = append(d.config.Metadata.Keys, name)
 }

 return err
}

到此這篇關(guān)于golang使用mapstructure解析json的文章就介紹到這了,更多相關(guān)go mapstructure解析json內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 更換GORM默認(rèn)SQLite驅(qū)動出現(xiàn)的問題解決分析

    更換GORM默認(rèn)SQLite驅(qū)動出現(xiàn)的問題解決分析

    這篇文章主要為大家介紹了更換GORM默認(rèn)SQLite驅(qū)動出現(xiàn)的問題解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01
  • 一文帶你了解Golang中的泛型

    一文帶你了解Golang中的泛型

    泛型是一種可以編寫?yīng)毩⒂谑褂玫奶囟愋偷拇a的方法,可以通過編寫函數(shù)或類型來使用一組類型中的任何一個,下面就來和大家聊聊Golang中泛型的使用吧
    2023-07-07
  • go語言channel實現(xiàn)多核并行化運行的方法

    go語言channel實現(xiàn)多核并行化運行的方法

    這篇文章主要介紹了go語言channel實現(xiàn)多核并行化運行的方法,實例分析了channel實現(xiàn)多核并行化運行的技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-03-03
  • 詳解Golang中strconv庫的用法

    詳解Golang中strconv庫的用法

    strconv包提供了字符串和基本數(shù)據(jù)類型之間的相互轉(zhuǎn)換功能,本文將帶大家深入了解Go語言標(biāo)準(zhǔn)庫中的strconv包,掌握其常用的函數(shù)和用法,希望對大家有所幫助
    2023-06-06
  • GoLang調(diào)用鏈可視化go-callvis使用介紹

    GoLang調(diào)用鏈可視化go-callvis使用介紹

    與鏈路追蹤(Tracing)不同,Tracing關(guān)注復(fù)雜的分布式環(huán)境中各個服務(wù)節(jié)點間的調(diào)用關(guān)系,主要用于服務(wù)治理。而我們本次探索的代碼調(diào)用鏈路則是代碼方法級別的調(diào)用關(guān)系,主要用于代碼設(shè)計
    2023-02-02
  • Golang中的自定義函數(shù)詳解

    Golang中的自定義函數(shù)詳解

    函數(shù)構(gòu)成代碼執(zhí)行的邏輯結(jié)構(gòu)。在Go語言中,函數(shù)的基本組成為:關(guān)鍵字func、函數(shù)名、參數(shù)列表、返回值、函數(shù)體和返回語句。
    2018-10-10
  • go-zero熔斷機(jī)制組件Breaker接口定義使用解析

    go-zero熔斷機(jī)制組件Breaker接口定義使用解析

    這篇文章主要為大家介紹了go-zero熔斷機(jī)制組件Breaker接口定義使用解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-05-05
  • Golang中for循環(huán)的用法示例詳解

    Golang中for循環(huán)的用法示例詳解

    for循環(huán)就是讓一段代碼循環(huán)的執(zhí)行,接下來通過本文給大家講解Golang中for循環(huán)的用法,代碼簡單易懂,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-12-12
  • 基于Go語言實現(xiàn)插入排序算法及優(yōu)化

    基于Go語言實現(xiàn)插入排序算法及優(yōu)化

    插入排序是一種簡單的排序算法。這篇文章將利用Go語言實現(xiàn)冒泡排序算法,文中的示例代碼講解詳細(xì),對學(xué)習(xí)Go語言有一定的幫助,需要的可以參考一下
    2022-12-12
  • 深入淺析Go中三個點(...)用法

    深入淺析Go中三個點(...)用法

    這篇文章主要介紹了深入淺析Go中三個點(...)用法,需要的朋友可以參考下
    2021-10-10

最新評論