├── .circleci └── config.yml ├── .gitignore ├── LICENSE ├── doc.go ├── go.mod ├── readme.md ├── smapping.go └── smapping_test.go /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Golang CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-go/ for more details 4 | version: 2.1 5 | jobs: 6 | build: 7 | docker: 8 | # specify the version 9 | - image: circleci/golang:1.13 10 | 11 | # Specify service dependencies here if necessary 12 | # CircleCI maintains a library of pre-built images 13 | # documented at https://circleci.com/docs/2.0/circleci-images/ 14 | # - image: circleci/postgres:9.4 15 | 16 | #### TEMPLATE_NOTE: go expects specific checkout path representing url 17 | #### expecting it in the form of 18 | #### /go/src/github.com/circleci/go-tool 19 | #### /go/src/bitbucket.org/circleci/go-tool 20 | working_directory: /go/src/github.com/mashingan/smapping 21 | steps: 22 | - add_ssh_keys: 23 | fingerprints: 24 | - "bb:5a:75:61:c5:6b:f4:03:2f:95:47:77:21:f5:8f:2d" 25 | - checkout 26 | 27 | # specify any bash command here prefixed with `run: ` 28 | - run: go get -v -t -d ./... 29 | - run: go test -v ./... 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all 2 | * 3 | 4 | # Unignore all with extensions 5 | !*.* 6 | 7 | # Don't ignore LICENSE 8 | !LICENSE 9 | 10 | # Unignore all dirs 11 | !*/ 12 | 13 | # vim swap and backup files 14 | *.un~ 15 | *.swap 16 | 17 | # windows executable 18 | *.exe 19 | *.ilk 20 | *.pdb 21 | 22 | # cached folder 23 | nimcache/ 24 | nim.cfg 25 | 26 | # built folder if available 27 | build/ 28 | 29 | # ignore go.sum 30 | go.sum 31 | 32 | .idea 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2022 Rahmatullah 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 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Rahmatullah 2 | // This library is licensed with MIT license which can be found 3 | // in LICENSE 4 | 5 | /* 6 | Package smapping is Library for collecting various operations on struct and its mapping 7 | to interface{} and/or map[string]interface{} type. 8 | Implemented to ease the conversion between Golang struct and json format 9 | together with ease of mapping selections using different part of field tagging. 10 | 11 | The implementation is abstraction on top reflection package, reflect. 12 | 13 | Examples 14 | 15 | The snippet code below will be used accross example for brevity 16 | 17 | type source struct { 18 | Label string `json:"label"` 19 | Info string `json:"info"` 20 | Version int `json:"version"` 21 | Toki time.Time `json:"tomare"` 22 | } 23 | 24 | type sink struct { 25 | Label string 26 | Info string 27 | } 28 | 29 | type differentSink struct { 30 | DiffLabel string `json:"label"` 31 | NiceInfo string `json:"info"` 32 | Version string `json:"unversion"` 33 | Toki time.Time `json:"doki"` 34 | } 35 | 36 | type differentSourceSink struct { 37 | Source source `json:"source"` 38 | DiffSink differentSink `json:"differentSink"` 39 | } 40 | 41 | var toki = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC) 42 | var sourceobj source = source{ 43 | Label: "source", 44 | Info: "the origin", 45 | Version: 1, 46 | Toki: toki, 47 | } 48 | 49 | func printIfNotExists(mapped Mapped, keys ...string) { 50 | for _, key := range keys { 51 | if _, ok := mapped[key]; !ok { 52 | fmt.Println(key, ": not exists") 53 | } 54 | } 55 | } 56 | */ 57 | package smapping 58 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mashingan/smapping 2 | 3 | go 1.14 4 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![license](https://img.shields.io/github/license/mashape/apistatus.svg?style=plastic)](./LICENSE) 2 | [![CircleCI](https://circleci.com/gh/mashingan/smapping.svg?style=svg)](https://circleci.com/gh/mashingan/smapping) 3 | [![GoDoc](https://godoc.org/github.com/mashingan/smapping?status.svg)](https://godoc.org/github.com/mashingan/smapping) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/mashingan/smapping)](https://goreportcard.com/report/github.com/mashingan/smapping) 5 | 6 | # smapping 7 | Golang structs generic mapping. 8 | 9 | ### Version Limit 10 | To support nesting object conversion, the lowest Golang version supported is `1.12.0`. 11 | To support `smapping.SQLScan`, the lowest Golang version supported is `1.13.0`. 12 | 13 | # Table of Contents 14 | 1. [Motivation At Glimpse](#at-glimpse). 15 | 2. [Motivation Length](#motivation). 16 | 3. [Install](#install). 17 | 4. [Examples](#examples). 18 | * [Basic usage examples](#basic-usage-examples) 19 | * [Nested object example](#nested-object-example) 20 | * [SQLScan usage example](#sqlscan-usage-example) 21 | * [Omit fields example](#omit-fields-example) 22 | 5. [License](#license). 23 | 24 | # At Glimpse 25 | ## What? 26 | A library to provide a mapped structure generically/dynamically. 27 | 28 | ## Who? 29 | Anyone who has to work with large structure. 30 | 31 | ## Why? 32 | Scalability and Flexibility. 33 | 34 | ## When? 35 | At the runtime. 36 | 37 | ## Where? 38 | In users code. 39 | 40 | ## How? 41 | By converting into `smapping.Mapped` which alias for `map[string]interface{}`, 42 | users can iterate the struct arbitarily with `reflect` package. 43 | 44 | # Motivation 45 | Working with between ``struct``, and ``json`` with **Golang** has various 46 | degree of difficulty. 47 | The thing that makes difficult is that sometimes we get arbitrary ``json`` 48 | or have to make json with arbitrary fields. Sometime we also need to have 49 | a different field names, extracting specific fields, working with same 50 | structure with different domain fields name etc. 51 | 52 | In order to answer those flexibility, we map the object struct to the 53 | more general data structure as table/map. 54 | 55 | Table/Map is the data structure which ubiquitous after list, 56 | which in turn table/map can be represented as 57 | list of pair values (In Golang we can't have it because there's no tuple 58 | data type, tuple is limited as return values). 59 | 60 | Object can be represented as table/map dynamically just like in 61 | JavaScript/EcmaScript which object is behaving like table and in 62 | Lua with its metatable. By some extent we can represent the JSON 63 | as table too. 64 | 65 | In this library, we provide the mechanism to smoothly map the object 66 | representation back-and-forth without having the boilerplate of 67 | type-checking one by one by hand. Type-checking by hand is certainly 68 | *seems* easier when the domain set is small, but it soon becomes 69 | unbearable as the structure and/or architecure dynamically changed 70 | because of newer insight and information. Hence in [`Who section`](#who) 71 | mentioned this library is for anyone who has to work with large 72 | domain set. 73 | 74 | Except for type `smapping.Mapped` as alias, we don't provide others 75 | type struct currently as each operation doesn't need to keep the 76 | internal state so each operation is transparent and *almost* functional 77 | (*almost functional* because we modify the struct fields values instead of 78 | returning the new struct itself, but this is only trade-off because Golang 79 | doesn't have type-parameter which known as generic). 80 | 81 | Since `v0.1.10`, we added the [`MapEncoder`](smapping.go#L21) and 82 | [`MapDecoder`](smapping.go#L27) interfaces for users to have custom conversion 83 | for custom and self-defined struct. 84 | 85 | ## Install 86 | ``` 87 | go get github.com/mashingan/smapping 88 | ``` 89 | 90 | ## Examples 91 | 92 | ### Basic usage examples 93 | Below example are basic representation how we can work with `smapping`. 94 | Several examples are converged into single runnable example for the ease 95 | of reusing the same structure definition and its various tags. 96 | Refer this example to get a glimpse of how to do things. Afterward, 97 | users can creatively use to accomplish what they're wanting to do 98 | with the provided flexibility. 99 | 100 | ```go 101 | package main 102 | 103 | import ( 104 | "encoding/json" 105 | "fmt" 106 | 107 | "github.com/mashingan/smapping" 108 | ) 109 | 110 | type Source struct { 111 | Label string `json:"label"` 112 | Info string `json:"info"` 113 | Version int `json:"version"` 114 | } 115 | 116 | type Sink struct { 117 | Label string 118 | Info string 119 | } 120 | 121 | type HereticSink struct { 122 | NahLabel string `json:"label"` 123 | HahaInfo string `json:"info"` 124 | Version string `json:"heretic_version"` 125 | } 126 | 127 | type DifferentOneField struct { 128 | Name string `json:"name"` 129 | Label string `json:"label"` 130 | Code string `json:"code"` 131 | Private string `json:"private" api:"internal"` 132 | } 133 | 134 | func main() { 135 | source := Source{ 136 | Label: "source", 137 | Info: "the origin", 138 | Version: 1, 139 | } 140 | fmt.Println("source:", source) 141 | mapped := smapping.MapFields(source) 142 | fmt.Println("mapped:", mapped) 143 | sink := Sink{} 144 | err := smapping.FillStruct(&sink, mapped) 145 | if err != nil { 146 | panic(err) 147 | } 148 | fmt.Println("sink:", sink) 149 | 150 | maptags := smapping.MapTags(source, "json") 151 | fmt.Println("maptags:", maptags) 152 | hereticsink := HereticSink{} 153 | err = smapping.FillStructByTags(&hereticsink, maptags, "json") 154 | if err != nil { 155 | panic(err) 156 | } 157 | fmt.Println("heretic sink:", hereticsink) 158 | 159 | fmt.Println("=============") 160 | recvjson := []byte(`{"name": "bella", "label": "balle", "code": "albel", "private": "allbe"}`) 161 | dof := DifferentOneField{} 162 | _ = json.Unmarshal(recvjson, &dof) 163 | fmt.Println("unmarshaled struct:", dof) 164 | 165 | marshaljson, _ := json.Marshal(dof) 166 | fmt.Println("marshal back:", string(marshaljson)) 167 | 168 | // What we want actually "internal" instead of "private" field 169 | // we use the api tags on to make the json 170 | apijson, _ := json.Marshal(smapping.MapTagsWithDefault(dof, "api", "json")) 171 | fmt.Println("api marshal:", string(apijson)) 172 | 173 | fmt.Println("=============") 174 | // This time is the reverse, we receive "internal" field when 175 | // we need to receive "private" field to match our json tag field 176 | respjson := []byte(`{"name": "bella", "label": "balle", "code": "albel", "internal": "allbe"}`) 177 | respdof := DifferentOneField{} 178 | _ = json.Unmarshal(respjson, &respdof) 179 | fmt.Println("unmarshal resp:", respdof) 180 | 181 | // to get that, we should put convert the json to Mapped first 182 | jsonmapped := smapping.Mapped{} 183 | _ = json.Unmarshal(respjson, &jsonmapped) 184 | // now we fill our struct respdof 185 | _ = smapping.FillStructByTags(&respdof, jsonmapped, "api") 186 | fmt.Println("full resp:", respdof) 187 | returnback, _ := json.Marshal(respdof) 188 | fmt.Println("marshal resp back:", string(returnback)) 189 | // first we unmarshal respdof, we didn't get the "private" field 190 | // but after our mapping, we get "internal" field value and 191 | // simply marshaling back to `returnback` 192 | } 193 | ``` 194 | 195 | ### Nested object example 196 | This example illustrates how we map back-and-forth even with deep 197 | nested object structure. The ability to map nested objects is to 198 | creatively change its representation whether to flatten all tagged 199 | field name even though the inner struct representation is nested. 200 | Regardless of the usage (`whether to flatten the representation`) or 201 | just simply fetching and remapping into different domain name set, 202 | the ability to map the nested object is necessary. 203 | 204 | ```go 205 | 206 | type RefLevel3 struct { 207 | What string `json:"finally"` 208 | } 209 | type Level2 struct { 210 | *RefLevel3 `json:"ref_level3"` 211 | } 212 | type Level1 struct { 213 | Level2 `json:"level2"` 214 | } 215 | type TopLayer struct { 216 | Level1 `json:"level1"` 217 | } 218 | type MadNest struct { 219 | TopLayer `json:"top"` 220 | } 221 | 222 | var madnestStruct MadNest = MadNest{ 223 | TopLayer: TopLayer{ 224 | Level1: Level1{ 225 | Level2: Level2{ 226 | RefLevel3: &RefLevel3{ 227 | What: "matryoska", 228 | }, 229 | }, 230 | }, 231 | }, 232 | } 233 | 234 | func main() { 235 | // since we're targeting the same MadNest, both of functions will yield 236 | // same result hence this unified example/test. 237 | var madnestObj MadNest 238 | var err error 239 | testByTags := true 240 | if testByTags { 241 | madnestMap := smapping.MapTags(madnestStruct, "json") 242 | err = smapping.FillStructByTags(&madnestObj, madnestMap, "json") 243 | } else { 244 | madnestMap := smapping.MapFields(madnestStruct) 245 | err = smapping.FillStruct(&madnestObj) 246 | } 247 | if err != nil { 248 | fmt.Printf("%s", err.Error()) 249 | return 250 | } 251 | // the result should yield as intented value. 252 | if madnestObj.TopLayer.Level1.Level2.RefLevel3.What != "matryoska" { 253 | fmt.Printf("Error: expected \"matroska\" got \"%s\"", madnestObj.Level1.Level2.RefLevel3.What) 254 | } 255 | } 256 | ``` 257 | 258 | ### SQLScan usage example 259 | This example, we're using `sqlite3` as the database, we add a convenience 260 | feature for any struct/type that implements `Scan` method as `smapping.SQLScanner`. 261 | Keep in mind this is quite different with `sql.Scanner` that's also requiring 262 | the type/struct to implement `Scan` method. The difference here, `smapping.SQLScanner` 263 | receiving variable arguments of `interface{}` as values' placeholder while `sql.Scanner` 264 | is only receive a single `interface{}` argument as source. `smapping.SQLScan` is 265 | working for `Scan` literally after we've gotten the `*sql.Row` or `*sql.Rows`. 266 | 267 | ```go 268 | package main 269 | 270 | import ( 271 | "database/sql" 272 | "encoding/json" 273 | "fmt" 274 | 275 | "github.com/mashingan/smapping" 276 | _ "github.com/mattn/go-sqlite3" 277 | ) 278 | 279 | type book struct { 280 | Author author `json:"author"` 281 | } 282 | 283 | type author struct { 284 | Num int `json:"num"` 285 | ID sql.NullString `json:"id"` 286 | Name sql.NullString `json:"name"` 287 | } 288 | 289 | func (a author) MarshalJSON() ([]byte, error) { 290 | mapres := map[string]interface{}{} 291 | if !a.ID.Valid { 292 | //if a.ID == nil || !a.ID.Valid { 293 | mapres["id"] = nil 294 | } else { 295 | mapres["id"] = a.ID.String 296 | } 297 | //if a.Name == nil || !a.Name.Valid { 298 | if !a.Name.Valid { 299 | mapres["name"] = nil 300 | } else { 301 | mapres["name"] = a.Name.String 302 | } 303 | mapres["num"] = a.Num 304 | return json.Marshal(mapres) 305 | } 306 | 307 | func getAuthor(db *sql.DB, id string) author { 308 | res := author{} 309 | err := db.QueryRow("select * from author where id = ?", id). 310 | Scan(&res.Num, &res.ID, &res.Name) 311 | if err != nil { 312 | panic(err) 313 | } 314 | return res 315 | } 316 | 317 | func getAuthor12(db *sql.DB, id string) author { 318 | result := author{} 319 | fields := []string{"num", "id", "name"} 320 | err := smapping.SQLScan( 321 | db.QueryRow("select * from author where id = ?", id), 322 | &result, 323 | "json", 324 | fields...) 325 | if err != nil { 326 | panic(err) 327 | } 328 | return result 329 | } 330 | 331 | func getAuthor13(db *sql.DB, id string) author { 332 | result := author{} 333 | fields := []string{"num", "name"} 334 | err := smapping.SQLScan( 335 | db.QueryRow("select num, name from author where id = ?", id), 336 | &result, 337 | "json", 338 | fields...) 339 | if err != nil { 340 | panic(err) 341 | } 342 | return result 343 | } 344 | 345 | func getAllAuthor(db *sql.DB) []author { 346 | result := []author{} 347 | rows, err := db.Query("select * from author") 348 | if err != nil { 349 | panic(err) 350 | } 351 | for rows.Next() { 352 | res := author{} 353 | if err := smapping.SQLScan(rows, &res, "json"); err != nil { 354 | fmt.Println("error scan:", err) 355 | break 356 | } 357 | result = append(result, res) 358 | } 359 | return result 360 | } 361 | 362 | func main() { 363 | db, err := sql.Open("sqlite3", "./dummy.db") 364 | if err != nil { 365 | panic(err) 366 | } 367 | defer db.Close() 368 | _, err = db.Exec(` 369 | drop table if exists author; 370 | create table author(num integer primary key autoincrement, id text, name text); 371 | insert into author(id, name) values 372 | ('id1', 'name1'), 373 | ('this-nil', null);`) 374 | if err != nil { 375 | panic(err) 376 | } 377 | //auth1 := author{ID: &sql.NullString{String: "id1"}} 378 | auth1 := author{ID: sql.NullString{String: "id1"}} 379 | auth1 = getAuthor(db, auth1.ID.String) 380 | fmt.Println("auth1:", auth1) 381 | jsonbyte, _ := json.Marshal(auth1) 382 | fmt.Println("json auth1:", string(jsonbyte)) 383 | b1 := book{Author: auth1} 384 | fmt.Println(b1) 385 | jbook1, _ := json.Marshal(b1) 386 | fmt.Println("json book1:", string(jbook1)) 387 | auth2 := getAuthor(db, "this-nil") 388 | fmt.Println("auth2:", auth2) 389 | jbyte, _ := json.Marshal(auth2) 390 | fmt.Println("json auth2:", string(jbyte)) 391 | b2 := book{Author: auth2} 392 | fmt.Println("book2:", b2) 393 | jbook2, _ := json.Marshal(b2) 394 | fmt.Println("json book2:", string(jbook2)) 395 | fmt.Println("author12:", getAuthor12(db, auth1.ID.String)) 396 | fmt.Println("author13:", getAuthor13(db, auth1.ID.String)) 397 | fmt.Println("all author1:", getAllAuthor(db)) 398 | } 399 | 400 | ``` 401 | 402 | ### Omit fields example 403 | 404 | Often we need to reuse the same object with exception a field or two. With smapping it's possible to generate 405 | map with custom tag. However having different tag would be too much of manual work. 406 | In this example, we'll see how to exclude using the `delete` keyword. 407 | 408 | ```go 409 | package main 410 | 411 | import ( 412 | "github.com/mashingan/smapping" 413 | ) 414 | 415 | type Struct struct { 416 | Field1 int `json:"field1"` 417 | Field2 string `json:"field2"` 418 | RequestOnly string `json:"input"` 419 | ResponseOnly string `jsoN:"output"` 420 | } 421 | 422 | func main() { 423 | s := Struct{ 424 | Field1: 5, 425 | Field2: "555", 426 | RequestOnly: "vanish later", 427 | ResponseOnly: "still available", 428 | } 429 | 430 | m := smapping.MapTags(s, "json") 431 | _, ok := m["input"] 432 | if !ok { 433 | panic("key 'input' should be still available") 434 | } 435 | delete(m, "input") 436 | _, ok = m["input"] 437 | if ok { 438 | panic("key 'input' should be not available") 439 | } 440 | } 441 | ``` 442 | 443 | ## LICENSE 444 | MIT 445 | -------------------------------------------------------------------------------- /smapping.go: -------------------------------------------------------------------------------- 1 | /* 2 | mapping 3 | Golang mapping structure 4 | */ 5 | 6 | package smapping 7 | 8 | import ( 9 | "database/sql" 10 | "database/sql/driver" 11 | "fmt" 12 | "reflect" 13 | s "strings" 14 | "time" 15 | ) 16 | 17 | // Mapped simply an alias 18 | type Mapped map[string]interface{} 19 | 20 | type MapEncoder interface { 21 | MapEncode() (interface{}, error) 22 | } 23 | 24 | var mapEncoderI = reflect.TypeOf((*MapEncoder)(nil)).Elem() 25 | 26 | type MapDecoder interface { 27 | MapDecode(interface{}) error 28 | } 29 | 30 | var mapDecoderI = reflect.TypeOf((*MapDecoder)(nil)).Elem() 31 | 32 | func extractValue(x interface{}) reflect.Value { 33 | var result reflect.Value 34 | switch v := x.(type) { 35 | case reflect.Value: 36 | result = v 37 | default: 38 | result = reflect.ValueOf(x) 39 | for result.Type().Kind() == reflect.Ptr { 40 | result = result.Elem() 41 | } 42 | if result.Type().Kind() != reflect.Struct { 43 | typ := reflect.StructOf([]reflect.StructField{}) 44 | result = reflect.Zero(typ) 45 | } 46 | } 47 | return result 48 | } 49 | 50 | /* 51 | MapFields maps between struct to mapped interfaces{}. 52 | The argument must be (zero or many pointers to) struct or else it will be ignored. 53 | Now it's implemented as MapTags with empty tag "". 54 | 55 | Only map the exported fields. 56 | */ 57 | func MapFields(x interface{}) Mapped { 58 | return MapTags(x, "") 59 | } 60 | 61 | func tagHead(tag string) string { 62 | return s.Split(tag, ",")[0] 63 | } 64 | 65 | func isValueNil(v reflect.Value) bool { 66 | for _, kind := range []reflect.Kind{ 67 | reflect.Ptr, reflect.Slice, reflect.Map, 68 | reflect.Chan, reflect.Interface, reflect.Func, 69 | } { 70 | if v.Kind() == kind && v.IsNil() { 71 | return true 72 | } 73 | 74 | } 75 | return false 76 | } 77 | 78 | func getValTag(fieldval reflect.Value, tag string) interface{} { 79 | var resval interface{} 80 | if isValueNil(fieldval) { 81 | return nil 82 | } 83 | if fieldval.Type().Name() == "Time" || 84 | reflect.Indirect(fieldval).Type().Name() == "Time" { 85 | resval = fieldval.Interface() 86 | } else if typof := fieldval.Type(); typof.Implements(mapEncoderI) || 87 | reflect.PtrTo(typof).Implements(mapEncoderI) { 88 | valx, ok := fieldval.Interface().(MapEncoder) 89 | if !ok { 90 | return nil 91 | } 92 | val, err := valx.MapEncode() 93 | if err != nil { 94 | val = nil 95 | } 96 | resval = val 97 | } else { 98 | switch fieldval.Kind() { 99 | case reflect.Struct: 100 | resval = MapTags(fieldval, tag) 101 | case reflect.Ptr: 102 | indirect := reflect.Indirect(fieldval) 103 | if indirect.Kind() < reflect.Array || indirect.Kind() == reflect.String { 104 | resval = indirect.Interface() 105 | } else { 106 | resval = MapTags(fieldval.Elem(), tag) 107 | } 108 | case reflect.Slice: 109 | placeholder := make([]interface{}, fieldval.Len()) 110 | for i := 0; i < fieldval.Len(); i++ { 111 | fieldvalidx := fieldval.Index(i) 112 | theval := getValTag(fieldvalidx, tag) 113 | placeholder[i] = theval 114 | } 115 | resval = placeholder 116 | default: 117 | resval = fieldval.Interface() 118 | } 119 | 120 | } 121 | return resval 122 | } 123 | 124 | /* 125 | MapTags maps the tag value of defined field tag name. This enable 126 | various field extraction that will be mapped to mapped interfaces{}. 127 | */ 128 | func MapTags(x interface{}, tag string) Mapped { 129 | result := make(Mapped) 130 | value := extractValue(x) 131 | if !value.IsValid() { 132 | return nil 133 | } 134 | xtype := value.Type() 135 | for i := 0; i < value.NumField(); i++ { 136 | field := xtype.Field(i) 137 | if field.PkgPath != "" { 138 | continue 139 | } 140 | fieldval := value.Field(i) 141 | if tag == "" { 142 | result[field.Name] = getValTag(fieldval, tag) 143 | } else if tagvalue, ok := field.Tag.Lookup(tag); ok { 144 | result[tagHead(tagvalue)] = getValTag(fieldval, tag) 145 | } 146 | } 147 | return result 148 | } 149 | 150 | /* 151 | MapTagsWithDefault maps the tag with optional fallback tags. This to enable 152 | tag differences when there are only few difference with the default “json“ 153 | tag. 154 | */ 155 | func MapTagsWithDefault(x interface{}, tag string, defs ...string) Mapped { 156 | result := make(Mapped) 157 | value := extractValue(x) 158 | if !value.IsValid() { 159 | return nil 160 | } 161 | xtype := value.Type() 162 | for i := 0; i < value.NumField(); i++ { 163 | field := xtype.Field(i) 164 | if field.PkgPath != "" { 165 | continue 166 | } 167 | var ( 168 | tagval string 169 | ok bool 170 | ) 171 | if tagval, ok = field.Tag.Lookup(tag); ok { 172 | result[tagHead(tagval)] = getValTag(value.Field(i), tag) 173 | } else { 174 | for _, deftag := range defs { 175 | if tagval, ok = field.Tag.Lookup(deftag); ok { 176 | result[tagHead(tagval)] = getValTag(value.Field(i), deftag) 177 | break // break from looping the defs 178 | } 179 | } 180 | } 181 | } 182 | return result 183 | } 184 | 185 | // MapTagsFlatten is to flatten mapped object with specific tag. The limitation 186 | // of this flattening that it can't have duplicate tag name and it will give 187 | // incorrect result because the older value will be written with newer map field value. 188 | func MapTagsFlatten(x interface{}, tag string) Mapped { 189 | result := make(Mapped) 190 | value := extractValue(x) 191 | if !value.IsValid() { 192 | return nil 193 | } 194 | xtype := value.Type() 195 | for i := 0; i < value.NumField(); i++ { 196 | field := xtype.Field(i) 197 | if field.PkgPath != "" { 198 | continue 199 | } 200 | fieldval := value.Field(i) 201 | isStruct := reflect.Indirect(fieldval).Type().Kind() == reflect.Struct 202 | if tagvalue, ok := field.Tag.Lookup(tag); ok && !isStruct { 203 | key := tagHead(tagvalue) 204 | result[key] = fieldval.Interface() 205 | continue 206 | } 207 | fieldval = reflect.Indirect(fieldval) 208 | if !isStruct { 209 | continue 210 | } 211 | nests := MapTagsFlatten(fieldval, tag) 212 | for k, v := range nests { 213 | result[k] = v 214 | } 215 | } 216 | return result 217 | } 218 | 219 | func isTime(typ reflect.Type) bool { 220 | return typ.Name() == "Time" || typ.String() == "*time.Time" 221 | } 222 | func handleTime(layout, format string, typ reflect.Type) (reflect.Value, error) { 223 | t, err := time.Parse(layout, format) 224 | var resval reflect.Value 225 | if err != nil { 226 | return resval, fmt.Errorf("time conversion: %s", err.Error()) 227 | } 228 | if typ.Kind() == reflect.Ptr { 229 | resval = reflect.New(typ).Elem() 230 | resval.Set(reflect.ValueOf(&t)) 231 | } else { 232 | resval = reflect.ValueOf(&t).Elem() 233 | 234 | } 235 | return resval, err 236 | } 237 | 238 | func isSlicedObj(val, res reflect.Value) bool { 239 | return val.Type().Kind() == reflect.Slice && 240 | res.Kind() == reflect.Slice 241 | } 242 | 243 | func fillMapIter(vfield, res reflect.Value, val *reflect.Value, tagname string) error { 244 | iter := val.MapRange() 245 | m := Mapped{} 246 | for iter.Next() { 247 | m[iter.Key().String()] = iter.Value().Interface() 248 | } 249 | if vfield.Kind() == reflect.Ptr { 250 | vval := vfield.Type().Elem() 251 | ptrres := reflect.New(vval).Elem() 252 | mapf := make(map[string]reflect.StructField) 253 | if tagname != "" { 254 | populateMapFieldsTag(mapf, tagname, ptrres) 255 | } 256 | for k, v := range m { 257 | _, err := setFieldFromTag(ptrres, tagname, k, v, mapf) 258 | if err != nil { 259 | return fmt.Errorf("ptr nested error: %s", err.Error()) 260 | } 261 | } 262 | *val = ptrres.Addr() 263 | } else { 264 | if err := FillStructByTags(res, m, tagname); err != nil { 265 | return fmt.Errorf("nested error: %s", err.Error()) 266 | } 267 | *val = res 268 | } 269 | return nil 270 | } 271 | 272 | func fillTime(vfield reflect.Value, val *reflect.Value) error { 273 | if (*val).Type().Name() == "string" { 274 | newval, err := handleTime(time.RFC3339, val.String(), vfield.Type()) 275 | if err != nil { 276 | return fmt.Errorf("smapping Time conversion: %s", err.Error()) 277 | } 278 | *val = newval 279 | } else if val.Type().Name() == "Time" { 280 | *val = reflect.Indirect(*val) 281 | } 282 | return nil 283 | } 284 | 285 | func scalarType(val reflect.Value) bool { 286 | if val.Kind() != reflect.Interface { 287 | return false 288 | } 289 | switch val.Interface().(type) { 290 | case int, int8, int16, int32, int64, 291 | uint, uint8, uint16, uint32, uint64, 292 | float32, float64, string, []byte: 293 | return true 294 | 295 | } 296 | return false 297 | } 298 | 299 | func ptrExtract(vval, rval reflect.Value) (reflect.Value, bool) { 300 | acttype := rval.Type().Elem() 301 | newrval := reflect.New(acttype).Elem() 302 | gotval := false 303 | if newrval.Kind() < reflect.Array { 304 | gotval = true 305 | ival := vval.Interface() 306 | if newrval.Kind() > reflect.Bool && newrval.Kind() < reflect.Uint { 307 | nval := reflect.ValueOf(ival).Int() 308 | newrval.SetInt(nval) 309 | } else if newrval.Kind() > reflect.Uintptr && 310 | newrval.Kind() < reflect.Complex64 { 311 | fval := reflect.ValueOf(ival).Float() 312 | newrval.SetFloat(fval) 313 | } else { 314 | newrval.Set(reflect.ValueOf(ival)) 315 | } 316 | } 317 | return newrval, gotval 318 | } 319 | 320 | func fillSlice(res reflect.Value, val *reflect.Value, tagname string) error { 321 | for i := 0; i < val.Len(); i++ { 322 | vval := val.Index(i) 323 | rval := reflect.New(res.Type().Elem()).Elem() 324 | if vval.Kind() < reflect.Array { 325 | rval.Set(vval) 326 | res = reflect.Append(res, rval) 327 | continue 328 | } else if scalarType(vval) { 329 | if rval.Kind() == reflect.Ptr { 330 | if newrval, ok := ptrExtract(vval, rval); ok { 331 | res = reflect.Append(res, newrval.Addr()) 332 | continue 333 | } 334 | } 335 | rval.Set(reflect.ValueOf(vval.Interface())) 336 | res = reflect.Append(res, rval) 337 | continue 338 | } else if vval.IsNil() { 339 | res = reflect.Append(res, reflect.Zero(rval.Type())) 340 | continue 341 | } 342 | newrval := rval 343 | if rval.Kind() == reflect.Ptr { 344 | var ok bool 345 | if newrval, ok = ptrExtract(vval, rval); ok { 346 | res = reflect.Append(res, newrval.Addr()) 347 | continue 348 | } 349 | } 350 | m, ok := vval.Interface().(Mapped) 351 | if !ok && newrval.Kind() >= reflect.Array { 352 | m = MapTags(vval.Interface(), tagname) 353 | } 354 | err := FillStructByTags(newrval, m, tagname) 355 | if err != nil { 356 | return fmt.Errorf("cannot set an element slice") 357 | } 358 | if rval.Kind() == reflect.Ptr { 359 | res = reflect.Append(res, newrval.Addr()) 360 | } else { 361 | res = reflect.Append(res, newrval) 362 | } 363 | } 364 | *val = res 365 | return nil 366 | } 367 | 368 | func populateMapFieldsTag(mapfield map[string]reflect.StructField, tagname string, obj interface{}) { 369 | sval := extractValue(obj) 370 | stype := sval.Type() 371 | for i := 0; i < sval.NumField(); i++ { 372 | field := stype.Field(i) 373 | if field.PkgPath != "" { 374 | continue 375 | } 376 | if tag, ok := field.Tag.Lookup(tagname); ok { 377 | mapfield[tagHead(tag)] = field 378 | } 379 | } 380 | } 381 | 382 | func setFieldFromTag(obj interface{}, tagname, tagvalue string, 383 | value interface{}, mapfield map[string]reflect.StructField) (bool, error) { 384 | sval := extractValue(obj) 385 | stype := sval.Type() 386 | var ( 387 | vfield reflect.Value 388 | field reflect.StructField 389 | ) 390 | if tagname == "" { 391 | vfield = sval.FieldByName(tagvalue) 392 | var fieldok bool 393 | field, fieldok = stype.FieldByName(tagvalue) 394 | if !fieldok { 395 | return false, nil 396 | } 397 | } else { 398 | var fieldok bool 399 | field, fieldok = mapfield[tagvalue] 400 | if !fieldok { 401 | return false, nil 402 | } 403 | vfield = sval.FieldByName(field.Name) 404 | } 405 | val := reflect.ValueOf(value) 406 | if !val.IsValid() { 407 | return false, nil 408 | } 409 | res := reflect.New(vfield.Type()).Elem() 410 | if typof := vfield.Type(); typof.Implements(mapDecoderI) || 411 | reflect.PtrTo(typof).Implements(mapDecoderI) { 412 | isPtr := typof.Kind() == reflect.Ptr 413 | var mapval reflect.Value 414 | if isPtr { 415 | mapval = reflect.New(typof.Elem()) 416 | } else { 417 | mapval = reflect.New(typof) 418 | } 419 | mapdecoder, ok := mapval.Interface().(MapDecoder) 420 | if !ok { 421 | return false, nil 422 | } 423 | if err := mapdecoder.MapDecode(value); err != nil { 424 | return false, err 425 | } 426 | if isPtr { 427 | val = reflect.ValueOf(mapdecoder) 428 | } else { 429 | val = reflect.Indirect(reflect.ValueOf(mapdecoder)) 430 | } 431 | } else if isTime(vfield.Type()) { 432 | if err := fillTime(vfield, &val); err != nil { 433 | return false, err 434 | } 435 | } else if res.IsValid() && val.Type().Name() == "Mapped" { 436 | if err := fillMapIter(vfield, res, &val, tagname); err != nil { 437 | return false, err 438 | } 439 | } else if isSlicedObj(val, res) { 440 | if err := fillSlice(res, &val, tagname); err != nil { 441 | return false, err 442 | } 443 | } else if vfield.Kind() == reflect.Ptr { 444 | vfv := vfield.Type().Elem() 445 | if vfv != val.Type() { 446 | return false, fmt.Errorf( 447 | "provided value (%#v) pointer type %T not match field tag '%s' of tagname '%s' of type '%v' from object", 448 | value, value, tagname, tagvalue, field.Type) 449 | } 450 | nval := reflect.New(vfv).Elem() 451 | nval.Set(val) 452 | val = nval.Addr() 453 | } else if field.Type != val.Type() { 454 | return false, fmt.Errorf("provided value (%#v) type %T not match field tag '%s' of tagname '%s' of type '%v' from object", 455 | value, value, tagname, tagvalue, field.Type) 456 | } 457 | vfield.Set(val) 458 | return true, nil 459 | } 460 | 461 | /* 462 | FillStruct acts just like “json.Unmarshal“ but works with “Mapped“ 463 | instead of bytes of char that made from “json“. 464 | */ 465 | func FillStruct(obj interface{}, mapped Mapped) error { 466 | errmsg := "" 467 | mapf := make(map[string]reflect.StructField) 468 | for k, v := range mapped { 469 | if v == nil { 470 | continue 471 | } 472 | _, err := setFieldFromTag(obj, "", k, v, mapf) 473 | if err != nil { 474 | if errmsg != "" { 475 | errmsg += "," 476 | } 477 | errmsg += err.Error() 478 | } 479 | } 480 | if errmsg != "" { 481 | return fmt.Errorf(errmsg) 482 | } 483 | return nil 484 | } 485 | 486 | /* 487 | FillStructByTags fills the field that has tagname and tagvalue 488 | instead of Mapped key name. 489 | */ 490 | func FillStructByTags(obj interface{}, mapped Mapped, tagname string) error { 491 | errmsg := "" 492 | mapf := make(map[string]reflect.StructField) 493 | populateMapFieldsTag(mapf, tagname, obj) 494 | for k, v := range mapped { 495 | if v == nil { 496 | continue 497 | } 498 | _, err := setFieldFromTag(obj, tagname, k, v, mapf) 499 | if err != nil { 500 | if errmsg != "" { 501 | errmsg += "," 502 | } 503 | errmsg += err.Error() 504 | } 505 | } 506 | if errmsg != "" { 507 | return fmt.Errorf(errmsg) 508 | } 509 | return nil 510 | } 511 | 512 | // FillStructDeflate fills the nested object from flat map. 513 | // This works by filling outer struct first and then checking its subsequent object fields. 514 | func FillStructDeflate(obj interface{}, mapped Mapped, tagname string) error { 515 | errmsg := "" 516 | err := FillStructByTags(obj, mapped, tagname) 517 | if err != nil { 518 | errmsg = err.Error() 519 | } 520 | sval := extractValue(obj) 521 | for i := 0; i < sval.NumField(); i++ { 522 | field := sval.Field(i) 523 | kind := field.Kind() 524 | if kind == reflect.Struct { 525 | res := reflect.New(field.Type()).Elem() 526 | if err = FillStructDeflate(res, mapped, tagname); err != nil { 527 | if errmsg != "" { 528 | errmsg += ", " 529 | } 530 | errmsg += err.Error() 531 | continue 532 | } 533 | field.Set(res) 534 | } else if kind == reflect.Ptr { 535 | indirectField := field.Type().Elem() 536 | if indirectField.Kind() != reflect.Struct { 537 | continue 538 | } 539 | res := reflect.New(indirectField).Elem() 540 | if err = FillStructDeflate(res, mapped, tagname); err != nil { 541 | if errmsg != "" { 542 | errmsg += ", " 543 | } 544 | errmsg += err.Error() 545 | continue 546 | } 547 | field.Set(res.Addr()) 548 | } 549 | } 550 | if errmsg != "" { 551 | return fmt.Errorf(errmsg) 552 | } 553 | return nil 554 | } 555 | 556 | func assignScanner(mapvals []interface{}, tagFields map[string]reflect.StructField, 557 | tag string, index int, key string, obj, value interface{}) { 558 | switch value.(type) { 559 | case int: 560 | mapvals[index] = new(int) 561 | case int8: 562 | mapvals[index] = new(int8) 563 | case int16: 564 | mapvals[index] = new(int16) 565 | case int32: 566 | mapvals[index] = new(int32) 567 | case int64: 568 | mapvals[index] = new(int64) 569 | case uint: 570 | mapvals[index] = new(uint) 571 | case uint8: 572 | mapvals[index] = new(uint8) 573 | case uint16: 574 | mapvals[index] = new(uint16) 575 | case uint32: 576 | mapvals[index] = new(uint32) 577 | case uint64: 578 | mapvals[index] = new(uint64) 579 | case string: 580 | mapvals[index] = new(string) 581 | case float32: 582 | mapvals[index] = new(float32) 583 | case float64: 584 | mapvals[index] = new(float64) 585 | case bool: 586 | mapvals[index] = new(bool) 587 | case []byte: 588 | mapvals[index] = new([]byte) 589 | case time.Time: 590 | mapvals[index] = new(time.Time) 591 | case sql.Scanner, driver.Valuer, Mapped: 592 | mapvals[index] = new(interface{}) 593 | typof := reflect.TypeOf(obj).Elem() 594 | if tag == "" { 595 | strufield, ok := typof.FieldByName(key) 596 | if !ok { 597 | return 598 | } 599 | typof = strufield.Type 600 | } else if strufield, ok := tagFields[key]; ok { 601 | typof = strufield.Type 602 | } else { 603 | for i := 0; i < typof.NumField(); i++ { 604 | strufield := typof.Field(i) 605 | if tagval, ok := strufield.Tag.Lookup(tag); ok { 606 | tagFields[key] = strufield 607 | if tagHead(tagval) == key { 608 | typof = strufield.Type 609 | break 610 | } 611 | } 612 | } 613 | } 614 | 615 | scannerI := reflect.TypeOf((*sql.Scanner)(nil)).Elem() 616 | if typof.Implements(scannerI) || reflect.PtrTo(typof).Implements(scannerI) { 617 | valx := reflect.New(typof).Elem() 618 | mapvals[index] = valx.Addr().Interface() 619 | } 620 | default: 621 | } 622 | 623 | } 624 | 625 | func assignValuer(mapres Mapped, tagFields map[string]reflect.StructField, 626 | tag, key string, obj, value interface{}) { 627 | switch v := value.(type) { 628 | case *int8: 629 | mapres[key] = *v 630 | case *int16: 631 | mapres[key] = *v 632 | case *int32: 633 | mapres[key] = *v 634 | case *int64: 635 | mapres[key] = *v 636 | case *int: 637 | mapres[key] = *v 638 | case *uint8: 639 | mapres[key] = *v 640 | case *uint16: 641 | mapres[key] = *v 642 | case *uint32: 643 | mapres[key] = *v 644 | case *uint64: 645 | mapres[key] = *v 646 | case *uint: 647 | mapres[key] = *v 648 | case *string: 649 | mapres[key] = *v 650 | case *bool: 651 | mapres[key] = *v 652 | case *float32: 653 | mapres[key] = *v 654 | case *float64: 655 | mapres[key] = *v 656 | case *[]byte: 657 | mapres[key] = *v 658 | case *time.Time: 659 | mapres[key] = *v 660 | case *driver.Valuer: 661 | default: 662 | typof := reflect.TypeOf(obj).Elem() 663 | if tag == "" { 664 | strufield, ok := typof.FieldByName(key) 665 | if !ok { 666 | return 667 | } 668 | typof = strufield.Type 669 | } else if strufield, ok := tagFields[key]; ok { 670 | typof = strufield.Type 671 | } else { 672 | for i := 0; i < typof.NumField(); i++ { 673 | strufield := typof.Field(i) 674 | if tagval, ok := strufield.Tag.Lookup(tag); ok { 675 | if tagHead(tagval) == key { 676 | typof = strufield.Type 677 | break 678 | } 679 | } 680 | } 681 | } 682 | valuerI := reflect.TypeOf((*driver.Valuer)(nil)).Elem() 683 | if typof.Implements(valuerI) || reflect.PtrTo(typof).Implements(valuerI) { 684 | valx := reflect.New(typof).Elem() 685 | valv := reflect.Indirect(reflect.ValueOf(value)) 686 | valx.Set(valv) 687 | mapres[key] = valx.Interface() 688 | } 689 | // ignore if it's not recognized 690 | } 691 | } 692 | 693 | // SQLScanner is the interface that dictate 694 | // any type that implement Scan method to 695 | // be compatible with sql.Row Scan method. 696 | type SQLScanner interface { 697 | Scan(dest ...interface{}) error 698 | } 699 | 700 | /* 701 | SQLScan is the function that will map scanning object based on provided 702 | field name or field tagged string. The tags can receive the empty string 703 | "" and then it will map the field name by default. 704 | */ 705 | func SQLScan(row SQLScanner, obj interface{}, tag string, x ...string) error { 706 | mapres := MapTags(obj, tag) 707 | fieldsName := x 708 | length := len(x) 709 | if length == 0 || (length == 1 && x[0] == "*") { 710 | typof := reflect.TypeOf(obj).Elem() 711 | newfields := make([]string, typof.NumField()) 712 | length = typof.NumField() 713 | for i := 0; i < length; i++ { 714 | field := typof.Field(i) 715 | if tag == "" { 716 | newfields[i] = field.Name 717 | } else { 718 | if tagval, ok := field.Tag.Lookup(tag); ok { 719 | newfields[i] = tagHead(tagval) 720 | } 721 | } 722 | } 723 | fieldsName = newfields 724 | } 725 | mapvals := make([]interface{}, length) 726 | tagFields := make(map[string]reflect.StructField) 727 | for i, k := range fieldsName { 728 | assignScanner(mapvals, tagFields, tag, i, k, obj, mapres[k]) 729 | } 730 | if err := row.Scan(mapvals...); err != nil { 731 | return err 732 | } 733 | for i, k := range fieldsName { 734 | assignValuer(mapres, tagFields, tag, k, obj, mapvals[i]) 735 | } 736 | var err error 737 | if tag == "" { 738 | err = FillStruct(obj, mapres) 739 | } else { 740 | err = FillStructByTags(obj, mapres, tag) 741 | } 742 | return err 743 | } 744 | -------------------------------------------------------------------------------- /smapping_test.go: -------------------------------------------------------------------------------- 1 | package smapping 2 | 3 | import ( 4 | "database/sql" 5 | "encoding/json" 6 | "fmt" 7 | "strings" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | type source struct { 13 | Label string `json:"label"` 14 | Info string `json:"info"` 15 | Version int `json:"version"` 16 | Toki time.Time `json:"tomare"` 17 | Addr *string `json:"address"` 18 | } 19 | 20 | type sink struct { 21 | Label string 22 | Info string 23 | } 24 | 25 | type differentSink struct { 26 | DiffLabel string `json:"label"` 27 | NiceInfo string `json:"info"` 28 | Version string `json:"unversion"` 29 | Toki time.Time `json:"doki"` 30 | Addr *string `json:"address"` 31 | } 32 | 33 | type differentSourceSink struct { 34 | Source source `json:"source"` 35 | DiffSink differentSink `json:"differentSink"` 36 | } 37 | 38 | var toki = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC) 39 | var hello = "hello異世界" 40 | 41 | var sourceobj source = source{ 42 | Label: "source", 43 | Info: "the origin", 44 | Version: 1, 45 | Toki: toki, 46 | Addr: &hello, 47 | } 48 | 49 | func printIfNotExists(mapped Mapped, keys ...string) { 50 | for _, key := range keys { 51 | if _, ok := mapped[key]; !ok { 52 | fmt.Println(key, ": not exists") 53 | } 54 | } 55 | } 56 | 57 | func ExampleMapFields() { 58 | mapped := MapFields(sourceobj) 59 | printIfNotExists(mapped, "Label", "Info", "Version") 60 | // Output: 61 | } 62 | 63 | func ExampleMapTags_basic() { 64 | ptrSourceObj := &sourceobj 65 | maptags := MapTags(&ptrSourceObj, "json") 66 | printIfNotExists(maptags, "label", "info", "version") 67 | // Output: 68 | } 69 | 70 | func ExampleMapTags_nested() { 71 | nestedSource := differentSourceSink{ 72 | Source: sourceobj, 73 | DiffSink: differentSink{ 74 | DiffLabel: "nested diff", 75 | NiceInfo: "nested info", 76 | Version: "next version", 77 | Toki: toki, 78 | Addr: &hello, 79 | }, 80 | } 81 | 82 | // this part illustrates that MapTags or any other Map function 83 | // accept arbitrary pointers to struct. 84 | ptrNestedSource := &nestedSource 85 | ptr2NestedSource := &ptrNestedSource 86 | ptr3NestedSource := &ptr2NestedSource 87 | nestedMap := MapTags(&ptr3NestedSource, "json") 88 | for k, v := range nestedMap { 89 | fmt.Println("top key:", k) 90 | for kk, vv := range v.(Mapped) { 91 | if vtime, ok := vv.(time.Time); ok { 92 | fmt.Println(" nested:", kk, vtime.Format(time.RFC3339)) 93 | } else { 94 | 95 | fmt.Println(" nested:", kk, vv) 96 | } 97 | } 98 | fmt.Println() 99 | } 100 | // Unordered Output: 101 | // top key: source 102 | // nested: label source 103 | // nested: info the origin 104 | // nested: version 1 105 | // nested: tomare 2000-01-01T00:00:00Z 106 | // nested: address hello異世界 107 | // 108 | // top key: differentSink 109 | // nested: label nested diff 110 | // nested: info nested info 111 | // nested: unversion next version 112 | // nested: doki 2000-01-01T00:00:00Z 113 | // nested: address hello異世界 114 | } 115 | 116 | type generalFields struct { 117 | Name string `json:"name" api:"general_name"` 118 | Rank string `json:"rank" api:"general_rank"` 119 | Code int `json:"code" api:"general_code"` 120 | nickname string // won't be mapped because not exported 121 | } 122 | 123 | func ExampleMapTags_twoTags() { 124 | 125 | general := generalFields{ 126 | Name: "duran", 127 | Rank: "private", 128 | Code: 1337, 129 | nickname: "drone", 130 | } 131 | mapjson := MapTags(&general, "json") 132 | printIfNotExists(mapjson, "name", "rank", "code") 133 | 134 | mapapi := MapTags(&general, "api") 135 | printIfNotExists(mapapi, "general_name", "general_rank", "general_code") 136 | 137 | // Output: 138 | } 139 | 140 | func ExampleMapTagsWithDefault() { 141 | type higherCommon struct { 142 | General generalFields `json:"general"` 143 | Communality string `json:"common"` 144 | Available bool `json:"available" api:"is_available"` 145 | } 146 | rawjson := []byte(`{ 147 | "general": { 148 | name: "duran", 149 | rank: "private", 150 | code: 1337, 151 | }, 152 | "common": "rare", 153 | "available": true 154 | }`) 155 | hc := higherCommon{} 156 | _ = json.Unmarshal(rawjson, &hc) 157 | maptags := MapTagsWithDefault(&hc, "api", "json") 158 | printIfNotExists(maptags, "available") 159 | // Output: available : not exists 160 | } 161 | 162 | func ExampleFillStruct() { 163 | mapped := MapFields(&sourceobj) 164 | sinked := sink{} 165 | err := FillStruct(&sinked, mapped) 166 | if err != nil { 167 | panic(err) 168 | } 169 | fmt.Println(sinked) 170 | // Output: {source the origin} 171 | } 172 | 173 | func ExampleFillStructByTags() { 174 | maptags := MapTags(&sourceobj, "json") 175 | for k, v := range maptags { 176 | if vt, ok := v.(time.Time); ok { 177 | fmt.Printf("maptags[%s]: %s\n", k, vt.Format(time.RFC3339)) 178 | } else { 179 | fmt.Printf("maptags[%s]: %v\n", k, v) 180 | 181 | } 182 | } 183 | diffsink := differentSink{} 184 | err := FillStructByTags(&diffsink, maptags, "json") 185 | if err != nil { 186 | panic(err) 187 | } 188 | fmt.Println(diffsink.DiffLabel) 189 | fmt.Println(diffsink.NiceInfo) 190 | fmt.Println(diffsink.Version) 191 | fmt.Println(diffsink.Toki) 192 | fmt.Println(*diffsink.Addr) 193 | 194 | // Unordered Output: 195 | // maptags[label]: source 196 | // maptags[info]: the origin 197 | // maptags[version]: 1 198 | // maptags[tomare]: 2000-01-01T00:00:00Z 199 | // maptags[address]: hello異世界 200 | // source 201 | // the origin 202 | // 203 | // 0001-01-01 00:00:00 +0000 UTC 204 | // hello異世界 205 | } 206 | 207 | type RefLevel3 struct { 208 | What string `json:"finally"` 209 | } 210 | type Level2 struct { 211 | *RefLevel3 `json:"ref_level3"` 212 | } 213 | type Level1 struct { 214 | Level2 `json:"level2"` 215 | } 216 | type TopLayer struct { 217 | Level1 `json:"level1"` 218 | } 219 | type MadNest struct { 220 | TopLayer `json:"top"` 221 | } 222 | 223 | var madnestStruct MadNest = MadNest{ 224 | TopLayer: TopLayer{ 225 | Level1: Level1{ 226 | Level2: Level2{ 227 | RefLevel3: &RefLevel3{ 228 | What: "matryoska", 229 | }, 230 | }, 231 | }, 232 | }, 233 | } 234 | 235 | func TestMapTags_nested(t *testing.T) { 236 | madnestMap := MapTags(&madnestStruct, "json") 237 | if len(madnestMap) != 1 { 238 | t.Errorf("Got empty Mapped, expected 1") 239 | return 240 | } 241 | top, ok := madnestMap["top"] 242 | if !ok { 243 | t.Errorf("Failed to get top field") 244 | return 245 | } 246 | lv1, ok := top.(Mapped)["level1"] 247 | if !ok { 248 | t.Errorf("Failed to get level 1 field") 249 | return 250 | } 251 | lv2, ok := lv1.(Mapped)["level2"] 252 | if !ok { 253 | t.Errorf("Failed to get level 2 field") 254 | return 255 | } 256 | reflv3, ok := lv2.(Mapped)["ref_level3"] 257 | if !ok { 258 | t.Errorf("Failed to get ref level 3 field") 259 | return 260 | } 261 | what, ok := reflv3.(Mapped)["finally"] 262 | if !ok { 263 | t.Errorf("Failed to get the inner ref level 3") 264 | return 265 | } 266 | switch v := what.(type) { 267 | case string: 268 | theval := what.(string) 269 | if theval != "matryoska" { 270 | t.Errorf("Expected matryoska, got %s", theval) 271 | } 272 | default: 273 | t.Errorf("Expected string, got %T", v) 274 | } 275 | } 276 | 277 | func TestMappingNotStruct(t *testing.T) { 278 | m := MapFields("") 279 | if lenm := len(m); lenm != 0 { 280 | t.Errorf("Expected 0 got map with length %d", lenm) 281 | } 282 | 283 | m["dummy"] = 11 284 | m["will"] = "vanish" 285 | m = MapTags(5, "dummy-tag") 286 | if lenm := len(m); lenm != 0 { 287 | t.Errorf("Expected 0 got map with length %d", lenm) 288 | } 289 | } 290 | 291 | func FillStructNestedTest(bytag bool, t *testing.T) { 292 | var madnestObj MadNest 293 | var err error 294 | if bytag { 295 | madnestMap := MapTags(&madnestStruct, "json") 296 | err = FillStructByTags(&madnestObj, madnestMap, "json") 297 | } else { 298 | madnestMap := MapFields(&madnestStruct) 299 | err = FillStruct(&madnestObj, madnestMap) 300 | } 301 | if err != nil { 302 | t.Errorf("%s", err.Error()) 303 | return 304 | } 305 | t.Logf("madnestObj %#v\n", madnestObj) 306 | if madnestObj.TopLayer.Level1.Level2.RefLevel3.What != "matryoska" { 307 | t.Errorf("Error: expected \"matroska\" got \"%s\"", madnestObj.Level1.Level2.RefLevel3.What) 308 | } 309 | } 310 | 311 | func TestFillStructByTags_nested(t *testing.T) { 312 | FillStructNestedTest(true, t) 313 | } 314 | 315 | func TestFillStruct_nested(t *testing.T) { 316 | FillStructNestedTest(false, t) 317 | } 318 | 319 | func fillStructTime(bytag bool, t *testing.T) { 320 | type timeMap struct { 321 | Label string `json:"label"` 322 | Time time.Time `json:"definedTime"` 323 | PtrTime *time.Time `json:"ptrTime"` 324 | } 325 | now := time.Now() 326 | obj := timeMap{Label: "test", Time: now, PtrTime: &now} 327 | objTarget := timeMap{} 328 | if bytag { 329 | // jsbyte, err := json.Marshal(obj) 330 | // if err != nil { 331 | // t.Error(err) 332 | // return 333 | // } 334 | // mapp := Mapped{} 335 | // _ = json.Unmarshal(jsbyte, &mapp) 336 | mapfield := MapTags(&obj, "json") 337 | err := FillStructByTags(&objTarget, mapfield, "json") 338 | if err != nil { 339 | t.Error(err) 340 | return 341 | } 342 | } else { 343 | mapfield := MapFields(&obj) 344 | t.Logf("mapfield: %#v\n", mapfield) 345 | // jsbyte, err := json.Marshal(mapfield) 346 | // if err != nil { 347 | // t.Error(err) 348 | // return 349 | // } 350 | // mapp := Mapped{} 351 | // err = json.Unmarshal(jsbyte, &mapp) 352 | // if err != nil { 353 | // t.Error(err) 354 | // return 355 | // } 356 | err := FillStruct(&objTarget, mapfield) 357 | if err != nil { 358 | t.Error(err) 359 | return 360 | } 361 | } 362 | if !objTarget.Time.Equal(obj.Time) { 363 | t.Errorf("Error value conversion: %s not equal with %s", 364 | objTarget.Time.Format(time.RFC3339), 365 | obj.Time.Format(time.RFC3339), 366 | ) 367 | return 368 | } 369 | if !objTarget.PtrTime.Equal(*(obj.PtrTime)) { 370 | t.Errorf("Error value pointer time conversion: %s not equal with %s", 371 | objTarget.PtrTime.Format(time.RFC3339), 372 | obj.PtrTime.Format(time.RFC3339), 373 | ) 374 | return 375 | 376 | } 377 | } 378 | 379 | func TestFillStructByTags_time_conversion(t *testing.T) { 380 | fillStructTime(true, t) 381 | } 382 | 383 | func TestFillStruct_time_conversion(t *testing.T) { 384 | fillStructTime(false, t) 385 | } 386 | 387 | func ExampleMapTagsFlatten() { 388 | type ( 389 | Last struct { 390 | Final string `json:"final"` 391 | Destination string 392 | } 393 | Lv3 struct { 394 | Lv3Str string `json:"lv3str"` 395 | *Last `json:"last"` 396 | Lv3Dummy string 397 | } 398 | Lv2 struct { 399 | Lv2Str string `json:"lv2str"` 400 | Lv3 `json:"lv3"` 401 | Lv2Dummy string 402 | } 403 | Lv1 struct { 404 | Lv2 405 | Lv1Str string `json:"lv1str"` 406 | Lv1Dummy string 407 | } 408 | ) 409 | 410 | obj := Lv1{ 411 | Lv1Str: "level 1 string", 412 | Lv1Dummy: "baka", 413 | Lv2: Lv2{ 414 | Lv2Dummy: "bakabaka", 415 | Lv2Str: "level 2 string", 416 | Lv3: Lv3{ 417 | Lv3Dummy: "bakabakka", 418 | Lv3Str: "level 3 string", 419 | Last: &Last{ 420 | Final: "destination", 421 | Destination: "overloop", 422 | }, 423 | }, 424 | }, 425 | } 426 | 427 | for k, v := range MapTagsFlatten(&obj, "json") { 428 | fmt.Printf("key: %s, value: %v\n", k, v) 429 | } 430 | // Unordered Output: 431 | // key: final, value: destination 432 | // key: lv1str, value: level 1 string 433 | // key: lv2str, value: level 2 string 434 | // key: lv3str, value: level 3 string 435 | } 436 | 437 | func ExampleFillStructDeflate_fromJson() { 438 | type ( 439 | nest2 struct { 440 | N2FieldFloat float64 `json:"nested2_flt"` 441 | N2FieldStr string `json:"nested2_str"` 442 | } 443 | 444 | nest1 struct { 445 | N1FieldFloat float64 `json:"nested1_flt"` 446 | N1FieldStr string `json:"nested1_str"` 447 | Nest2 *nest2 `json:"nested2"` 448 | } 449 | 450 | outerobj struct { 451 | FieldFloat float64 `json:"field_flt"` 452 | FieldStr string `json:"field_str"` 453 | Nest1 nest1 `json:"nested1"` 454 | } 455 | ) 456 | 457 | rawb := ` 458 | { 459 | "field_str": "555", 460 | "field_flt": 5, 461 | "nested1_flt": 515, 462 | "nested1_str": "515", 463 | "nested2_flt": 525, 464 | "nested2_str": "525" 465 | }` 466 | var m map[string]interface{} 467 | fmt.Println(json.Unmarshal([]byte(rawb), &m)) 468 | var tgt outerobj 469 | fmt.Println("error result fill struct deflate:", FillStructDeflate(&tgt, m, "json")) 470 | fmt.Printf("%#v\n", tgt.FieldFloat) 471 | fmt.Printf("%#v\n", tgt.FieldStr) 472 | fmt.Printf("%#v\n", tgt.Nest1.N1FieldFloat) 473 | fmt.Printf("%#v\n", tgt.Nest1.N1FieldStr) 474 | fmt.Printf("%#v\n", tgt.Nest1.Nest2.N2FieldFloat) 475 | fmt.Printf("%#v\n", tgt.Nest1.Nest2.N2FieldStr) 476 | 477 | // Output: 478 | // 479 | // error result fill struct deflate: 480 | // 5 481 | // "555" 482 | // 515 483 | // "515" 484 | // 525 485 | // "525" 486 | } 487 | 488 | type dummyValues struct { 489 | Int int 490 | Int8 int8 491 | Int16 int16 492 | Int32 int32 493 | Int64 int64 494 | Uint uint 495 | Uint8 uint8 496 | Uint16 uint16 497 | Uint32 uint32 498 | Uint64 uint64 499 | Float32 float32 500 | Float64 float64 501 | Bool bool 502 | String string 503 | Bytes []byte 504 | sql.NullBool 505 | sql.NullFloat64 506 | sql.NullInt32 507 | sql.NullInt64 508 | sql.NullString 509 | sql.NullTime 510 | } 511 | 512 | type dummyRow struct { 513 | Values dummyValues 514 | } 515 | 516 | func (dr *dummyRow) Scan(dest ...interface{}) error { 517 | for i, x := range dest { 518 | switch x.(type) { 519 | case *int: 520 | dest[i] = &dr.Values.Int 521 | case *int8: 522 | dest[i] = &dr.Values.Int8 523 | case *int16: 524 | dest[i] = &dr.Values.Int16 525 | case *int32: 526 | dest[i] = &dr.Values.Int32 527 | case *int64: 528 | dest[i] = &dr.Values.Int64 529 | case *uint: 530 | dest[i] = &dr.Values.Uint 531 | case *uint8: 532 | dest[i] = &dr.Values.Uint8 533 | case *uint16: 534 | dest[i] = &dr.Values.Uint16 535 | case *uint32: 536 | dest[i] = &dr.Values.Uint32 537 | case *uint64: 538 | dest[i] = &dr.Values.Uint64 539 | case *float32: 540 | dest[i] = &dr.Values.Float32 541 | case *float64: 542 | dest[i] = &dr.Values.Float64 543 | case *string: 544 | dest[i] = &dr.Values.String 545 | case *[]byte: 546 | dest[i] = &dr.Values.Bytes 547 | case *bool: 548 | dest[i] = &dr.Values.Bool 549 | case *sql.NullBool: 550 | dest[i] = &dr.Values.NullBool 551 | case *sql.NullFloat64: 552 | dest[i] = &dr.Values.NullFloat64 553 | case *sql.NullInt32: 554 | dest[i] = &dr.Values.NullInt32 555 | case *sql.NullInt64: 556 | dest[i] = &dr.Values.NullInt64 557 | case *sql.NullString: 558 | dest[i] = &dr.Values.NullString 559 | case *sql.NullTime: 560 | dest[i] = &dr.Values.NullTime 561 | } 562 | } 563 | return nil 564 | } 565 | 566 | func createDummyRow(destTime time.Time) *dummyRow { 567 | return &dummyRow{ 568 | Values: dummyValues{ 569 | Int: -5, 570 | Int8: -4, 571 | Int16: -3, 572 | Int32: -2, 573 | Int64: -1, 574 | Uint: 1, 575 | Uint8: 2, 576 | Uint16: 3, 577 | Uint32: 4, 578 | Uint64: 5, 579 | Float32: 42.1, 580 | Float64: 42.2, 581 | Bool: true, 582 | String: "hello 異世界", 583 | Bytes: []byte("hello 異世界"), 584 | NullBool: sql.NullBool{Bool: true, Valid: true}, 585 | NullFloat64: sql.NullFloat64{Float64: 42.2, Valid: true}, 586 | NullInt32: sql.NullInt32{Int32: 421, Valid: true}, 587 | NullInt64: sql.NullInt64{Int64: 422, Valid: true}, 588 | NullString: sql.NullString{String: "hello 異世界", Valid: true}, 589 | NullTime: sql.NullTime{Time: destTime, Valid: true}, 590 | }, 591 | } 592 | } 593 | 594 | func ExampleSQLScan_suppliedFields() { 595 | currtime := time.Now() 596 | dr := createDummyRow(currtime) 597 | result := dummyValues{} 598 | if err := SQLScan(dr, &result, 599 | "", /* This is the tag, since we don't have so put it empty 600 | to match the field name */ 601 | /* Below arguments are variadic and we only take several 602 | fields from all available dummyValues */ 603 | "Int32", "Uint64", "Bool", "Bytes", 604 | "NullString", "NullTime"); err != nil { 605 | fmt.Println("Error happened!") 606 | return 607 | } 608 | fmt.Printf("NullString is Valid? %t\n", result.NullString.Valid) 609 | fmt.Printf("NullTime is Valid? %t\n", result.NullTime.Valid) 610 | fmt.Printf("result.NullTime.Time.Equal(dr.Values.NullTime.Time)? %t\n", 611 | result.NullTime.Time.Equal(dr.Values.NullTime.Time)) 612 | fmt.Printf("result.Uint64 == %d\n", result.Uint64) 613 | 614 | // output: 615 | // NullString is Valid? true 616 | // NullTime is Valid? true 617 | // result.NullTime.Time.Equal(dr.Values.NullTime.Time)? true 618 | // result.Uint64 == 5 619 | } 620 | 621 | func ExampleSQLScan_allFields() { 622 | currtime := time.Now() 623 | dr := createDummyRow(currtime) 624 | result := dummyValues{} 625 | if err := SQLScan(dr, &result, ""); err != nil { 626 | fmt.Println("Error happened!") 627 | return 628 | } 629 | fmt.Printf("NullString is Valid? %t\n", result.NullString.Valid) 630 | fmt.Printf("result.NullString is %s\n", result.NullString.String) 631 | fmt.Printf("NullTime is Valid? %t\n", result.NullTime.Valid) 632 | fmt.Printf("result.NullTime.Time.Equal(dr.Values.NullTime.Time)? %t\n", 633 | result.NullTime.Time.Equal(dr.Values.NullTime.Time)) 634 | fmt.Printf("result.Uint64 == %d\n", result.Uint64) 635 | 636 | // output: 637 | // NullString is Valid? true 638 | // result.NullString is hello 異世界 639 | // NullTime is Valid? true 640 | // result.NullTime.Time.Equal(dr.Values.NullTime.Time)? true 641 | // result.Uint64 == 5 642 | } 643 | 644 | func notin(s string, pool ...string) bool { 645 | for _, sp := range pool { 646 | if strings.Contains(s, sp) { 647 | return false 648 | } 649 | } 650 | return true 651 | } 652 | 653 | func compareErrorReports(t *testing.T, msgs, errval, errfield []string) { 654 | for _, msg := range msgs { 655 | var ( 656 | field string 657 | ) 658 | if notin(msg, errval...) { 659 | t.Errorf("value '%s' not found", msg) 660 | } 661 | if field != "" && notin(field, errfield...) { 662 | t.Errorf("field '%s' not found", field) 663 | } 664 | } 665 | } 666 | 667 | func TestBetterErrorReporting(t *testing.T) { 668 | type SomeStruct struct { 669 | Field1 int `errtag:"fieldint"` 670 | Field2 bool `errtag:"fieldbol"` 671 | Field3 string `errtag:"fieldstr"` 672 | Field4 float64 `errtag:"fieldflo"` 673 | Field5 struct{} `errtag:"fieldsru"` 674 | } 675 | field1 := "this should be int" 676 | field2 := "this should be boolean" 677 | field3 := "this is succesfully converted" 678 | field4 := "this should be float64" 679 | field5 := "this should be struct" 680 | ssmap := Mapped{ 681 | "Field1": field1, 682 | "Field2": field2, 683 | "Field3": field3, 684 | "Field4": field4, 685 | "Field5": field5, 686 | } 687 | ss := SomeStruct{} 688 | err := FillStruct(&ss, ssmap) 689 | if err == nil { 690 | t.Errorf("Error should not nil") 691 | } 692 | t.Log(err) 693 | if ss.Field3 != field3 { 694 | t.Errorf("ss.Field3 expected '%s' but got '%s'", field3, ss.Field3) 695 | } 696 | errmsg := err.Error() 697 | msgs := strings.Split(errmsg, ",") 698 | if len(msgs) == 0 { 699 | t.Errorf("Error message should report more than one field, got 0 report") 700 | } 701 | errval := []string{field1, field2, field4, field5} 702 | for i, s := range errval { 703 | errval[i] = fmt.Sprintf(`"%s"`, s) 704 | } 705 | errfield := []string{"Field1", "Field2", "Field4", "Field5"} 706 | compareErrorReports(t, msgs, errval, errfield) 707 | 708 | ssmaptag := Mapped{ 709 | "fieldint": field1, 710 | "fieldbol": field2, 711 | "fieldstr": field3, 712 | "fieldflo": field4, 713 | "fieldsru": field5, 714 | } 715 | ss = SomeStruct{} 716 | err = FillStructByTags(&ss, ssmaptag, "errtag") 717 | if err == nil { 718 | t.Errorf("Error should not nil") 719 | } 720 | if ss.Field3 != field3 { 721 | t.Errorf("ss.Field3 expected '%s' but got '%s'", field3, ss.Field3) 722 | } 723 | errmsg = err.Error() 724 | msgs = strings.Split(errmsg, ",") 725 | if len(msgs) == 0 { 726 | t.Errorf("Error message should report more than one field, got 0 report") 727 | } 728 | errfield = []string{"fieldint", "fieldbol", "fieldflo", "fieldsru"} 729 | compareErrorReports(t, msgs, errval, errfield) 730 | } 731 | 732 | type ( 733 | embedObj struct { 734 | FieldInt int `json:"fieldInt"` 735 | FieldStr string `json:"fieldStr"` 736 | FieldFloat float64 `json:"fieldFloat"` 737 | } 738 | embedEmbed struct { 739 | Embed1 embedObj `json:"embed1"` 740 | Embed2 *embedObj `json:"embed2"` 741 | } 742 | embedObjs struct { 743 | Objs []*embedObj `json:"embeds"` 744 | } 745 | ) 746 | 747 | func TestNilValue(t *testing.T) { 748 | obj := embedEmbed{ 749 | Embed1: embedObj{1, "one", 1.1}, 750 | } 751 | objmap := MapTags(&obj, "json") 752 | embed2 := embedEmbed{} 753 | if err := FillStructByTags(&embed2, objmap, "json"); err != nil { 754 | t.Errorf("objmap fill fail: %v", err) 755 | } 756 | if embed2.Embed2 != nil { 757 | t.Errorf("Invalid nil conversion, value should be nil") 758 | } 759 | 760 | objmap = MapFields(&obj) 761 | embed2 = embedEmbed{} 762 | if err := FillStruct(&embed2, objmap); err != nil { 763 | t.Errorf("objmap fields fill fail: %v", err) 764 | } 765 | if embed2.Embed2 != nil { 766 | t.Errorf("Invalid nil conversion, value should be nil") 767 | } 768 | 769 | objsem := embedObjs{ 770 | Objs: []*embedObj{ 771 | {1, "one", 1.1}, 772 | {2, "two", 2.2}, 773 | nil, 774 | {4, "four", 3.3}, 775 | {5, "five", 4.4}, 776 | }, 777 | } 778 | objsmap := MapTags(&objsem, "json") 779 | fillobjsem := embedObjs{} 780 | if err := FillStructByTags(&fillobjsem, objsmap, "json"); err != nil { 781 | t.Errorf("Should not fail: %v", err) 782 | } 783 | for i, obj := range fillobjsem.Objs { 784 | if obj == nil && i != 2 { 785 | t.Errorf("index %d of object value %v should not nil", i, obj) 786 | } else if i == 2 && obj != nil { 787 | t.Errorf("index 3 of object value %v should be nil", obj) 788 | } 789 | } 790 | 791 | objsmap = MapFields(&objsem) 792 | fillobjsem = embedObjs{} 793 | if err := FillStruct(&fillobjsem, objsmap); err != nil { 794 | t.Errorf("Should not fail: %v", err) 795 | } 796 | for i, obj := range fillobjsem.Objs { 797 | if obj == nil && i != 2 { 798 | t.Errorf("index %d of object value %v should not nil", i, obj) 799 | } else if i == 2 && obj != nil { 800 | t.Errorf("index 3 of object value %v should be nil", obj) 801 | } 802 | } 803 | 804 | } 805 | 806 | func eq(a, b *embedObj) bool { 807 | if a == nil || b == nil { 808 | return false 809 | } 810 | return a.FieldFloat == b.FieldFloat && a.FieldInt == b.FieldInt && 811 | a.FieldStr == b.FieldStr 812 | } 813 | 814 | func arrobj(t *testing.T) { 815 | objsem := embedObjs{ 816 | Objs: []*embedObj{ 817 | {1, "one", 1.1}, 818 | {2, "two", 2.2}, 819 | nil, 820 | {4, "four", 3.3}, 821 | {5, "five", 4.4}, 822 | }, 823 | } 824 | maptag := MapTags(&objsem, "json") 825 | 826 | embedstf, ok := maptag["embeds"].([]interface{}) 827 | if !ok { 828 | t.Fatalf("Wrong type, %#v", maptag["embeds"]) 829 | } 830 | if len(embedstf) != len(objsem.Objs) { 831 | t.Fatalf("len(embedstf) expected %d got %d\n", len(objsem.Objs), len(embedstf)) 832 | } 833 | for i, emtf := range embedstf { 834 | if i == 2 && emtf != nil { 835 | t.Errorf("%v expected nil, got empty value\n", emtf) 836 | continue 837 | } 838 | if i == 2 { 839 | continue 840 | 841 | } 842 | emtfmap, ok := emtf.(Mapped) 843 | // emtf2, ok := emtf.(*embedObj) 844 | if !ok { 845 | t.Errorf("Cannot cast to Mapped %#v\n", emtf) 846 | continue 847 | } 848 | emtf2 := &embedObj{} 849 | if err := FillStructByTags(emtf2, emtfmap, "json"); err != nil { 850 | t.Error(err) 851 | } 852 | if !eq(emtf2, objsem.Objs[i]) && i != 2 { 853 | t.Errorf("embedObj (%#v) at index %d got wrong value, expect (%#v)", 854 | emtf2, i, objsem.Objs[i]) 855 | } 856 | } 857 | 858 | // raw of mapped case 859 | rawtfobj := Mapped{ 860 | "embeds": []Mapped{ 861 | {"fieldInt": 1, "fieldStr": "one", "fieldFloat": 1.1}, 862 | {"fieldInt": 2, "fieldStr": "two", "fieldFloat": 2.2}, 863 | nil, 864 | {"fieldInt": 4, "fieldStr": "four", "fieldFloat": 4.4}, 865 | {"fieldInt": 5, "fieldStr": "five", "fieldFloat": 5.5}, 866 | }, 867 | } 868 | expectedVals := []*embedObj{ 869 | {1, "one", 1.1}, 870 | {2, "two", 2.2}, 871 | nil, 872 | {4, "four", 4.4}, 873 | {5, "five", 5.5}, 874 | } 875 | 876 | testit := func(raw Mapped) { 877 | newemb := embedObjs{} 878 | err := FillStructByTags(&newemb, rawtfobj, "json") 879 | if err != nil { 880 | t.Error(err) 881 | } 882 | t.Logf("%#v\n", newemb) 883 | newemblen := len(newemb.Objs) 884 | exptlen := len(expectedVals) 885 | if newemblen != exptlen { 886 | t.Fatalf("New len got %d, expected %d", newemblen, exptlen) 887 | } 888 | for i, ob := range newemb.Objs { 889 | if i == 2 && ob != nil { 890 | t.Errorf("%v expected nil, got empty value\n", ob) 891 | continue 892 | } 893 | if i != 2 && !eq(ob, expectedVals[i]) { 894 | t.Errorf("embedObj (%#v) at index %d got wrong value, expect (%#v)", 895 | ob, i, expectedVals[i]) 896 | 897 | } 898 | } 899 | 900 | } 901 | testit(rawtfobj) 902 | 903 | // case of actual object 904 | rawtfobj = Mapped{ 905 | "embeds": []*embedObj{ 906 | {1, "one", 1.1}, 907 | {2, "two", 2.2}, 908 | nil, 909 | {4, "four", 4.4}, 910 | {5, "five", 5.5}, 911 | }, 912 | } 913 | testit(rawtfobj) 914 | } 915 | 916 | func arrvalues(t *testing.T) { 917 | type ( 918 | ArrInt []int 919 | MyArrInt struct { 920 | ArrInt `json:"array_int"` 921 | } 922 | APint []*int 923 | MPint struct { 924 | APint `json:"ptarr_int"` 925 | } 926 | APfloat []*float32 927 | MPfloat struct { 928 | APfloat `json:"ptarr_float"` 929 | } 930 | ) 931 | 932 | initobj := MyArrInt{ 933 | ArrInt: []int{1, 2, 3, 4, 5}, 934 | } 935 | minitobj := MapTags(&initobj, "json") 936 | arintminit, ok := minitobj["array_int"].([]interface{}) 937 | if !ok { 938 | t.Errorf("failed to cast %#v\n", minitobj["array_int"]) 939 | return 940 | } 941 | t.Logf("arrintminit %#v\n", arintminit) 942 | rawminit := Mapped{ 943 | "array_int": []int{5, 4, 3, 2, 1}, 944 | } 945 | var rinit MyArrInt 946 | if err := FillStructByTags(&rinit, rawminit, "json"); err != nil { 947 | t.Error(err) 948 | } 949 | t.Logf("rinit %#v\n", rinit) 950 | 951 | a := new(int) 952 | b := new(int) 953 | c := new(int) 954 | d := new(int) 955 | e := new(int) 956 | *a = 11 957 | *b = 22 958 | *c = 33 959 | *d = 44 960 | *e = 55 961 | pinitobj := MPint{ 962 | APint: []*int{a, b, nil, c, d, e}, 963 | } 964 | mapinit := MapTags(&pinitobj, "json") 965 | rawpinit, ok := mapinit["ptarr_int"].([]interface{}) 966 | if !ok { 967 | t.Errorf("failed conv %#v\n", mapinit["ptrarr_int"]) 968 | } 969 | 970 | t.Logf("rawpinit %#v\n", rawpinit) 971 | for i, rp := range rawpinit { 972 | if i == 2 && rp != nil { 973 | t.Errorf("rp should be nil, %#v\n", rp) 974 | } 975 | if i == 2 { 976 | continue 977 | } 978 | p, ok := rp.(int) 979 | if !ok { 980 | t.Errorf("failed cast, got %#v %T\n", rp, rp) 981 | 982 | } 983 | ptrop := pinitobj.APint[i] 984 | if ptrop != nil && i != 2 && *ptrop != p { 985 | t.Errorf("Wrong value at index %d, got %#v expected %#v", 986 | i, p, *ptrop) 987 | } 988 | } 989 | 990 | rawpinit2 := Mapped{ 991 | "ptarr_int": []interface{}{55, 44, nil, 33, 22, 11}, 992 | } 993 | var pinit2 MPint 994 | if err := FillStructByTags(&pinit2, rawpinit2, "json"); err != nil { 995 | t.Error(err) 996 | } 997 | expt2 := []*int{e, d, nil, c, b, a} 998 | t.Logf("pinit2 %#v\n", pinit2) 999 | for i, rp := range pinit2.APint { 1000 | if i == 2 && rp != nil { 1001 | t.Errorf("rp should be nil, %#v\n", rp) 1002 | } 1003 | if i == 2 { 1004 | continue 1005 | } 1006 | 1007 | if !ok { 1008 | t.Errorf("failed cast, got %#v %T\n", rp, rp) 1009 | 1010 | } 1011 | ptrop := expt2[i] 1012 | if ptrop != nil && i != 2 && *ptrop != *rp { 1013 | t.Errorf("Wrong value at index %d, got %#v expected %#v", 1014 | i, *rp, *ptrop) 1015 | } 1016 | } 1017 | 1018 | rawfloat := Mapped{ 1019 | "ptarr_float": []interface{}{1.1, 2.2, nil, 3.3, 4.4}, 1020 | } 1021 | var mfloat MPfloat 1022 | if err := FillStructByTags(&mfloat, rawfloat, "json"); err != nil { 1023 | t.Error(err) 1024 | } 1025 | testelemfloat := func(ap APfloat, expected []interface{}) { 1026 | vallen := len(ap) 1027 | expectlen := len(expected) 1028 | if vallen != expectlen { 1029 | t.Fatalf("Got %d length, expected %d length", vallen, expectlen) 1030 | } 1031 | for i, rp := range ap { 1032 | if i == 2 && rp != nil { 1033 | t.Errorf("rp should be nil, got %v %#v\n", *rp, rp) 1034 | } else { 1035 | if expected[i] == nil { 1036 | continue 1037 | } 1038 | ptrop := expected[i].(float64) 1039 | if float32(ptrop) != *rp { 1040 | t.Errorf("Wrong value at index %d, got %#v expected %#v", 1041 | i, *rp, ptrop) 1042 | } 1043 | } 1044 | } 1045 | } 1046 | testelemfloat(mfloat.APfloat, rawfloat["ptarr_float"].([]interface{})) 1047 | } 1048 | 1049 | func TestTagsSlice(t *testing.T) { 1050 | arrobj(t) 1051 | arrvalues(t) 1052 | } 1053 | 1054 | type sometype struct { 1055 | Value string `json:"value"` 1056 | } 1057 | 1058 | func (s sometype) MapEncode() (interface{}, error) { 1059 | return s.Value, nil 1060 | } 1061 | 1062 | func (s *sometype) MapDecode(x interface{}) error { 1063 | if x == nil { 1064 | s.Value = "" 1065 | return nil 1066 | } 1067 | xstr, ok := x.(string) 1068 | if !ok { 1069 | return nil 1070 | } 1071 | s.Value = xstr 1072 | return nil 1073 | } 1074 | func ExampleMapEncoder() { 1075 | type MapEncoderExample struct { 1076 | Data sometype `json:"data"` 1077 | } 1078 | type map2 struct { 1079 | Data *sometype `json:"data"` 1080 | } 1081 | 1082 | s := MapEncoderExample{Data: sometype{Value: "hello"}} 1083 | smap := MapTags(&s, "json") 1084 | str, ok := smap["data"].(string) 1085 | if !ok { 1086 | fmt.Println("Not ok!") 1087 | return 1088 | } 1089 | fmt.Println(str) 1090 | 1091 | s2 := map2{Data: &sometype{Value: "hello2"}} 1092 | smap = MapTags(&s2, "json") 1093 | str, ok = smap["data"].(string) 1094 | if !ok { 1095 | fmt.Println("Not ok!") 1096 | return 1097 | } 1098 | fmt.Println(str) 1099 | 1100 | // Output: 1101 | // hello 1102 | // hello2 1103 | } 1104 | 1105 | func ExampleMapDecoder() { 1106 | var err error 1107 | var smap map[string]interface{} 1108 | 1109 | type MapDecoderExample struct { 1110 | Data sometype `json:"data"` 1111 | } 1112 | smap = map[string]interface{}{"data": "hello"} 1113 | var s MapDecoderExample 1114 | err = FillStructByTags(&s, smap, "json") 1115 | if err != nil { 1116 | fmt.Println(err) 1117 | return 1118 | } 1119 | fmt.Println(s.Data.Value) 1120 | 1121 | type map2 struct { 1122 | Data *sometype `json:"data"` 1123 | } 1124 | smap = map[string]interface{}{"data": "hello2"} 1125 | var s2 map2 1126 | // s2 := map2{Data: &sometype{}} 1127 | err = FillStructByTags(&s2, smap, "json") 1128 | if err != nil { 1129 | fmt.Println(err) 1130 | return 1131 | } 1132 | fmt.Println(s2.Data.Value) 1133 | 1134 | // Output: 1135 | // hello 1136 | // hello2 1137 | } 1138 | 1139 | func TestMapFromEmpty(t *testing.T) { 1140 | type ts1 struct{} 1141 | type ts2 struct{} 1142 | type ts3 struct{} 1143 | type ts4 struct{} 1144 | type teststruct struct { 1145 | Ts1 *ts1 1146 | Ts2 *ts2 1147 | Ts3 *ts3 1148 | Ts4 *ts4 1149 | } 1150 | var ( 1151 | source Mapped 1152 | target teststruct 1153 | ) 1154 | err := FillStruct(&target, source) 1155 | if err != nil { 1156 | t.Error(err) 1157 | } 1158 | } 1159 | 1160 | func TestMapArray(t *testing.T) { 1161 | type ( 1162 | arrint struct { 1163 | Fields []int64 `json:"array"` 1164 | } 1165 | ssarrint struct { 1166 | Arrint arrint `json:"array_int"` 1167 | } 1168 | 1169 | ssarrint2 struct { 1170 | Arrint arrint `json:"array_int"` 1171 | } 1172 | ) 1173 | raw := []byte(` 1174 | { 1175 | "array_int": { 1176 | "array": [1000000, 10000000, 100000000] 1177 | } 1178 | 1179 | } 1180 | `) 1181 | var ss ssarrint 1182 | json.Unmarshal(raw, &ss) 1183 | sm := MapTags(&ss, "json") 1184 | var ss2 ssarrint2 1185 | if err := FillStructByTags(&ss2, sm, "json"); err != nil { 1186 | t.Log(err) 1187 | } 1188 | for i := range ss.Arrint.Fields { 1189 | expected := ss.Arrint.Fields[i] 1190 | got := ss2.Arrint.Fields[i] 1191 | if expected != got { 1192 | t.Errorf("fail fill struct, expected %d, got %d", expected, got) 1193 | } 1194 | } 1195 | 1196 | raw = []byte(` 1197 | { 1198 | "array_int": { 1199 | "array": [1000, "hehue", 42.42] 1200 | } 1201 | 1202 | }`) 1203 | ss = ssarrint{} 1204 | json.Unmarshal(raw, &ss) 1205 | sm = MapTags(&ss, "json") 1206 | ss2 = ssarrint2{} 1207 | if err := FillStructByTags(&ss2, sm, "json"); err != nil { 1208 | t.Error(err) 1209 | } 1210 | if ss2.Arrint.Fields[0] != ss.Arrint.Fields[0] { 1211 | t.Errorf("expected %d got %d", ss.Arrint.Fields[0], ss2.Arrint.Fields[0]) 1212 | } 1213 | 1214 | if ss2.Arrint.Fields[1] != 0 || ss2.Arrint.Fields[2] != 0 { 1215 | t.Error("expected rest of elem is 0, but got other values") 1216 | } 1217 | } 1218 | 1219 | func BenchmarkMapField(b *testing.B) { 1220 | var m Mapped 1221 | for i := 0; i < b.N; i++ { 1222 | m = MapFields(sourceobj) 1223 | } 1224 | _ = m 1225 | } 1226 | 1227 | func BenchmarkMapTags(b *testing.B) { 1228 | var m Mapped 1229 | for i := 0; i < b.N; i++ { 1230 | m = MapTags(sourceobj, "json") 1231 | } 1232 | _ = m 1233 | } 1234 | 1235 | func BenchmarkFillStruct(b *testing.B) { 1236 | m := MapFields(sourceobj) 1237 | var s source 1238 | for i := 0; i < b.N; i++ { 1239 | FillStruct(&s, m) 1240 | } 1241 | _ = s 1242 | 1243 | } 1244 | 1245 | func BenchmarkFillStructByTags(b *testing.B) { 1246 | m := MapTags(sourceobj, "json") 1247 | var s source 1248 | for i := 0; i < b.N; i++ { 1249 | FillStructByTags(&s, m, "json") 1250 | } 1251 | _ = s 1252 | } 1253 | --------------------------------------------------------------------------------