From 749ad3e848b719119c40800a4868b258d494a229 Mon Sep 17 00:00:00 2001 From: mvh Date: Tue, 2 May 2023 01:35:11 +0200 Subject: [PATCH] Transform JSON to Go structs --- .gitignore | 2 + go.mod | 3 + main.go | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 194 insertions(+) create mode 100644 .gitignore create mode 100644 go.mod create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8be04af --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +structify +test.json diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5ef4b39 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.snorba.art/max/structify + +go 1.18 diff --git a/main.go b/main.go new file mode 100644 index 0000000..fe86f3f --- /dev/null +++ b/main.go @@ -0,0 +1,189 @@ +package main + +import ( + "bufio" + "encoding/json" + "fmt" + "go/format" + "io" + "log" + "os" + "strings" +) + +type StructField interface { + GetName() string + GetType() string + GetJSONTag() string + String() string +} + +type StructType struct { + JSONField string + Name string + Children []StructField +} + +func (s *StructType) GetJSONTag() string { + return fmt.Sprintf("`json:\"%s\"`", s.JSONField) +} + +func (s *StructType) String() string { + var children []string + for _, v := range s.Children { + children = append(children, v.String()) + } + + return fmt.Sprintf( + "%s struct {\n%s\n}", + s.Name, + strings.Join(children, "\n"), + ) +} + +func (s *StructType) GetType() string { + return "struct" +} + +func (s *StructType) GetName() string { + return s.Name +} + +type SimpleType struct { + JSONField string + Name string + Type string +} + +func (s *SimpleType) GetJSONTag() string { + return fmt.Sprintf("`json:\"%s\"`", s.JSONField) +} + +func (s *SimpleType) String() string { + return fmt.Sprintf("%s %s", s.Name, s.Type) +} + +func (s *SimpleType) GetName() string { + return s.Name +} + +func (s *SimpleType) GetType() string { + return s.Type +} + +func (s *StructType) AddChild(name string, value json.RawMessage) { + var i interface{} + json.Unmarshal(value, &i) + switch v := i.(type) { + case float64: + s.addChild(ParseNumberField(name, value)) + case string, bool: + s.addChild(ParseSimpleField(name, fmt.Sprintf("%T", v))) + case []interface{}: + // To Do: parse slices + s.addChild(ParseSliceField(name, value)) + case map[string]interface{}: + s.addChild(ParseStruct(name, value)) + default: + fmt.Printf("%s %s %T\n", name, "dunno", v) + } +} + +func (s *StructType) addChild(field StructField) { + s.Children = append(s.Children, field) +} + +func ParseSliceField(name string, value json.RawMessage) *SimpleType { + var s []interface{} + err := json.Unmarshal(value, &s) + if err != nil { + log.Fatal(err) + } + + if len(s) == 0 { + return &SimpleType{JSONField: name, Name: SanitizeName(name), Type: "[]interface{}"} + } + + sliceType := fmt.Sprintf("%T", s[0]) + for _, v := range s { + if sliceType != fmt.Sprintf("%T", v) { + return &SimpleType{JSONField: name, Name: SanitizeName(name), Type: "[]interface{}"} + } + } + + return &SimpleType{JSONField: name, Name: SanitizeName(name), Type: fmt.Sprintf("[]%s", sliceType)} +} + +func ParseNumberField(name string, value json.RawMessage) *SimpleType { + for _, b := range value { + if b == '.' { + return &SimpleType{JSONField: name, Name: SanitizeName(name), Type: "float64"} + } + } + return &SimpleType{JSONField: name, Name: SanitizeName(name), Type: "int"} +} + +func ParseSimpleField(name, gotype string) *SimpleType { + return &SimpleType{JSONField: name, Name: SanitizeName(name), Type: gotype} +} + +func ParseStruct(name string, data []byte) *StructType { + jsonMap := make(map[string]json.RawMessage) + err := json.Unmarshal(data, &jsonMap) + if err != nil { + log.Fatal(err) + } + + s := &StructType{JSONField: name, Name: SanitizeName(name)} + + for key, value := range jsonMap { + s.AddChild(key, value) + } + return s +} + +func SanitizeName(name string) string { + return strings.ReplaceAll(strings.Title(strings.ReplaceAll(name, "_", "-")), "-", "") +} + +func FormatStructType(s *StructType, inlineTypes bool) string { + var lines []string + var types []string + lines = append(lines, fmt.Sprintf("type %s struct {", s.Name)) + for _, c := range s.Children { + switch v := c.(type) { + case *SimpleType: + lines = append(lines, fmt.Sprintf("%s %s", c.String(), c.GetJSONTag())) + case *StructType: + if inlineTypes { + lines = append(lines, fmt.Sprintf("%s %s", c.String(), c.GetJSONTag())) + continue + } + lines = append(lines, fmt.Sprintf("%s %s %s", c.GetName(), c.GetName(), c.GetJSONTag())) + types = append(types, FormatStructType(v, inlineTypes)) + } + } + + lines = append(lines, "}") + lines = append(lines, types...) + + formatted, err := format.Source([]byte(strings.Join(lines, "\n"))) + if err != nil { + log.Fatal(err) + } + + return string(formatted) +} + +func main() { + log.Default().SetOutput(os.Stderr) + reader := bufio.NewReader(os.Stdin) + buf, err := io.ReadAll(reader) + if err != nil { + log.Fatal(err) + } + + s := ParseStruct("AutoGenerated", buf) + + fmt.Println(FormatStructType(s, false)) +}