Skip to content

Commit 52f4aa7

Browse files
authored
Merge pull request #7 from alehechka/feature/parse-uuid-time
Parse time and uuid variables
2 parents 268b409 + b379a2e commit 52f4aa7

File tree

9 files changed

+110
-17
lines changed

9 files changed

+110
-17
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ json2go generate --url="https://gorest.co.in/public/v2/users"
3737
| Root Object Name | `--root=RootObject` | `string` | Name for top-level object in JSON payload | `Root` |
3838
| Package Name | `--package=api` | `string` | Name of package to generate types into. A nested package path is valid | `main` |
3939
| Output File Name | `--output` | `string` | The name of the file that is generated. If a file is provided as input, will use matching name unless explicitly provided. The ".go" extension is not required and will be automatically appended. | `types.go` |
40+
| Time Format | `--time=2006-01-02` | `string` | Time format to use while parsing strings for potential time.Time variables. View time.Time constants for possible defaults: https://pkg.go.dev/time#pkg-constants | `RFC3339` |
4041
| Debug logging | `--debug` | `bool` | Will output debugging console logs. | `false` |
4142
| Quiet | `--quiet` | `bool` | Will quiet fatal errors. | `false` |
4243
| STDOUT | `--out` | `bool` | Instead of generating a Go file, will instead print the contents to STDOUT | `false` |

