[mirror] convert json to go types olexsmir.xyz/json2go
at main 3.4 kB view raw
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}