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
34 changes: 34 additions & 0 deletions expected_output_test_tags.file
Original file line number Diff line number Diff line change
@@ -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"`
}
4 changes: 3 additions & 1 deletion gojson/gojson.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import (
"io/ioutil"
"log"
"os"
"strings"

. "github.com/ChimeraCoder/gojson"
)
Expand All @@ -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() {
Expand All @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions json-to-array_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
26 changes: 15 additions & 11 deletions json-to-struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand All @@ -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))
Expand All @@ -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
}
Expand Down Expand Up @@ -327,19 +331,19 @@ 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)
for _, o := range objects {
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{}"
}
Expand Down
38 changes: 33 additions & 5 deletions json-to-struct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,35 @@ 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)
}
}

// 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)
}
}

// 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)
}
}

// 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)
}
}
Expand All @@ -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)
}
Expand Down