Skip to content

Commit

Permalink
Specify type (#1)
Browse files Browse the repository at this point in the history
* 支持生成支持类型

* 更新
  • Loading branch information
guonaihong authored Jan 27, 2023
1 parent 9ed8bc1 commit b7d7eb0
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 12 deletions.
45 changes: 41 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
json/yaml/http header/query string 转成struct定义,免去手写struct的烦恼.


## 一、json字符串生成结构体
## 一、json/yaml/http header/query string 生成结构体
### 1.1 json字符串生成结构体
```go
import (
"github.com/antlabs/tostruct/json"
Expand Down Expand Up @@ -73,7 +74,7 @@ func main() {
}
```

## 二、http header生成结构体
### 1.2 http header生成结构体
```go
import (
"github.com/antlabs/tostruct/header"
Expand Down Expand Up @@ -105,7 +106,7 @@ type test struct {
*/
}
```
## 、查询字符串生成结构体
### 1.3、查询字符串生成结构体
```go
import (
"github.com/antlabs/tostruct/url"
Expand All @@ -132,7 +133,7 @@ type test struct {

```

## 四、yaml生成结构体
### 1.4 yaml生成结构体
```go
import (
"github.com/antlabs/tostruct/url"
Expand Down Expand Up @@ -209,3 +210,39 @@ type F struct {
*/
}
```

## 二、各种配置项函数用法
### 2.1 option.WithSpecifyType 指定生成类型
```go
obj := `
{
"action": "get",
"count": 0,
"data": {
"a123": "delivered"
},
"duration": 5,
"entities": [],
"timestamp": 1542601830084,
"uri": "http://XXXX/XXXX/XXXX/users/user1/offline_msg_status/123"
}
`
// 默认对象是转成结构体的,比如这里的data成员,有些接口返回的key是变动的,比如key是用户名value是消息是否投递成功(假装在im系统中)
// 此类业务就需要转成map[string]string类型,这里就可以用上option.WithSpecifyType
// 传参的类型是map[string]string, key为json路径,value值是要指定的类型
all, err := Marshal([]byte(obj), option.WithStructName("reqName"), option.WithTagName("json"), option.WithSpecifyType(map[string]string{
".data": "map[string]string",
}))

// output all
type reqName struct {
Action string `json:"action"`
Count int `json:"count"`
Data map[string]string `json:"data"`
Duration int `json:"duration"`
entities interface{} `json:"json:entities"`
Timestamp int `json:"timestamp"`
URI string `json:"uri"`
}

```
32 changes: 24 additions & 8 deletions json/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ const (
boolFmt = "%s %sbool `%s:\"%s\"`"
float64Fmt = "%s %sfloat64 `%s:\"%s\"`"
intFmt = "%s %sint `%s:\"%s\"`"
specifytFmt = "%s %s `%s:\"%s\"`"
)

func Marshal[T Type](t T, opt ...option.OptionFunc) (b []byte, err error) {
Expand All @@ -104,6 +105,7 @@ func newDefault() *JSON {
Option: option.Option{Tag: "json", StructName: defStructName, Inline: true}}

}

func new[T Type](t T, opt ...option.OptionFunc) (f *JSON, err error) {
var tmp any = t
var rv *JSON
Expand Down Expand Up @@ -155,7 +157,7 @@ func new[T Type](t T, opt ...option.OptionFunc) (f *JSON, err error) {
}

func (f *JSON) marshal() (b []byte, err error) {
f.marshalValue("", f.obj, false, 0, &f.buf)
f.marshalValue("", f.obj, false, 0, &f.buf, "")
f.buf.WriteString(endStruct)
if !f.Inline {
keys := mapex.Keys(f.structBuf)
Expand All @@ -170,6 +172,7 @@ func (f *JSON) marshal() (b []byte, err error) {
}

if b, err = format.Source(f.buf.Bytes()); err != nil {
fmt.Printf("%s\n", f.buf.String())
return nil, err
}

Expand All @@ -193,7 +196,11 @@ func (f *JSON) getStructTypeName(fieldName string) (structTypeName string, buf *
}
}

func (f *JSON) marshalMap(key string, m map[string]interface{}, typePrefix string, depth int, buf *bytes.Buffer) {
func appendKeyPath(pathKey string, key string) string {
return fmt.Sprintf("%s.%s", pathKey, strings.ToLower(key))
}

func (f *JSON) marshalMap(key string, m map[string]interface{}, typePrefix string, depth int, buf *bytes.Buffer, pathKey string) {

remaining := len(m)

Expand All @@ -212,6 +219,7 @@ func (f *JSON) marshalMap(key string, m map[string]interface{}, typePrefix strin

if len(key) > 0 {
if f.Inline {
// 如果是内嵌结构体
buf.WriteString(fmt.Sprintf(startInlineMap, fieldName, typePrefix))
} else {
// 生成struct类型名和子结构体可以保存的子buf
Expand All @@ -229,7 +237,7 @@ func (f *JSON) marshalMap(key string, m map[string]interface{}, typePrefix strin

f.writeIndent(buf, depth+1)

f.marshalValue(key, m[key], false, depth+1, buf)
f.marshalValue(key, m[key], false, depth+1, buf, appendKeyPath(pathKey, key))

f.writeObjSep(buf)
}
Expand All @@ -245,33 +253,41 @@ func (f *JSON) marshalMap(key string, m map[string]interface{}, typePrefix strin
}
}

func (f *JSON) marshalArray(key string, a []interface{}, depth int, buf *bytes.Buffer) {
func (f *JSON) marshalArray(key string, a []interface{}, depth int, buf *bytes.Buffer, keyPath string) {
if len(a) == 0 {
buf.WriteString(fmt.Sprintf("%s interface{} `json:\"json:%s\"`", key, key))
return
}

f.marshalValue(key, a[0], true, depth, buf)
f.marshalValue(key, a[0], true, depth, buf, keyPath)
}

func (f *JSON) marshalValue(key string, obj interface{}, fromArray bool, depth int, buf *bytes.Buffer) {
func (f *JSON) marshalValue(key string, obj interface{}, fromArray bool, depth int, buf *bytes.Buffer, keyPath string) {
typePrefix := ""
if fromArray {
typePrefix = "[]"
}

fieldName, tagName := name.GetFieldAndTagName(key)

if f.TypeMap != nil {
fieldType, ok := f.TypeMap[keyPath]
if ok {
buf.WriteString(fmt.Sprintf(specifytFmt, fieldName, fieldType, f.Tag, tagName))
return
}
}

tmpFieldName := strings.ToUpper(fieldName)
if tab.InitialismsTab[tmpFieldName] {
fieldName = tmpFieldName
}

switch v := obj.(type) {
case map[string]interface{}:
f.marshalMap(key, v, typePrefix, depth, buf)
f.marshalMap(key, v, typePrefix, depth, buf, keyPath)
case []interface{}:
f.marshalArray(key, v, depth, buf)
f.marshalArray(key, v, depth, buf, keyPath+"[0]")
case string:
buf.WriteString(fmt.Sprintf(stringFmt, fieldName, typePrefix, f.Tag, tagName))
case float64: //json默认解析的数字是float64类型
Expand Down
15 changes: 15 additions & 0 deletions json/json_empty_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package json

import (
"testing"

"github.com/antlabs/tostruct/option"
"github.com/stretchr/testify/assert"
)

func TestEmpty(t *testing.T) {
str := []byte("")
all, err := Marshal(str, option.WithStructName("empty"), option.WithTagName("json"))
assert.Error(t, err)
assert.Equal(t, all, []byte(nil))
}
86 changes: 86 additions & 0 deletions json/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,89 @@ func Test_Gen_Obj_JSON6(t *testing.T) {
}

}

func Test_Gen_Obj_JSON7(t *testing.T) {

obj := `
{
"action": "get",
"count": 0,
"data": {
"a123": "delivered"
},
"duration": 5,
"entities": [],
"timestamp": 1542601830084,
"uri": "http://XXXX/XXXX/XXXX/users/user1/offline_msg_status/123"
}
`

for _, v := range [][]byte{
func() []byte {
all, err := Marshal([]byte(obj), option.WithStructName("reqName"), option.WithTagName("json"), option.WithSpecifyType(map[string]string{
".data": "map[string]string",
}))
assert.NoError(t, err)
return all
}(),
func() []byte {
all, err := Marshal([]byte(obj), option.WithStructName("reqName"), option.WithTagName("json"), option.WithNotInline(), option.WithSpecifyType(map[string]string{
".data": "map[string]string",
}))
assert.NoError(t, err)
return all
}(),
} {

all := v
fmt.Println(string(all))
need, err := os.ReadFile("../testdata/testjson7.0.txt")
assert.NoError(t, err)
assert.Equal(t, string(bytes.TrimSpace(need)), string(bytes.TrimSpace(all)))
}

}

func Test_Gen_Obj_JSON8(t *testing.T) {

obj := `
{
"action": "get",
"count": 0,
"data": {
"data": {
"a123": "delivered"
}
},
"duration": 5,
"entities": [],
"timestamp": 1542601830084,
"uri": "http://XXXX/XXXX/XXXX/users/user1/offline_msg_status/123"
}
`

for k, v := range [][]byte{
func() []byte {
all, err := Marshal([]byte(obj), option.WithStructName("reqName"), option.WithTagName("json"), option.WithSpecifyType(map[string]string{
".data.data": "map[string]string",
}))
assert.NoError(t, err)
return all
}(),
func() []byte {
all, err := Marshal([]byte(obj), option.WithStructName("reqName"), option.WithTagName("json"), option.WithNotInline(), option.WithSpecifyType(map[string]string{
".data.data": "map[string]string",
}))
assert.NoError(t, err)
return all
}(),
} {

all := v
fmt.Println(string(all))
need, err := os.ReadFile(fmt.Sprintf("../testdata/testjson8.%d.txt", k))
assert.NoError(t, err)
assert.Equal(t, string(bytes.TrimSpace(need)), string(bytes.TrimSpace(all)))
}

}
20 changes: 20 additions & 0 deletions option/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type Option struct {
Inline bool
Tag string
StructName string
TypeMap map[string]string
}

// 控制生成的结构体是否内联
Expand Down Expand Up @@ -42,3 +43,22 @@ func WithStructName(name string) OptionFunc {
c.StructName = name
}
}

// 指定类型, datal默认转成struct, 这里直接指定生成map[string]string类型
// {
//
// "data" : {
// "user1": "111"
// }
// }
//
// WithSpecifyType(map[string]string{
// ".data": "map[string]string"
// })
//
// 目录只支持json/yaml
func WithSpecifyType(typeMap map[string]string) OptionFunc {
return func(c *Option) {
c.TypeMap = typeMap
}
}
9 changes: 9 additions & 0 deletions testdata/testjson7.0.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
type reqName struct {
Action string `json:"action"`
Count int `json:"count"`
Data map[string]string `json:"data"`
Duration int `json:"duration"`
entities interface{} `json:"json:entities"`
Timestamp int `json:"timestamp"`
URI string `json:"uri"`
}
11 changes: 11 additions & 0 deletions testdata/testjson8.0.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
type reqName struct {
Action string `json:"action"`
Count int `json:"count"`
Data struct {
Data map[string]string `json:"data"`
} `json:"data"`
Duration int `json:"duration"`
entities interface{} `json:"json:entities"`
Timestamp int `json:"timestamp"`
URI string `json:"uri"`
}
13 changes: 13 additions & 0 deletions testdata/testjson8.1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
type reqName struct {
Action string `json:"action"`
Count int `json:"count"`
Data Data `json:"data"`
Duration int `json:"duration"`
entities interface{} `json:"json:entities"`
Timestamp int `json:"timestamp"`
URI string `json:"uri"`
}

type Data struct {
Data map[string]string `json:"data"`
}

0 comments on commit b7d7eb0

Please sign in to comment.