├── .gitignore ├── LICENSE ├── README.md ├── README_zh.md ├── go.mod ├── structmap.go └── structmap_test └── to_map_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Liang Yaopei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Go Report Card](https://goreportcard.com/badge/github.com/liangyaopei/structmap)](https://goreportcard.com/report/github.com/liangyaopei/structmap) 2 | [![GoDoc](https://godoc.org/github.com/liangyaopei/structmap?status.svg)](http://godoc.org/github.com/liangyaopei/structmap) 3 | 4 | [中文版说明](README_zh.md) 5 | 6 | This repo provides a function convert a struct in Golang to a map(unmarshal a struct to a map).It supports 7 | 1. use tag to define the name of a filed in struct when converted in map. If no tag is specified, it will the name of field as key. 8 | 2. a filed in struct can customize its to map method. 9 | To note that the customized method should have `(string,interface{})` as output, with the `string` as key in map and `interface{}` as value in map. 10 | 11 | Also, it skips unexported filed , nil pointer and field without tag. 12 | 13 | 14 | ## Tags 15 | For now, it supports 4 tags: 16 | - '-' means ignoring this field 17 | - 'omitempty' means ignoring this field when it is empty 18 | - 'dive' means recursively traversing the struct, converting every filed in struct to be a key in map 19 | - 'wildcard' applies for string value, it returns "%" +value +"%", which is conveniently for db wildcard query 20 | 21 | 22 | ## Example 23 | For example, 24 | ```go 25 | type User struct { 26 | Name string `map:"name,omitempty"` // string 27 | Github GithubPage `map:"github,dive,omitempty"` // struct dive 28 | NoDive StructNoDive `map:"no_dive,omitempty"` // no dive struct 29 | MyProfile Profile `map:"my_profile,omitempty"` // struct implements its own method 30 | } 31 | 32 | type GithubPage struct { 33 | URL string `map:"url"` 34 | Star int `map:"star"` 35 | } 36 | 37 | type StructNoDive struct { 38 | NoDive int `map:"no_dive_int"` 39 | } 40 | 41 | type Profile struct { 42 | Experience string `map:"experience"` 43 | Date time.Time `map:"time"` 44 | } 45 | 46 | // its own toMap method 47 | func (p Profile) StructToMap() (key string, value interface{}) { 48 | return "time", p.Date.Format(timeLayout) 49 | } 50 | ``` 51 | by using 52 | ```go 53 | res, err := structmap.StructToMap(&user, tag, methodName) 54 | ``` 55 | can be converted to be 56 | ```go 57 | map[string]interface{}{ 58 | "name": "user", 59 | "no_dive": map[string]int{"no_dive_int": 1}, 60 | // dive struct field 61 | "url": "https://github.com/liangyaopei", 62 | "star": 1, 63 | // customized method 64 | "time": "2020-07-21 12:00:00", 65 | } 66 | ``` 67 | 68 | ## Performance 69 | Compared with using `marshal`/`unmarshal` function in `json` package, this repo has better performance. 70 | ```go 71 | func BenchmarkStructToMapByJson(b *testing.B) { 72 | user := newBenchmarkUser() 73 | b.ResetTimer() 74 | for i := 0; i < b.N; i++ { 75 | data, _ := json.Marshal(&user) 76 | m := make(map[string]interface{}) 77 | json.Unmarshal(data, &m) 78 | } 79 | } 80 | 81 | func BenchmarkStructToMapByToMap(b *testing.B) { 82 | user := newBenchmarkUser() 83 | tag := "json" 84 | methodName := "" 85 | b.ResetTimer() 86 | for i := 0; i < b.N; i++ { 87 | struct_to_map.StructToMap(&user, tag, methodName) 88 | } 89 | } 90 | ``` 91 | The result of using `StructToMap` 92 | ```sh 93 | $ go test ./... -bench=BenchmarkStructToMapByToMap -benchmem -run=^$ -count=10 94 | 95 | BenchmarkStructToMapByToMap-4 1322222 960 ns/op 488 B/op 14 allocs/op 96 | BenchmarkStructToMapByToMap-4 1225311 965 ns/op 488 B/op 14 allocs/op 97 | BenchmarkStructToMapByToMap-4 1201177 947 ns/op 488 B/op 14 allocs/op 98 | BenchmarkStructToMapByToMap-4 1308039 895 ns/op 488 B/op 14 allocs/op 99 | BenchmarkStructToMapByToMap-4 1201592 936 ns/op 488 B/op 14 allocs/op 100 | BenchmarkStructToMapByToMap-4 1351603 882 ns/op 488 B/op 14 allocs/op 101 | BenchmarkStructToMapByToMap-4 1361508 878 ns/op 488 B/op 14 allocs/op 102 | BenchmarkStructToMapByToMap-4 1355869 876 ns/op 488 B/op 14 allocs/op 103 | BenchmarkStructToMapByToMap-4 1353205 1151 ns/op 488 B/op 14 allocs/op 104 | BenchmarkStructToMapByToMap-4 1278057 1487 ns/op 488 B/op 14 allocs/op 105 | ``` 106 | The result of using `json` package 107 | ```sh 108 | $ go test ./... -bench=BenchmarkStructToMapByJson -benchmem -run=^$ -count=10 109 | BenchmarkStructToMapByJson-4 383409 3171 ns/op 992 B/op 30 allocs/op 110 | BenchmarkStructToMapByJson-4 314690 3297 ns/op 992 B/op 30 allocs/op 111 | BenchmarkStructToMapByJson-4 363764 3009 ns/op 992 B/op 30 allocs/op 112 | BenchmarkStructToMapByJson-4 365799 3122 ns/op 992 B/op 30 allocs/op 113 | BenchmarkStructToMapByJson-4 361111 3057 ns/op 992 B/op 30 allocs/op 114 | BenchmarkStructToMapByJson-4 335830 3176 ns/op 992 B/op 30 allocs/op 115 | BenchmarkStructToMapByJson-4 347206 3069 ns/op 992 B/op 30 allocs/op 116 | BenchmarkStructToMapByJson-4 380973 3067 ns/op 992 B/op 30 allocs/op 117 | BenchmarkStructToMapByJson-4 373834 3000 ns/op 992 B/op 30 allocs/op 118 | BenchmarkStructToMapByJson-4 373597 3206 ns/op 992 B/op 30 allocs/op 119 | ``` -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | [English Version](README.md) 2 | 3 | 这个仓库提供将Golang的结构体转化为`map`的函数。它支持: 4 | 1. 使用`tag`去定义结构体中的域(field)的名字。如果没有指定,就使用结构体的域的名字。 5 | 2. 域(field)可以自定义自己的转化成map的方法。这个自定义的方法要有`(string,interface{})`作为输出,其中`string`作为map的键(key),`interface`作为map的值。 6 | 7 | 并且,会跳过没有导出的域,空指针,和没有标签的域。 8 | 9 | ## 标签 10 | 目前,支持4种标签 11 | - '-':忽略当前这个域 12 | - 'omitempty' : 当这个域的值为空,忽略这个域 13 | - 'dive' : 递归地遍历这个结构体,将所有字段作为键 14 | - 'wildcard': 只适用于字符串类型,返回"%"+值+"%",这是为了方便数据库的模糊查询 15 | 16 | ## 例子 17 | 举个例子, 18 | ```go 19 | type User struct { 20 | Name string `map:"name,omitempty"` // string 21 | Github GithubPage `map:"github,dive,omitempty"` // struct dive 22 | NoDive StructNoDive `map:"no_dive,omitempty"` // no dive struct 23 | MyProfile Profile `map:"my_profile,omitempty"` // struct implements its own method 24 | } 25 | 26 | type GithubPage struct { 27 | URL string `map:"url"` 28 | Star int `map:"star"` 29 | } 30 | 31 | type StructNoDive struct { 32 | NoDive int `map:"no_dive_int"` 33 | } 34 | 35 | type Profile struct { 36 | Experience string `map:"experience"` 37 | Date time.Time `map:"time"` 38 | } 39 | 40 | // its own toMap method 41 | func (p Profile) StructToMap() (key string, value interface{}) { 42 | return "time", p.Date.Format(timeLayout) 43 | } 44 | ``` 45 | 使用 46 | ```go 47 | res, err := structmap.StructToMap(&user, tag, methodName) 48 | ``` 49 | 转化为 50 | ```go 51 | map[string]interface{}{ 52 | "name": "user", 53 | "no_dive": map[string]int{"no_dive_int": 1}, 54 | // dive struct field 55 | "url": "https://github.com/liangyaopei", 56 | "star": 1, 57 | // customized method 58 | "time": "2020-07-21 12:00:00", 59 | } 60 | ``` 61 | 62 | ## 性能 63 | 与使用`json`包的`marshal`/`unmarshal`比较,这个函数有更好的性能 64 | ```go 65 | func BenchmarkStructToMapByJson(b *testing.B) { 66 | user := newBenchmarkUser() 67 | b.ResetTimer() 68 | for i := 0; i < b.N; i++ { 69 | data, _ := json.Marshal(&user) 70 | m := make(map[string]interface{}) 71 | json.Unmarshal(data, &m) 72 | } 73 | } 74 | 75 | func BenchmarkStructToMapByToMap(b *testing.B) { 76 | user := newBenchmarkUser() 77 | tag := "json" 78 | methodName := "" 79 | b.ResetTimer() 80 | for i := 0; i < b.N; i++ { 81 | struct_to_map.StructToMap(&user, tag, methodName) 82 | } 83 | } 84 | ``` 85 | 使用 `StructToMap`的结果 86 | ```sh 87 | $ go test ./... -bench=BenchmarkStructToMapByToMap -benchmem -run=^$ -count=10 88 | 89 | BenchmarkStructToMapByToMap-4 1322222 960 ns/op 488 B/op 14 allocs/op 90 | BenchmarkStructToMapByToMap-4 1225311 965 ns/op 488 B/op 14 allocs/op 91 | BenchmarkStructToMapByToMap-4 1201177 947 ns/op 488 B/op 14 allocs/op 92 | BenchmarkStructToMapByToMap-4 1308039 895 ns/op 488 B/op 14 allocs/op 93 | BenchmarkStructToMapByToMap-4 1201592 936 ns/op 488 B/op 14 allocs/op 94 | BenchmarkStructToMapByToMap-4 1351603 882 ns/op 488 B/op 14 allocs/op 95 | BenchmarkStructToMapByToMap-4 1361508 878 ns/op 488 B/op 14 allocs/op 96 | BenchmarkStructToMapByToMap-4 1355869 876 ns/op 488 B/op 14 allocs/op 97 | BenchmarkStructToMapByToMap-4 1353205 1151 ns/op 488 B/op 14 allocs/op 98 | BenchmarkStructToMapByToMap-4 1278057 1487 ns/op 488 B/op 14 allocs/op 99 | ``` 100 | 使用 `json` 包的结果 101 | ```sh 102 | $ go test ./... -bench=BenchmarkStructToMapByJson -benchmem -run=^$ -count=10 103 | BenchmarkStructToMapByJson-4 383409 3171 ns/op 992 B/op 30 allocs/op 104 | BenchmarkStructToMapByJson-4 314690 3297 ns/op 992 B/op 30 allocs/op 105 | BenchmarkStructToMapByJson-4 363764 3009 ns/op 992 B/op 30 allocs/op 106 | BenchmarkStructToMapByJson-4 365799 3122 ns/op 992 B/op 30 allocs/op 107 | BenchmarkStructToMapByJson-4 361111 3057 ns/op 992 B/op 30 allocs/op 108 | BenchmarkStructToMapByJson-4 335830 3176 ns/op 992 B/op 30 allocs/op 109 | BenchmarkStructToMapByJson-4 347206 3069 ns/op 992 B/op 30 allocs/op 110 | BenchmarkStructToMapByJson-4 380973 3067 ns/op 992 B/op 30 allocs/op 111 | BenchmarkStructToMapByJson-4 373834 3000 ns/op 992 B/op 30 allocs/op 112 | BenchmarkStructToMapByJson-4 373597 3206 ns/op 992 B/op 30 allocs/op 113 | ``` -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/liangyaopei/structmap 2 | 3 | go 1.14 4 | -------------------------------------------------------------------------------- /structmap.go: -------------------------------------------------------------------------------- 1 | package structmap 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | ) 8 | 9 | const ( 10 | methodResNum = 2 11 | ) 12 | 13 | const ( 14 | OptIgnore = "-" 15 | OptOmitempty = "omitempty" 16 | OptDive = "dive" 17 | OptWildcard = "wildcard" 18 | ) 19 | 20 | const ( 21 | flagIgnore = 1 << iota 22 | flagOmiEmpty 23 | flagDive 24 | flagWildcard 25 | ) 26 | 27 | // StructToMap convert a golang sturct to a map 28 | // key can be specified by tag, LIKE `map:"tag"`. 29 | // If there is no tag, struct filed name will be used instead 30 | // methodName is the name the field has implemented. 31 | // If implemented, it uses the method to get the key and value 32 | func StructToMap(s interface{}, tag string, methodName string) (res map[string]interface{}, err error) { 33 | v := reflect.ValueOf(s) 34 | 35 | if v.Kind() == reflect.Ptr && v.IsNil() { 36 | return nil, fmt.Errorf("%s is a nil pointer", v.Kind().String()) 37 | } 38 | if v.Kind() == reflect.Ptr { 39 | v = v.Elem() 40 | } 41 | // only accept struct param 42 | if v.Kind() != reflect.Struct { 43 | return nil, fmt.Errorf("s is not a struct but %s", v.Kind().String()) 44 | } 45 | 46 | t := v.Type() 47 | res = make(map[string]interface{}) 48 | for i := 0; i < t.NumField(); i++ { 49 | fieldType := t.Field(i) 50 | 51 | // ignore unexported field 52 | if fieldType.PkgPath != "" { 53 | continue 54 | } 55 | // read tag 56 | tagVal, flag := readTag(fieldType, tag) 57 | 58 | if flag&flagIgnore != 0 { 59 | continue 60 | } 61 | 62 | fieldValue := v.Field(i) 63 | if flag&flagOmiEmpty != 0 && fieldValue.IsZero() { 64 | continue 65 | } 66 | 67 | // ignore nil pointer in field 68 | if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() { 69 | continue 70 | } 71 | if fieldValue.Kind() == reflect.Ptr { 72 | fieldValue = fieldValue.Elem() 73 | } 74 | 75 | // get kind 76 | switch fieldValue.Kind() { 77 | case reflect.Slice, reflect.Array: 78 | if methodName != "" { 79 | _, ok := fieldValue.Type().MethodByName(methodName) 80 | if ok { 81 | key, value, err := callFunc(fieldValue, methodName) 82 | if err != nil { 83 | return nil, err 84 | } 85 | res[key] = value 86 | continue 87 | } 88 | } 89 | res[tagVal] = fieldValue 90 | case reflect.Struct: 91 | if methodName != "" { 92 | _, ok := fieldValue.Type().MethodByName(methodName) 93 | if ok { 94 | key, value, err := callFunc(fieldValue, methodName) 95 | if err != nil { 96 | return nil, err 97 | } 98 | res[key] = value 99 | continue 100 | } 101 | } 102 | 103 | // recursive 104 | deepRes, deepErr := StructToMap(fieldValue.Interface(), tag, methodName) 105 | if deepErr != nil { 106 | return nil, deepErr 107 | } 108 | if flag&flagDive != 0 { 109 | for k, v := range deepRes { 110 | res[k] = v 111 | } 112 | } else { 113 | res[tagVal] = deepRes 114 | } 115 | case reflect.Map: 116 | res[tagVal] = fieldValue 117 | case reflect.Chan: 118 | res[tagVal] = fieldValue 119 | case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64: 120 | res[tagVal] = fieldValue.Int() 121 | case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64: 122 | res[tagVal] = fieldValue.Uint() 123 | case reflect.Float32, reflect.Float64: 124 | res[tagVal] = fieldValue.Float() 125 | case reflect.String: 126 | if flag&flagWildcard != 0 { 127 | res[tagVal] = "%" + fieldValue.String() + "%" 128 | } else { 129 | res[tagVal] = fieldValue.String() 130 | } 131 | case reflect.Bool: 132 | res[tagVal] = fieldValue.Bool() 133 | case reflect.Complex64, reflect.Complex128: 134 | res[tagVal] = fieldValue.Complex() 135 | case reflect.Interface: 136 | res[tagVal] = fieldValue.Interface() 137 | default: 138 | } 139 | } 140 | return 141 | } 142 | 143 | // readTag read tag with format `json:"name,omitempty"` or `json:"-"` 144 | // For now, only supports above format 145 | func readTag(f reflect.StructField, tag string) (string, int) { 146 | val, ok := f.Tag.Lookup(tag) 147 | fieldTag := "" 148 | flag := 0 149 | 150 | // no tag, skip this field 151 | if !ok { 152 | flag |= flagIgnore 153 | return "", flag 154 | } 155 | opts := strings.Split(val, ",") 156 | 157 | fieldTag = opts[0] 158 | for i := 0; i < len(opts); i++ { 159 | switch opts[i] { 160 | case OptIgnore: 161 | flag |= flagIgnore 162 | case OptOmitempty: 163 | flag |= flagOmiEmpty 164 | case OptDive: 165 | flag |= flagDive 166 | case OptWildcard: 167 | flag |= flagWildcard 168 | } 169 | } 170 | 171 | return fieldTag, flag 172 | } 173 | 174 | // call function 175 | func callFunc(fv reflect.Value, methodName string) (string, interface{}, error) { 176 | methodRes := fv.MethodByName(methodName).Call([]reflect.Value{}) 177 | if len(methodRes) != methodResNum { 178 | return "", nil, fmt.Errorf("wrong method %s, should have 2 output: (string,interface{})", methodName) 179 | } 180 | if methodRes[0].Kind() != reflect.String { 181 | return "", nil, fmt.Errorf("wrong method %s, first output should be string", methodName) 182 | } 183 | key := methodRes[0].String() 184 | return key, methodRes[1], nil 185 | } 186 | -------------------------------------------------------------------------------- /structmap_test/to_map_test.go: -------------------------------------------------------------------------------- 1 | package struct_to_map_test 2 | 3 | import ( 4 | "encoding/json" 5 | "strconv" 6 | "strings" 7 | "testing" 8 | "time" 9 | 10 | "github.com/liangyaopei/structmap" 11 | ) 12 | 13 | const timeLayout = "2006-01-02 15:04:05" 14 | 15 | type Profile struct { 16 | Experience string `map:"experience"` 17 | Date time.Time `map:"time"` 18 | } 19 | 20 | // its own toMap method 21 | func (p Profile) StructToMap() (key string, value interface{}) { 22 | return "time", p.Date.Format(timeLayout) 23 | } 24 | 25 | type MySlice []int 26 | 27 | // its own toMap method 28 | func (a MySlice) StructToMap() (string, interface{}) { 29 | key := "array" 30 | b := strings.Builder{} 31 | if len(a) == 0 { 32 | return key, b.String() 33 | } 34 | for i := 0; i < len(a); i++ { 35 | b.WriteString(strconv.Itoa(a[i]) + ",") 36 | } 37 | return key, b.String() 38 | } 39 | 40 | // alias type 41 | type Gender int 42 | 43 | const ( 44 | male Gender = 1 45 | female Gender = 2 46 | ) 47 | 48 | // Dive struct 49 | type GithubPage struct { 50 | URL string `map:"url"` 51 | Star int `map:"star"` 52 | } 53 | 54 | type StructNoDive struct { 55 | NoDive int `map:"no_dive"` 56 | } 57 | 58 | // User is used for demonstration 59 | type User struct { 60 | Name string `map:"name,omitempty,wildcard"` // string 61 | Email *string `map:"email_ptr,omitempty"` // pointer 62 | MyGender Gender `map:"gender,omitempty"` // type alias 63 | Github GithubPage `map:"github,dive,omitempty"` // struct dive 64 | NoDive StructNoDive `map:"no_dive,omitempty"` // no dive struct 65 | MyProfile Profile `map:"my_profile,omitempty"` // struct implements its own method 66 | Arr []int `map:"arr,omitempty"` // normal slice 67 | MyArr MySlice `map:"my_arr,omitempty"` // slice implements its own method 68 | IgnoreFiled string `map:"-"` 69 | } 70 | 71 | func newUser() User { 72 | name := "user" 73 | email := "yaopei.liang@foxmail.com" 74 | myGender := male 75 | MyGithub := GithubPage{ 76 | URL: "https://github.com/liangyaopei", 77 | Star: 1, 78 | } 79 | NoDive := StructNoDive{NoDive: 1} 80 | dateStr := "2020-07-21 12:00:00" 81 | date, _ := time.Parse(timeLayout, dateStr) 82 | profile := Profile{ 83 | Experience: "my experience", 84 | Date: date, 85 | } 86 | arr := []int{1, 2, 3} 87 | myArr := MySlice{11, 12, 13} 88 | return User{ 89 | Name: name, 90 | Email: &email, 91 | MyGender: myGender, 92 | Github: MyGithub, 93 | NoDive: NoDive, 94 | MyProfile: profile, 95 | Arr: arr, 96 | MyArr: myArr, 97 | IgnoreFiled: "ignore", 98 | } 99 | } 100 | 101 | func TestStructToMap(t *testing.T) { 102 | user := newUser() 103 | tag := "map" 104 | methodName := "StructToMap" 105 | res, err := structmap.StructToMap(&user, tag, methodName) 106 | if err != nil { 107 | t.Errorf("struct to map:%s", err.Error()) 108 | return 109 | } 110 | for k, v := range res { 111 | t.Logf("k:%v,v:%v", k, v) 112 | } 113 | } 114 | 115 | type benchmarkUser struct { 116 | Name string `json:"name"` 117 | Age int `json:"age"` 118 | Address string `json:"address"` 119 | Contact string `json:"contact"` 120 | } 121 | 122 | func newBenchmarkUser() benchmarkUser { 123 | return benchmarkUser{ 124 | Name: "name", 125 | Age: 18, 126 | Address: "github address", 127 | Contact: "github contact", 128 | } 129 | } 130 | 131 | func BenchmarkStructToMapByJson(b *testing.B) { 132 | user := newBenchmarkUser() 133 | b.ResetTimer() 134 | for i := 0; i < b.N; i++ { 135 | data, _ := json.Marshal(&user) 136 | m := make(map[string]interface{}) 137 | json.Unmarshal(data, &m) 138 | } 139 | } 140 | 141 | func BenchmarkStructToMapByToMap(b *testing.B) { 142 | user := newBenchmarkUser() 143 | tag := "json" 144 | methodName := "" 145 | b.ResetTimer() 146 | for i := 0; i < b.N; i++ { 147 | structmap.StructToMap(&user, tag, methodName) 148 | } 149 | } 150 | --------------------------------------------------------------------------------