From afcba5b4d88b2d314211010b54e7c3f188ec6b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Guimar=C3=A3es?= Date: Tue, 24 May 2016 09:35:09 -0300 Subject: [PATCH] Able to add more tags to fields. Take a look on the method TestExampleWithMoreThanOneTag at json-to-struct_test.go file to more information --- expected_output_test_tags.file | 34 ++++++++++++++++++++++++++++++ gojson/gojson.go | 4 +++- json-to-array_test.go | 4 ++-- json-to-struct.go | 26 +++++++++++++---------- json-to-struct_test.go | 38 +++++++++++++++++++++++++++++----- 5 files changed, 87 insertions(+), 19 deletions(-) create mode 100644 expected_output_test_tags.file diff --git a/expected_output_test_tags.file b/expected_output_test_tags.file new file mode 100644 index 0000000..cef7439 --- /dev/null +++ b/expected_output_test_tags.file @@ -0,0 +1,34 @@ +package json2struct + +type UserTags struct { + OneNumberKey int `json:"1number_key" bson:"1number_key"` + AvatarURL string `json:"avatar_url" bson:"avatar_url"` + Bio interface{} `json:"bio" bson:"bio"` + Blog string `json:"blog" bson:"blog"` + Company string `json:"company" bson:"company"` + CreatedAt string `json:"created_at" bson:"created_at"` + Email string `json:"email" bson:"email"` + EventsURL string `json:"events_url" bson:"events_url"` + Followers int `json:"followers" bson:"followers"` + FollowersURL string `json:"followers_url" bson:"followers_url"` + Following int `json:"following" bson:"following"` + FollowingURL string `json:"following_url" bson:"following_url"` + GistsURL string `json:"gists_url" bson:"gists_url"` + GravatarID string `json:"gravatar_id" bson:"gravatar_id"` + Hireable bool `json:"hireable" bson:"hireable"` + HTMLURL string `json:"html_url" bson:"html_url"` + ID int `json:"id" bson:"id"` + Location string `json:"location" bson:"location"` + Login string `json:"login" bson:"login"` + Name string `json:"name" bson:"name"` + OrganizationsURL string `json:"organizations_url" bson:"organizations_url"` + PublicGists int `json:"public_gists" bson:"public_gists"` + PublicRepos int `json:"public_repos" bson:"public_repos"` + ReceivedEventsURL string `json:"received_events_url" bson:"received_events_url"` + ReposURL string `json:"repos_url" bson:"repos_url"` + StarredURL string `json:"starred_url" bson:"starred_url"` + SubscriptionsURL string `json:"subscriptions_url" bson:"subscriptions_url"` + Type string `json:"type" bson:"type"` + UpdatedAt string `json:"updated_at" bson:"updated_at"` + URL string `json:"url" bson:"url"` +} diff --git a/gojson/gojson.go b/gojson/gojson.go index 04ba71c..7a6c029 100644 --- a/gojson/gojson.go +++ b/gojson/gojson.go @@ -49,6 +49,7 @@ import ( "io/ioutil" "log" "os" + "strings" . "github.com/ChimeraCoder/gojson" ) @@ -58,6 +59,7 @@ var ( pkg = flag.String("pkg", "main", "the name of the package for the generated code") inputName = flag.String("input", "", "the name of the input file containing JSON (if input not provided via STDIN)") outputName = flag.String("o", "", "the name of the file to write the output to (outputs to STDOUT by default)") + tags = flag.String("t", "json", "tags to put on the fields (comma separated)") ) func main() { @@ -80,7 +82,7 @@ func main() { input = f } - if output, err := Generate(input, *name, *pkg); err != nil { + if output, err := Generate(input, *name, *pkg, strings.Split(*tags, ",")); err != nil { fmt.Fprintln(os.Stderr, "error parsing", err) os.Exit(1) } else { diff --git a/json-to-array_test.go b/json-to-array_test.go index 39328ff..3e2084a 100644 --- a/json-to-array_test.go +++ b/json-to-array_test.go @@ -17,8 +17,8 @@ func TestExampleArray(t *testing.T) { type Users []interface{} ` - - actual, err := Generate(i, "Users", "main") + tags := []string{"json"} + actual, err := Generate(i, "Users", "main", tags) if err != nil { t.Error(err) } diff --git a/json-to-struct.go b/json-to-struct.go index 743c742..8fbf0ad 100644 --- a/json-to-struct.go +++ b/json-to-struct.go @@ -161,7 +161,7 @@ var intToWordMap = []string{ // Given a JSON string representation of an object and a name structName, // attemp to generate a struct definition -func Generate(input io.Reader, structName, pkgName string) ([]byte, error) { +func Generate(input io.Reader, structName, pkgName string, tags []string) ([]byte, error) { var iresult interface{} var result map[string]interface{} if err := json.NewDecoder(input).Decode(&iresult); err != nil { @@ -191,7 +191,7 @@ func Generate(input io.Reader, structName, pkgName string) ([]byte, error) { src := fmt.Sprintf("package %s\ntype %s %s}", pkgName, structName, - generateTypes(result, 0)) + generateTypes(result, 0, tags)) formatted, err := format.Source([]byte(src)) if err != nil { err = fmt.Errorf("error formatting: %s, was formatting\n%s", err, src) @@ -200,7 +200,7 @@ func Generate(input io.Reader, structName, pkgName string) ([]byte, error) { } // Generate go struct entries for a map[string]interface{} structure -func generateTypes(obj map[string]interface{}, depth int) string { +func generateTypes(obj map[string]interface{}, depth int, tags []string) string { structure := "struct {" keys := make([]string, 0, len(obj)) @@ -211,21 +211,25 @@ func generateTypes(obj map[string]interface{}, depth int) string { for _, key := range keys { value := obj[key] - valueType := typeForValue(value) + valueType := typeForValue(value, tags) //If a nested value, recurse switch value := value.(type) { case []map[string]interface{}: - valueType = "[]" + generateTypes(value[0], depth+1) + "}" + valueType = "[]" + generateTypes(value[0], depth+1, tags) + "}" case map[string]interface{}: - valueType = generateTypes(value, depth+1) + "}" + valueType = generateTypes(value, depth+1, tags) + "}" } fieldName := fmtFieldName(stringifyFirstChar(key)) - structure += fmt.Sprintf("\n%s %s `json:\"%s\"`", + var tagString string + for _, tag := range tags { + tagString += fmt.Sprintf("%s:\"%s\" ", tag, key) + } + structure += fmt.Sprintf("\n%s %s `%s`", fieldName, valueType, - key) + tagString[0:len(tagString)-1]) } return structure } @@ -327,7 +331,7 @@ func lintFieldName(name string) string { } // generate an appropriate struct type entry -func typeForValue(value interface{}) string { +func typeForValue(value interface{}, tags []string) string { //Check if this is an array if objects, ok := value.([]interface{}); ok { types := make(map[reflect.Type]bool, 0) @@ -335,11 +339,11 @@ func typeForValue(value interface{}) string { types[reflect.TypeOf(o)] = true } if len(types) == 1 { - return "[]" + typeForValue(objects[0]) + return "[]" + typeForValue(objects[0], tags) } return "[]interface{}" } else if object, ok := value.(map[string]interface{}); ok { - return generateTypes(object, 0) + "}" + return generateTypes(object, 0, tags) + "}" } else if reflect.TypeOf(value) == nil { return "interface{}" } diff --git a/json-to-struct_test.go b/json-to-struct_test.go index fd3b654..def4c8e 100644 --- a/json-to-struct_test.go +++ b/json-to-struct_test.go @@ -11,7 +11,8 @@ import ( // It does not (yet) test for correctness of the end result func TestSimpleJson(t *testing.T) { i := strings.NewReader(`{"foo" : "bar"}`) - if _, err := Generate(i, "TestStruct", "main"); err != nil { + tags := []string{"json"} + if _, err := Generate(i, "TestStruct", "main", tags); err != nil { t.Error("Generate() error:", err) } } @@ -19,7 +20,8 @@ func TestSimpleJson(t *testing.T) { // TestNullableJson tests that a null JSON value is handled properly func TestNullableJson(t *testing.T) { i := strings.NewReader(`{"foo" : "bar", "baz" : null}`) - if _, err := Generate(i, "TestStruct", "main"); err != nil { + tags := []string{"json"} + if _, err := Generate(i, "TestStruct", "main", tags); err != nil { t.Error("Generate() error:", err) } } @@ -27,7 +29,8 @@ func TestNullableJson(t *testing.T) { // TestSimpleArray tests that an array without conflicting types is handled correctly func TestSimpleArray(t *testing.T) { i := strings.NewReader(`{"foo" : [{"bar": 24}, {"bar" : 42}]}`) - if _, err := Generate(i, "TestStruct", "main"); err != nil { + tags := []string{"json"} + if _, err := Generate(i, "TestStruct", "main", tags); err != nil { t.Error("Generate() error:", err) } } @@ -35,7 +38,8 @@ func TestSimpleArray(t *testing.T) { // TestInvalidFieldChars tests that a document with invalid field chars is handled correctly func TestInvalidFieldChars(t *testing.T) { i := strings.NewReader(`{"f.o-o" : 42}`) - if _, err := Generate(i, "TestStruct", "main"); err != nil { + tags := []string{"json"} + if _, err := Generate(i, "TestStruct", "main", tags); err != nil { t.Error("Generate() error:", err) } } @@ -52,7 +56,31 @@ func TestExample(t *testing.T) { t.Error("error reading expected_output_test.go", err) } - actual, err := Generate(i, "User", "json2struct") + tags := []string{"json"} + actual, err := Generate(i, "User", "json2struct", tags) + if err != nil { + t.Error(err) + } + sactual, sexpected := string(actual), string(expected) + if sactual != sexpected { + t.Errorf("'%s' (expected) != '%s' (actual)", sexpected, sactual) + } +} + +// Test example document +func TestExampleWithMoreThanOneTag(t *testing.T) { + i, err := os.Open("example.json") + if err != nil { + t.Error("error opening example.json", err) + } + + expected, err := ioutil.ReadFile("expected_output_test_tags.file") + if err != nil { + t.Error("error reading expected_output_test_tags.file", err) + } + + tags := []string{"json", "bson"} + actual, err := Generate(i, "UserTags", "json2struct", tags) if err != nil { t.Error(err) }