diff --git a/README.md b/README.md index 20f4a99..bbf8495 100644 --- a/README.md +++ b/README.md @@ -30,18 +30,19 @@ json2go generate --url="https://gorest.co.in/public/v2/users" ### CLI Arguments -| Argument | Example | Type | Purpose | Default | -| ---------------- | ---------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| File name | `--file=example.json` | `string` | Valid path to local file on disk | -| URL | `--url="https://example.com` | `string` | Valid URL that JSON can be fetched via GET request from. | -| Root Object Name | `--root=RootObject` | `string` | Name for top-level object in JSON payload | `Root` | -| Package Name | `--package=api` | `string` | Name of package to generate types into. A nested package path is valid | `main` | -| 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` | -| 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` | -| Omit Empty | `--omitempty` | `bool` | Appends the omitempty to all object variable tags. | `false` | -| Debug logging | `--debug` | `bool` | Will output debugging console logs. | `false` | -| Quiet | `--quiet` | `bool` | Will quiet fatal errors. | `false` | -| STDOUT | `--out` | `bool` | Instead of generating a Go file, will instead print the contents to STDOUT | `false` | +| Argument | Example | Type | Purpose | Default | +| ------------------ | ---------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| File name | `--file=example.json` | `string` | Valid path to local file on disk | +| URL | `--url="https://example.com` | `string` | Valid URL that JSON can be fetched via GET request from. | +| Root Object Name | `--root=RootObject` | `string` | Name for top-level object in JSON payload | `Root` | +| Package Name | `--package=api` | `string` | Name of package to generate types into. A nested package path is valid | `main` | +| 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` | +| 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` | +| Omit Empty | `--omitempty` | `bool` | Appends the omitempty to all object variable tags. | `false` | +| Alphabetical Order | `--alpha` | `bool` | Sorts all keys into alphabetical order before generation. | `false` | +| Debug logging | `--debug` | `bool` | Will output debugging console logs. | `false` | +| Quiet | `--quiet` | `bool` | Will quiet fatal errors. | `false` | +| STDOUT | `--out` | `bool` | Instead of generating a Go file, will instead print the contents to STDOUT | `false` | ### Local Development diff --git a/cmd/generate.go b/cmd/generate.go index 5f0d2a4..62e2164 100644 --- a/cmd/generate.go +++ b/cmd/generate.go @@ -10,16 +10,17 @@ import ( ) const ( - urlFlag = "url" - fileFlag = "file" - rootFlag = "root" - packageFlag = "package" - outputFileFlag = "output" - debugFlag = "debug" - quietFlag = "quiet" - stdoutFlag = "out" - timeFormatFlag = "time" - omitEmptyFlag = "omitempty" + urlFlag = "url" + fileFlag = "file" + rootFlag = "root" + packageFlag = "package" + outputFileFlag = "output" + debugFlag = "debug" + quietFlag = "quiet" + stdoutFlag = "out" + timeFormatFlag = "time" + omitEmptyFlag = "omitempty" + alphabeticalFlag = "alphabetical" ) var generateFlags = []cli.Flag{ @@ -62,6 +63,11 @@ var generateFlags = []cli.Flag{ Name: omitEmptyFlag, Usage: "Appends the omitempty to all object variable tags.", }, + &cli.BoolFlag{ + Name: alphabeticalFlag, + Aliases: []string{"a", "alpha"}, + Usage: "Sorts all keys into alphabetical order before generation.", + }, &cli.BoolFlag{ Name: debugFlag, Usage: "Log debug messages.", @@ -93,6 +99,7 @@ func generateTypes(ctx *cli.Context) (err error) { OutputFileName: ctx.String(outputFileFlag), TimeFormat: ctx.String(timeFormatFlag), OmitEmpty: ctx.Bool(omitEmptyFlag), + Alphabetical: ctx.Bool(alphabeticalFlag), } if ctx.Bool(stdoutFlag) { diff --git a/gen/config.go b/gen/config.go index ebb8f3b..0643be3 100644 --- a/gen/config.go +++ b/gen/config.go @@ -20,6 +20,7 @@ type Config struct { OutputFileName string TimeFormat string OmitEmpty bool + Alphabetical bool } func (c *Config) toJensharedConfig() *jenshared.Config { @@ -39,6 +40,7 @@ func (c *Config) toJensharedConfig() *jenshared.Config { OutputDirectory: dir, TimeFormat: c.getTimeFormat(), OmitEmpty: c.OmitEmpty, + Alphabetical: c.Alphabetical, Debugger: c.Debugger, } } diff --git a/jenshared/addStructs.go b/jenshared/addStructs.go index e400301..e1f723e 100644 --- a/jenshared/addStructs.go +++ b/jenshared/addStructs.go @@ -1,35 +1,53 @@ package jenshared import ( + "sort" + "github.com/dave/jennifer/jen" ) -func addStructs(f *jen.File, itemMap TypeItemsMap) { - for name, items := range itemMap { - f.Add(createStruct(name, items)) +func addStructs(f *jen.File, itemMap TypeItemsMap, config *Config) { + keys := make([]string, 0, len(itemMap)) + for key := range itemMap { + keys = append(keys, key) + } + + if config.Alphabetical { + sort.Strings(keys) + } + + for _, key := range keys { + f.Add(createStruct(key, itemMap[key], config)) f.Line() } } -func addStruct(f *jen.File, name string, items TypeItems) { - f.Add(createStruct(name, items)) +func addStruct(f *jen.File, name string, items TypeItems, config *Config) { + f.Add(createStruct(name, items, config)) } -func createStruct(name string, items TypeItems) *jen.Statement { +func createStruct(name string, items TypeItems, config *Config) *jen.Statement { if len(items) == 1 && name == items[0].Name { return jen.Type().Id(name).Id(items[0].Type) } - structItems := createStructItems(items) + structItems := createStructItems(items, config) return jen.Type().Id(name).Struct(structItems...) } -func createStructItems(items TypeItems) []jen.Code { +func createStructItems(items TypeItems, config *Config) []jen.Code { structItems := make([]jen.Code, 0) + if config.Alphabetical { + sort.Slice(items, func(i, ii int) bool { + return items[i].Title() < items[ii].Title() + }) + } + for _, item := range items { + item.OmitEmpty = config.OmitEmpty structItems = append(structItems, createStructItem(item)) } diff --git a/jenshared/addStructsFromJSON.go b/jenshared/addStructsFromJSON.go index 3b6dbc4..43a041c 100644 --- a/jenshared/addStructsFromJSON.go +++ b/jenshared/addStructsFromJSON.go @@ -11,7 +11,7 @@ import ( func addStructsFromJSON(f *jen.File, data interface{}, config *Config) { typeItemsMap := createTypeItemsMapFromJSON(data, config) - addStructs(f, typeItemsMap) + addStructs(f, typeItemsMap, config) } func createTypeItemsMapFromJSON(data interface{}, config *Config) TypeItemsMap { @@ -23,7 +23,7 @@ func createTypeItemsMapFromJSON(data interface{}, config *Config) TypeItemsMap { func parseInterface(items TypeItemsMap, data interface{}, config *Config) TypeItemsMap { switch concreteVal := data.(type) { case bool, float64, string: - items[config.RootName] = TypeItems{{Name: config.RootName, Type: inferDataType(concreteVal, config), OmitEmpty: config.OmitEmpty}} + items[config.RootName] = TypeItems{{Name: config.RootName, Type: inferDataType(concreteVal, config)}} case map[string]interface{}: parseMap(items, concreteVal, config.RootName, config) case []interface{}: @@ -37,10 +37,10 @@ func diveTopLevelArray(items TypeItemsMap, data []interface{}, config *Config, a if len(data) > 0 { switch firstVal := data[0].(type) { case bool, float64, string: - items[config.RootName] = TypeItems{{Name: config.RootName, Type: fmt.Sprintf("%s%s", acc, inferDataType(firstVal, config)), OmitEmpty: config.OmitEmpty}} + items[config.RootName] = TypeItems{{Name: config.RootName, Type: fmt.Sprintf("%s%s", acc, inferDataType(firstVal, config))}} case map[string]interface{}: arrTitle := fmt.Sprintf("%sArray", config.RootName) - items[arrTitle] = TypeItems{{Name: arrTitle, Type: fmt.Sprintf("%s%s", acc, config.RootName), OmitEmpty: config.OmitEmpty}} + items[arrTitle] = TypeItems{{Name: arrTitle, Type: fmt.Sprintf("%s%s", acc, config.RootName)}} parseMap(items, firstVal, config.RootName, config) case []interface{}: @@ -60,10 +60,10 @@ func parseMap(items TypeItemsMap, data map[string]interface{}, parent string, co items[parent] = append(items[parent], TypeItem{Name: key, Type: title}) parseMap(items, concreteVal, title, config) case []interface{}: - items[parent] = append(items[parent], TypeItem{Name: key, Type: fmt.Sprintf("[]%s", title), OmitEmpty: config.OmitEmpty}) + items[parent] = append(items[parent], TypeItem{Name: key, Type: fmt.Sprintf("[]%s", title)}) parseFirstIndexArray(items, concreteVal, title, config) default: - items[parent] = append(items[parent], TypeItem{Name: key, Type: inferDataType(concreteVal, config), OmitEmpty: config.OmitEmpty}) + items[parent] = append(items[parent], TypeItem{Name: key, Type: inferDataType(concreteVal, config)}) } } return items diff --git a/jenshared/types.go b/jenshared/types.go index e1843f7..c479d8f 100644 --- a/jenshared/types.go +++ b/jenshared/types.go @@ -15,6 +15,7 @@ type Config struct { OutputDirectory string TimeFormat string OmitEmpty bool + Alphabetical bool Debugger *log.Logger }