From 99affab13ecb8522b1bb013990bc85abb6e9a814 Mon Sep 17 00:00:00 2001 From: liyichao Date: Mon, 29 May 2023 17:16:15 +0800 Subject: [PATCH] feat: add string tag & json.Number --- cmd/json2go/main.go | 7 +++++ json2go.go | 68 ++++++++++++++++++++++++++++++++------------- json2go_test.go | 22 +++++++++++++-- 3 files changed, 76 insertions(+), 21 deletions(-) diff --git a/cmd/json2go/main.go b/cmd/json2go/main.go index 652ca82..9b0ee31 100644 --- a/cmd/json2go/main.go +++ b/cmd/json2go/main.go @@ -78,6 +78,7 @@ var ( mapType bool help bool tagKeys stringArr + stringTag bool ) func init() { @@ -101,6 +102,8 @@ func init() { flag.BoolVar(&help, "h", false, "the short flag for -help") flag.Var(&tagKeys, "tagkeys", "additional struct tag keys; can be used more than once") flag.Var(&tagKeys, "t", "the short flag for -tagkeys") + flag.BoolVar(&stringTag, "stringtag", false, "add `,string` after tag if the string type field can be parsed as a Int/Float value.") + flag.BoolVar(&stringTag, "S", false, "the short flag for -guess") } func main() { @@ -196,8 +199,10 @@ func realMain() int { t.SetPkg(pkg) } t.MapType = mapType + t.StringTag = stringTag t.SetStructName(structName) t.SetTagKeys(tagKeys.Get()) + // Generate the Go Types err = t.Gen() if err != nil { @@ -254,6 +259,8 @@ flag default description -h -help false Print the help text; 'help' is also valid. -t -tagkey Additional key to be added to struct tags. For multiple keys, use one per key value. +-S -stringtag false Add ',string' to tags which could be parsed + as a number type. ` fmt.Println(helpText) } diff --git a/json2go.go b/json2go.go index 7e96fb3..83be85f 100644 --- a/json2go.go +++ b/json2go.go @@ -8,6 +8,7 @@ import ( "io" "reflect" "sort" + "strconv" "strings" "sync" "unicode" @@ -63,6 +64,10 @@ type Transmogrifier struct { // // If false, a struct definition will be generated for the type. MapType bool + // StringTag is used to guess whether a string type field can + // be parsed as a Int/Float value. And a `,string`` tag will be + // added to the tail of the tag of that field. + StringTag bool } // NewTransmogrifier returns a new transmogrifier that reads from r and writes @@ -135,7 +140,7 @@ func (t *Transmogrifier) Gen() error { } } var def interface{} - err = json.Unmarshal(buff.Bytes(), &def) + err = unmarshalWithNumber(buff.Bytes(), &def) if err != nil { return err } @@ -195,7 +200,7 @@ func (t *Transmogrifier) Gen() error { DEFINE: go func() { - defineStruct(q, t.tagKeys, result, &wg) + defineStruct(q, t.tagKeys, result, &wg, t.StringTag) }() // collect the results until the resCh is closed for { @@ -252,7 +257,7 @@ func GenMapType(typeName, name string, tagKeys []string, data []byte) ([]byte, e name = strings.Title(name) } var def interface{} - err := json.Unmarshal(data, &def) + err := unmarshalWithNumber(data, &def) if err != nil { return nil, err } @@ -286,7 +291,7 @@ func GenMapType(typeName, name string, tagKeys []string, data []byte) ([]byte, e q.Enqueue(s) // start the worker & send initial work item go func() { - defineStruct(q, tagKeys, result, &wg) + defineStruct(q, tagKeys, result, &wg, false) }() // collect the results until the resCh is closed var i int @@ -302,7 +307,7 @@ func GenMapType(typeName, name string, tagKeys []string, data []byte) ([]byte, e return buff.Bytes(), nil } -func defineStruct(q *queue.Queue, tagKeys []string, result chan []byte, wg *sync.WaitGroup) { +func defineStruct(q *queue.Queue, tagKeys []string, result chan []byte, wg *sync.WaitGroup, stringTag bool) { for { if q.IsEmpty() { break @@ -319,21 +324,26 @@ func defineStruct(q *queue.Queue, tagKeys []string, result chan []byte, wg *sync val := s.val.MapIndex(key) typ := getValueKind(val) // maps are embedded structs - if typ == reflect.Map.String() { + switch typ { + case reflect.Map.String(): tmp := newStructDef(k, val.Elem()) q.Enqueue(tmp) s.buff.WriteString(fmt.Sprintf("\t%s `json:%q`\n", k, tag)) continue - } // a slicemap is a signal that it is a []T which means pluralize // the field name and generate the embedded struct - if typ == "slicemap" { + case "slicemap": tmp := newStructDef(k, val.Elem().Index(0).Elem()) q.Enqueue(tmp) s.buff.WriteString(fmt.Sprintf("\t%ss []%s ", k, k)) s.buff.WriteString(defineFieldTags(tag, tagKeys)) s.buff.WriteRune('\n') continue + case reflect.String.String(): + if strTag := guessStringTag(val.Elem().String()); stringTag && strTag != "" { + tag += ",string" + typ = strTag + } } s.buff.WriteString(fmt.Sprintf("\t%s %s ", k, typ)) s.buff.WriteString(defineFieldTags(tag, tagKeys)) @@ -363,28 +373,32 @@ func getValueKind(val reflect.Value) string { return "interface{}" } switch val.Elem().Type().Kind() { - case reflect.Float64: - v := val.Elem().Float() - if v == float64(int64(v)) { - return reflect.Int.String() - } - return reflect.Float64.String() case reflect.Slice: if val.Elem().Len() == 0 { - return fmt.Sprint("[]interface{}") + return "[]interface{}" } v := val.Elem().Index(0).Elem() switch v.Type().Kind() { - case reflect.Float64: - vv := v.Float() - if vv == float64(int64(vv)) { + case reflect.String: + // use json.Number to determine accurately. + if v.Type().String() == "json.Number" { + if strings.Contains(v.String(), ".") { + return fmt.Sprintf("[]%s", reflect.Float64.String()) + } return fmt.Sprintf("[]%s", reflect.Int.String()) } - return fmt.Sprintf("[]%s", reflect.Float64.String()) case reflect.Map: return "slicemap" } return fmt.Sprintf("[]%s", v.Type().Kind().String()) + case reflect.String: + // use json.Number to determine accurately. + if val.Elem().Type().String() == "json.Number" { + if strings.Contains(val.Elem().String(), ".") { + return reflect.Float64.String() + } + return reflect.Int.String() + } } return val.Elem().Type().Kind().String() } @@ -515,3 +529,19 @@ func toUpperInitialism(s string) string { } return s } + +func unmarshalWithNumber(data []byte, v interface{}) error { + decoder := json.NewDecoder(bytes.NewReader(data)) + decoder.UseNumber() + return decoder.Decode(v) +} + +func guessStringTag(s string) (typeName string) { + if _, err := strconv.ParseInt(s, 10, 64); err == nil { + return reflect.Int.String() + } + if _, err := strconv.ParseFloat(s, 64); err == nil { + return reflect.Float64.String() + } + return +} diff --git a/json2go_test.go b/json2go_test.go index 9c268f9..817d272 100644 --- a/json2go_test.go +++ b/json2go_test.go @@ -444,10 +444,28 @@ func TestEmptySlice(t *testing.T) { var buff bytes.Buffer calvin := NewTransmogrifier("test", r, &buff) err := calvin.Gen() - if err != nil { + if err != nil { + t.Errorf("unexpected error: %s", err) + } + if buff.String() != expected { + t.Errorf("got %q want %q", buff.String(), expected) + } +} + +func TestStringTag(t *testing.T) { + test := `{"aaa":"123","bbb":"123.123","test":9223372036854775807,"test1":"123,123"}` + + expected := "package main\n\ntype Test struct {\n\tAaa int `json:\"aaa,string\"`\n\tBbb float64 `json:\"bbb,string\"`\n\tTest int `json:\"test\"`\n\tTest1 string `json:\"test1\"`\n}\n" + + r := bytes.NewReader([]byte(test)) + var buff bytes.Buffer + calvin := NewTransmogrifier("test", r, &buff) + calvin.StringTag = true + err := calvin.Gen() + if err != nil { t.Errorf("unexpected error: %s", err) } - if buff.String () != expected { + if buff.String() != expected { t.Errorf("got %q want %q", buff.String(), expected) } }