Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions cmd/json2go/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ var (
mapType bool
help bool
tagKeys stringArr
stringTag bool
)

func init() {
Expand All @@ -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() {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
68 changes: 49 additions & 19 deletions json2go.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"io"
"reflect"
"sort"
"strconv"
"strings"
"sync"
"unicode"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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))
Expand Down Expand Up @@ -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()
}
Expand Down Expand Up @@ -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
}
22 changes: 20 additions & 2 deletions json2go_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}