From e0b7551d22caf0959788ece37d9202b5c71d8d8b Mon Sep 17 00:00:00 2001 From: Dumindu Madithiyagasthenna Date: Wed, 30 Jun 2021 16:29:57 +1000 Subject: [PATCH 1/8] Circle CI added --- .circleci/config.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..7d77d92 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,26 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. See: https://circleci.com/docs/2.0/configuration-reference +version: 2.1 +jobs: + build: + working_directory: ~/repo + docker: + - image: circleci/golang:1.15.8 + steps: + - checkout + - restore_cache: + keys: + - go-mod-v4-{{ checksum "go.sum" }} + - run: + name: Install Dependencies + command: go mod download + - save_cache: + key: go-mod-v4-{{ checksum "go.sum" }} + paths: + - "/go/pkg/mod" + - run: + name: Run tests + command: | + mkdir -p /tmp/test-reports + gotestsum --junitfile /tmp/test-reports/unit-tests.xml + - store_test_results: + path: /tmp/test-reports From b0c28223f46e889ec3a600487ce621b552c3aa36 Mon Sep 17 00:00:00 2001 From: Dumindu Madithiyagasthenna Date: Wed, 30 Jun 2021 16:38:15 +1000 Subject: [PATCH 2/8] Coveralls code coverage added --- .circleci/config.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7d77d92..898a63f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,9 @@ jobs: - go-mod-v4-{{ checksum "go.sum" }} - run: name: Install Dependencies - command: go mod download + command: | + go mod download + go get github.com/mattn/goveralls - save_cache: key: go-mod-v4-{{ checksum "go.sum" }} paths: @@ -22,5 +24,7 @@ jobs: command: | mkdir -p /tmp/test-reports gotestsum --junitfile /tmp/test-reports/unit-tests.xml + go test -v -cover -race -coverprofile=/home/ubuntu/coverage.out + /home/ubuntu/.go_workspace/bin/goveralls -coverprofile=/home/ubuntu/coverage.out -service=circle-ci -repotoken=$COVERALLS_TOKEN - store_test_results: path: /tmp/test-reports From e25dba452e0b40d612049014d89631103bbe724d Mon Sep 17 00:00:00 2001 From: Dumindu Madithiyagasthenna Date: Wed, 30 Jun 2021 16:44:09 +1000 Subject: [PATCH 3/8] Updated test name --- map_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/map_test.go b/map_test.go index 399702c..f32b79f 100644 --- a/map_test.go +++ b/map_test.go @@ -32,9 +32,9 @@ type Example struct { //three int `custom:"three"` // unexported, TODO: handle panic } -// TestHelloName calls greetings.Hello with a name, checking -// for a valid return value. -func TestHelloName(t *testing.T) { +// TestStructToMap calls ToMap function and checks against the expcted result +// The struct used tries to cover all the scenarios +func TestStructToMap(t *testing.T) { // the initial object initial := Example{ From f2c8f81592c6546569a8b181c20a342baf903c3e Mon Sep 17 00:00:00 2001 From: Dumindu Madithiyagasthenna Date: Wed, 30 Jun 2021 16:45:53 +1000 Subject: [PATCH 4/8] Added go doc and go report card badges --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index cf3d8db..6738eae 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +[![Documentation](https://godoc.org/github.com/dumim/tagconv?status.svg)](http://godoc.org/github.com//) +[![Go Report Card](https://goreportcard.com/badge/github.com/dumim/tagconv)](https://goreportcard.com/report/github.com//) + # TagConv Convert any Go Struct to a Map based on custom struct tags with dot notation From 0e114a215528d6417f0fe503ce1f7e4dab3f6aed Mon Sep 17 00:00:00 2001 From: Dumindu Madithiyagasthenna Date: Thu, 1 Jul 2021 11:15:22 +1000 Subject: [PATCH 5/8] Fixed badge links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6738eae..ac67ba4 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -[![Documentation](https://godoc.org/github.com/dumim/tagconv?status.svg)](http://godoc.org/github.com//) -[![Go Report Card](https://goreportcard.com/badge/github.com/dumim/tagconv)](https://goreportcard.com/report/github.com//) +[![Documentation](https://godoc.org/github.com/dumim/tagconv?status.svg)](http://godoc.org/github.com/dumim/tagconv) +[![Go Report Card](https://goreportcard.com/badge/github.com/dumim/tagconv)](https://goreportcard.com/report/github.com/dumim/tagconv) # TagConv Convert any Go Struct to a Map based on custom struct tags with dot notation From 2fc8279f69ff341045efc1297604a4bcce556027 Mon Sep 17 00:00:00 2001 From: Dumindu Madithiyagasthenna Date: Thu, 1 Jul 2021 11:26:37 +1000 Subject: [PATCH 6/8] Readme updated with usage examples --- README.md | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ac67ba4..afd2f80 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,109 @@ [![Documentation](https://godoc.org/github.com/dumim/tagconv?status.svg)](http://godoc.org/github.com/dumim/tagconv) [![Go Report Card](https://goreportcard.com/badge/github.com/dumim/tagconv)](https://goreportcard.com/report/github.com/dumim/tagconv) +[![MIT License](https://img.shields.io/apm/l/atomic-design-ui.svg?)](https://github.com/tterb/atomic-design-ui/blob/master/LICENSEs) # TagConv Convert any Go Struct to a Map based on custom struct tags with dot notation -TODO: installation, examples, how-to, tests +TODO: bacgkround +## Usage/Examples + +Import the package +```go +import "github.com/dumim/tagconv" +``` + +Given a deeply-nested complex struct with custom tags like below: +```go +type Obj struct { + Name string `custom:"name"` + Text string `custom:"text"` + World string `custom:"data.world"` // dot notation inside nested struct +} +type ObjTwo struct { + Hello string `custom:"hello"` + Text string `custom:"data.text"` +} +type ObjThree struct { + Name string `custom:"name"` + Value int `custom:"value"` +} +type Example struct { + Name string `custom:"name"` + Email string `custom:"email"` + Obj Obj `custom:"object"` + ObjTwo ObjTwo // no tag + ObjThree ObjTwo `custom:"-"` // explicitly ignored + Id int `custom:"id"` + Call int `custom:"data.call"` // top-level dot notation + ArrayObj []ObjThree `custom:"list"` +} +``` +The `ToMap` function can be used to convert this into a JSON/Map based on the values defined in the given custom tag like so. +```go +obj := Example{ + Name: "2", + Email: "3", + Obj: Obj{ + Name: "4", + Text: "5", + World: "6", + }, + ObjTwo: ObjTwo{ + Hello: "1", + Text: "2", + }, + Id: 01, + Call: 02, + ArrayObj: []ObjThree{ + {"hi", 1}, + {"world", 2}, + }, +} + +// get the map from custom tags +tagName = "custom" +myMap, err := ToMap(obj, tagName) +if err != nil { + panic() +} + +myMapJSON, err := json.MarshalIndent(myMap, "", " ") +if err != nil { + panic() +} + +fmt.Print(myMapJSON) + +``` +This will produce a result similar to: +```json +{ + "name": "2", + "email": "3", + "object": { + "name": "4", + "text": "5", + "data": { + "world": "6" + } + }, + "hello": "1", + "data": { + "text": "2", + "call": 2 + }, + "id": 1, + "list": [ + { + "name": "hi", + "value": 1 + }, + { + "name": "world", + "value": 2 + } + ] +} +``` + From f3f5ebab245d8cfd7570a1eb3a6d212ffa267178 Mon Sep 17 00:00:00 2001 From: Dumindu Madithiyagasthenna Date: Thu, 1 Jul 2021 13:22:22 +1000 Subject: [PATCH 7/8] Some background added --- README.md | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index afd2f80..b582aed 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,25 @@ [![MIT License](https://img.shields.io/apm/l/atomic-design-ui.svg?)](https://github.com/tterb/atomic-design-ui/blob/master/LICENSEs) # TagConv -Convert any Go Struct to a Map based on custom struct tags with dot notation +Convert any Go Struct to a Map based on custom struct tags with dot notation. -TODO: bacgkround +## Background +This package tries to simplify certain use-cases where a struct needs to be mapped manually to a different struct, which holds the same data but is organised differently. +(eg: mapping data from the db to a presentable API output) + +This package allows you to use custom struct tags (which could be any string) to define the mapping. +This mapping follows the dot-notation convention. Example: +```go +Hello string `mytag:"hello.world"` +``` +The above will result in a map with the JSON equivalent of: +```json +{ + "hello": { + "world": "hello world" + } +} +``` ## Usage/Examples Import the package @@ -106,4 +122,11 @@ This will produce a result similar to: ] } ``` - + +## Acknowledgements + +- [Helpful Stackoverflow answer](https://stackoverflow.com/a/7794127/10340220) +- [Mergo](https://github.com/imdario/mergo) +## Contributing + +Contributions are always welcome! From 49c562f42af69ea1e73c6fc4144477403bf027d0 Mon Sep 17 00:00:00 2001 From: Dumindu Madithiyagasthenna Date: Tue, 6 Jul 2021 10:17:27 +1000 Subject: [PATCH 8/8] Fix panic on unexported fields --- map.go | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/map.go b/map.go index 71b6438..fa012d1 100644 --- a/map.go +++ b/map.go @@ -31,8 +31,6 @@ var tagName = "" func getMapOfAllKeyValues(s interface{}) (*map[string]interface{}, error) { var vars = make(map[string]interface{}) // this will hold the variables as a map (JSON) - // TODO: catch panics when reflecting unexported fields - // get value of object t := reflect.ValueOf(s) if t.IsZero() { @@ -55,9 +53,11 @@ func getMapOfAllKeyValues(s interface{}) (*map[string]interface{}, error) { if tag == "" { if t.Field(i).Kind() == reflect.Struct { // TODO: check for error - qVars, _ := getMapOfAllKeyValues(t.Field(i).Interface()) //recursive call - for k, v := range *qVars { - vars[k] = v + if t.Field(i).CanInterface() { + qVars, _ := getMapOfAllKeyValues(t.Field(i).Interface()) //recursive call + for k, v := range *qVars { + vars[k] = v + } } } else { continue @@ -66,12 +66,16 @@ func getMapOfAllKeyValues(s interface{}) (*map[string]interface{}, error) { // recursive check nested fields in case this is a struct if t.Field(i).Kind() == reflect.Struct { // TODO: check for error - qVars, _ := getMapOfAllKeyValues(t.Field(i).Interface()) - for k, v := range *qVars { - vars[fmt.Sprintf("%s.%s", tag, k)] = v // prepend the parent tag name + if t.Field(i).CanInterface() { + qVars, _ := getMapOfAllKeyValues(t.Field(i).Interface()) + for k, v := range *qVars { + vars[fmt.Sprintf("%s.%s", tag, k)] = v // prepend the parent tag name + } } } else { - vars[tag] = t.Field(i).Interface() + if t.Field(i).CanInterface() { + vars[tag] = t.Field(i).Interface() + } } } } @@ -88,8 +92,10 @@ func getMapOfAllKeyValues(s interface{}) (*map[string]interface{}, error) { s := reflect.ValueOf(v) // iterate through the slice for i := 0; i < s.Len(); i++ { - m, _ := getMapOfAllKeyValues(s.Index(i).Interface()) // get the map value of the object, recursively - sliceOfMap = append(sliceOfMap, *m) // append to the slice + if t.Field(i).CanInterface() { + m, _ := getMapOfAllKeyValues(s.Index(i).Interface()) // get the map value of the object, recursively + sliceOfMap = append(sliceOfMap, *m) // append to the slice + } } finalMap[k] = sliceOfMap default: