[mirror] convert json to go types
olexsmir.xyz/json2go
1package json2go
2
3import (
4 "encoding/json"
5 "errors"
6 "fmt"
7 "regexp"
8 "sort"
9 "strings"
10)
11
12var identRe = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
13var (
14 ErrInvalidJSON = errors.New("invalid json")
15 ErrInvalidStructName = errors.New("invalid struct name")
16)
17
18type Transformer struct {
19 structName string
20 currentIndent int
21}
22
23func NewTransformer() *Transformer {
24 return &Transformer{}
25}
26
27// Transform transforms provided json string into go type annotation
28func (t *Transformer) Transform(structName, jsonStr string) (string, error) {
29 if !identRe.MatchString(structName) {
30 return "", ErrInvalidStructName
31 }
32
33 t.structName = structName
34 t.currentIndent = 0
35
36 var input any
37 if err := json.Unmarshal([]byte(jsonStr), &input); err != nil {
38 return "", errors.Join(ErrInvalidJSON, err)
39 }
40
41 type_ := t.getTypeAnnotation(structName, input)
42 return type_, nil
43}
44
45func (t *Transformer) getTypeAnnotation(typeName string, input any) string {
46 switch v := input.(type) {
47 case map[string]any:
48 return fmt.Sprintf("type %s %s", typeName, t.buildStruct(v))
49
50 case []any:
51 if len(v) == 0 {
52 return fmt.Sprintf("type %s []any", typeName)
53 }
54
55 type_ := t.getGoType(typeName+"Item", v[0])
56 return fmt.Sprintf("type %s []%s", typeName, type_)
57
58 case string:
59 return fmt.Sprintf("type %s string", typeName)
60
61 case float64:
62 if float64(int(v)) == v {
63 return fmt.Sprintf("type %s int", typeName)
64 }
65 return fmt.Sprintf("type %s float64", typeName)
66
67 case bool:
68 return fmt.Sprintf("type %s bool", typeName)
69
70 default:
71 return fmt.Sprintf("type %s any", typeName)
72
73 }
74}
75
76func (t *Transformer) buildStruct(input map[string]any) string {
77 var fields strings.Builder
78 for _, f := range mapToStructInput(input) {
79 fieldName := t.toGoFieldName(f.field)
80 if fieldName == "" {
81 fieldName = "NotNamedField"
82 f.field = "NotNamedField"
83 }
84
85 // increase indentation in case of building new struct
86 t.currentIndent++
87 fieldType := t.getGoType(fieldName, f.type_)
88 t.currentIndent--
89
90 jsonTag := fmt.Sprintf("`json:\"%s\"`", f.field)
91
92 indent := strings.Repeat("\t", t.currentIndent+1)
93 fields.WriteString(fmt.Sprintf(
94 "%s%s %s %s\n",
95 indent,
96 fieldName,
97 fieldType,
98 jsonTag,
99 ))
100 }
101
102 return fmt.Sprintf("struct {\n%s%s}",
103 fields.String(),
104 strings.Repeat("\t", t.currentIndent))
105}
106
107func (t *Transformer) getGoType(fieldName string, value any) string {
108 switch v := value.(type) {
109 case map[string]any:
110 return t.buildStruct(v)
111
112 case []any:
113 if len(v) == 0 {
114 return "[]any"
115 }
116
117 type_ := t.getGoType(fieldName, v[0])
118 return "[]" + type_
119
120 case float64:
121 if float64(int(v)) == v {
122 return "int"
123 }
124 return "float64"
125
126 case string:
127 return "string"
128
129 case bool:
130 return "bool"
131
132 default:
133 return "any"
134 }
135}
136
137func (t *Transformer) toGoFieldName(jsonField string) string {
138 parts := strings.Split(jsonField, "_")
139
140 var result strings.Builder
141 for _, part := range parts {
142 if part != "" {
143 if len(part) > 0 {
144 result.WriteString(strings.ToUpper(part[:1]) + part[1:])
145 }
146 }
147 }
148
149 return result.String()
150}
151
152type structInput struct {
153 field string
154 type_ any
155}
156
157func mapToStructInput(input map[string]any) []structInput {
158 res := make([]structInput, 0, len(input))
159 for k, v := range input {
160 res = append(res, structInput{k, v})
161 }
162
163 sort.Slice(res, func(i, j int) bool {
164 return res[i].field < res[j].field
165 })
166
167 return res
168}