cmd/generate.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cmd
33
import (
44
"fmt"
55
"log"
6+
"time"
67

78
"github.com/alehechka/json2go/gen"
89
"github.com/urfave/cli/v2"
@@ -17,6 +18,7 @@ const (
1718
debugFlag = "debug"
1819
quietFlag = "quiet"
1920
stdoutFlag = "out"
21+
timeFormatFlag = "time"
2022
)
2123

2224
var generateFlags = []cli.Flag{
@@ -49,6 +51,12 @@ var generateFlags = []cli.Flag{
4951
The ".go" extension is not required and will be automatically appended.`,
5052
Value: gen.DefaultOutputFile,
5153
},
54+
&cli.StringFlag{
55+
Name: timeFormatFlag,
56+
Aliases: []string{"t"},
57+
Usage: "Time format to use while parsing strings for potential time.Time variables. View time.Time constants for possible defaults: https://pkg.go.dev/time#pkg-constants",
58+
Value: time.RFC3339,
59+
},
5260
&cli.BoolFlag{
5361
Name: debugFlag,
5462
Usage: "Log debug messages.",
@@ -78,6 +86,7 @@ func generateTypes(ctx *cli.Context) (err error) {
7886
RootName: ctx.String(rootFlag),
7987
PackageName: ctx.String(packageFlag),
8088
OutputFileName: ctx.String(outputFileFlag),
89+
TimeFormat: ctx.String(timeFormatFlag),
8190
}
8291

8392
if ctx.Bool(stdoutFlag) {

gen/config.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"log"
66
"path/filepath"
77
"strings"
8+
"time"
89

910
"github.com/alehechka/json2go/jenshared"
1011
)
@@ -17,6 +18,7 @@ type Config struct {
1718
RootName string
1819
PackageName string
1920
OutputFileName string
21+
TimeFormat string
2022
}
2123

2224
func (c *Config) toJensharedConfig() *jenshared.Config {
@@ -34,6 +36,7 @@ func (c *Config) toJensharedConfig() *jenshared.Config {
3436
PackageName: c.PackageName,
3537
OutputFileName: c.OutputFileName,
3638
OutputDirectory: dir,
39+
TimeFormat: c.getTimeFormat(),
3740
Debugger: c.Debugger,
3841
}
3942
}
@@ -60,3 +63,47 @@ func (c *Config) prepareOutputFileName() {
6063
c.OutputFileName += ".go"
6164
}
6265
}
66+
67+
func (c *Config) getTimeFormat() string {
68+
69+
if len(c.TimeFormat) == 0 {
70+
return time.RFC3339
71+
}
72+
73+
switch c.TimeFormat {
74+
case "Layout":
75+
return time.Layout
76+
case "ANSIC":
77+
return time.ANSIC
78+
case "UnixDate":
79+
return time.UnixDate
80+
case "RubyDate":
81+
return time.RubyDate
82+
case "RFC822":
83+
return time.RFC822
84+
case "RFC822Z":
85+
return time.RFC822Z
86+
case "RFC850":
87+
return time.RFC850
88+
case "RFC1123":
89+
return time.RFC1123
90+
case "RFC1123Z":
91+
return time.RFC1123Z
92+
case "RFC3339":
93+
return time.RFC3339
94+
case "RFC3339Nano":
95+
return time.RFC3339Nano
96+
case "Kitchen":
97+
return time.Kitchen
98+
case "Stamp":
99+
return time.Stamp
100+
case "StampMilli":
101+
return time.StampMilli
102+
case "StampMicro":
103+
return time.StampMicro
104+
case "StampNano":
105+
return time.StampNano
106+
default:
107+
return c.TimeFormat
108+
}
109+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.18
44

55
require (
66
github.com/dave/jennifer v1.5.0
7+
github.com/google/uuid v1.3.0
78
github.com/stretchr/testify v1.7.4
89
github.com/urfave/cli/v2 v2.10.2
910
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ github.com/dave/rebecca v0.9.1/go.mod h1:N6XYdMD/OKw3lkF3ywh8Z6wPGuwNFDNtWYEMFWE
1212
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1313
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
1414
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
15+
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
16+
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
1517
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
1618
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
1719
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=

jenshared/addStructs.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,16 @@ func createStructItems(items TypeItems) []jen.Code {
3737
}
3838

3939
func createStructItem(item TypeItem) jen.Code {
40-
s := jen.Id(item.Title()).Id(item.Type)
40+
s := jen.Id(item.Title())
41+
42+
switch item.Type {
43+
case "time":
44+
s.Qual("time", "Time")
45+
case "uuid":
46+
s.Qual("github.com/google/uuid", "UUID")
47+
default:
48+
s.Id(item.Type)
49+
}
4150

4251
if item.Name != "" {
4352
s.Tag(map[string]string{"json": item.Name})

jenshared/addStructsFromJSON.go

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package jenshared
33
import (
44
"fmt"
55
"strings"
6+
"time"
67

78
"github.com/dave/jennifer/jen"
9+
"github.com/google/uuid"
810
)
911

1012
func addStructsFromJSON(f *jen.File, data interface{}, config *Config) {
@@ -21,9 +23,9 @@ func createTypeItemsMapFromJSON(data interface{}, config *Config) TypeItemsMap {
2123
func parseInterface(items TypeItemsMap, data interface{}, config *Config) TypeItemsMap {
2224
switch concreteVal := data.(type) {
2325
case bool, float64, string:
24-
items[config.RootName] = TypeItems{{Name: config.RootName, Type: inferDataType(concreteVal)}}
26+
items[config.RootName] = TypeItems{{Name: config.RootName, Type: inferDataType(concreteVal, config)}}
2527
case map[string]interface{}:
26-
parseMap(items, concreteVal, config.RootName)
28+
parseMap(items, concreteVal, config.RootName, config)
2729
case []interface{}:
2830
diveTopLevelArray(items, concreteVal, config, "[]")
2931
}
@@ -35,11 +37,11 @@ func diveTopLevelArray(items TypeItemsMap, data []interface{}, config *Config, a
3537
if len(data) > 0 {
3638
switch firstVal := data[0].(type) {
3739
case bool, float64, string:
38-
items[config.RootName] = TypeItems{{Name: config.RootName, Type: fmt.Sprintf("%s%s", acc, inferDataType(firstVal))}}
40+
items[config.RootName] = TypeItems{{Name: config.RootName, Type: fmt.Sprintf("%s%s", acc, inferDataType(firstVal, config))}}
3941
case map[string]interface{}:
4042
arrTitle := fmt.Sprintf("%sArray", config.RootName)
4143
items[arrTitle] = TypeItems{{Name: arrTitle, Type: fmt.Sprintf("%s%s", acc, config.RootName)}}
42-
parseMap(items, firstVal, config.RootName)
44+
parseMap(items, firstVal, config.RootName, config)
4345

4446
case []interface{}:
4547
diveTopLevelArray(items, firstVal, config, fmt.Sprintf("%s[]", acc))
@@ -49,36 +51,36 @@ func diveTopLevelArray(items TypeItemsMap, data []interface{}, config *Config, a
4951
return items
5052
}
5153

52-
func parseMap(items TypeItemsMap, data map[string]interface{}, parent string) TypeItemsMap {
54+
func parseMap(items TypeItemsMap, data map[string]interface{}, parent string, config *Config) TypeItemsMap {
5355
for key, val := range data {
5456
title := strings.Title(key)
5557
switch concreteVal := val.(type) {
5658
case map[string]interface{}:
5759
items[title] = make(TypeItems, 0)
5860
items[parent] = append(items[parent], TypeItem{Name: key, Type: title})
59-
parseMap(items, concreteVal, title)
61+
parseMap(items, concreteVal, title, config)
6062
case []interface{}:
6163
items[parent] = append(items[parent], TypeItem{Name: key, Type: fmt.Sprintf("[]%s", title)})
62-
parseFirstIndexArray(items, concreteVal, title)
64+
parseFirstIndexArray(items, concreteVal, title, config)
6365
default:
64-
items[parent] = append(items[parent], TypeItem{Name: key, Type: inferDataType(concreteVal)})
66+
items[parent] = append(items[parent], TypeItem{Name: key, Type: inferDataType(concreteVal, config)})
6567
}
6668
}
6769
return items
6870
}
6971

70-
func parseFirstIndexArray(items TypeItemsMap, array []interface{}, parent string) TypeItemsMap {
72+
func parseFirstIndexArray(items TypeItemsMap, array []interface{}, parent string, config *Config) TypeItemsMap {
7173
if len(array) > 0 {
7274
switch concreteVal := array[0].(type) {
7375
case map[string]interface{}:
74-
parseMap(items, concreteVal, parent)
76+
parseMap(items, concreteVal, parent, config)
7577
case []interface{}:
7678
InterfaceArrayOuter:
7779
for key, itemArray := range items {
7880
for index, item := range itemArray {
7981
if item.Title() == parent {
8082
items[key][index].Type = fmt.Sprintf("[]%s", item.Type)
81-
parseFirstIndexArray(items, concreteVal, parent)
83+
parseFirstIndexArray(items, concreteVal, parent, config)
8284
break InterfaceArrayOuter
8385
}
8486
}
@@ -88,7 +90,7 @@ func parseFirstIndexArray(items TypeItemsMap, array []interface{}, parent string
8890
for key, itemArray := range items {
8991
for index, item := range itemArray {
9092
if item.Title() == parent && strings.HasSuffix(item.Type, parent) {
91-
items[key][index].Type = fmt.Sprintf("%s%s", strings.TrimSuffix(item.Type, parent), inferDataType(concreteVal))
93+
items[key][index].Type = fmt.Sprintf("%s%s", strings.TrimSuffix(item.Type, parent), inferDataType(concreteVal, config))
9294
break DefaultOuter
9395
}
9496
}
@@ -98,6 +100,15 @@ func parseFirstIndexArray(items TypeItemsMap, array []interface{}, parent string
98100
return items
99101
}
100102

101-
func inferDataType(value interface{}) string {
102-
return fmt.Sprintf("%T", value)
103+
func inferDataType(value interface{}, config *Config) string {
104+
valType := fmt.Sprintf("%T", value)
105+
if valType == "string" {
106+
if _, err := time.Parse(config.TimeFormat, value.(string)); err == nil {
107+
return "time"
108+
}
109+
if _, err := uuid.Parse(value.(string)); err == nil {
110+
return "uuid"
111+
}
112+
}
113+
return valType
103114
}

jenshared/types.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package jenshared
22

33
import (
4+
"fmt"
45
"log"
6+
"regexp"
57
"strings"
68
)
79

@@ -11,6 +13,7 @@ type Config struct {
1113
PackageName string
1214
OutputFileName string
1315
OutputDirectory string
16+
TimeFormat string
1417
Debugger *log.Logger
1518
}
1619

@@ -22,7 +25,14 @@ type TypeItem struct {
2225

2326
// Title converts the JSON name to TitleCase
2427
func (t TypeItem) Title() string {
25-
return strings.Title(t.Name)
28+
str := regexp.MustCompile(`[^a-zA-Z0-9]`).ReplaceAllString(t.Name, "_")
29+
30+
numbers := regexp.MustCompile(`\d`)
31+
if len(str) > 0 && numbers.MatchString(str[0:1]) {
32+
str = fmt.Sprintf("_%s", str[1:])
33+
}
34+
35+
return strings.Title(str)
2636
}
2737

2838
// TypeItems is an array of TypeItem objects

testdata/object.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,8 @@
1818
"nestedBooleanArray": [true, false],
1919
"nestedFloat64Array": [12.34, 43.21],
2020
"deeplyNestedObjectArray": [[{ "thats": "deep" }]],
21-
"deeplyNestedStringArray": [[["hello", "world"]]]
21+
"deeplyNestedStringArray": [[["hello", "world"]]],
22+
"timeString": "2006-01-02",
23+
"uuidString": "5051ec14-ce89-4fcf-985e-99628a373497",
24+
"9%badName%": "this should break stuff"
2225
}

0 commit comments

Comments
 (0)