all repos

json2go @ e64121e

convert json to go type annotations

json2go/transpiler.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
refactor!: inline all nested types, 19 days ago
1
package json2go
2
3
import (
4
	"strings"
5
	"unicode"
6
)
7
8
// Transpiler transpiles AST [Value] to Go type definitions.
9
type Transpiler struct{}
10
11
func NewTranspiler() *Transpiler { return &Transpiler{} }
12
13
// Transpile converts a [Value] AST to Go type definitions.
14
func (t *Transpiler) Transpile(structName string, v Value, includeTags bool) (string, error) {
15
	var buf strings.Builder
16
	buf.WriteString("type ")
17
	buf.WriteString(structName)
18
19
	switch v.Kind {
20
	case ArrayValue:
21
		buf.WriteString(" [")
22
		if len(v.Array) == 0 {
23
			buf.WriteString("]any")
24
		} else {
25
			buf.WriteByte(']')
26
			t.writeInlineType(&buf, structName+"Item", v.Array[0], includeTags)
27
		}
28
29
	case ObjectValue:
30
		buf.WriteByte(' ')
31
		t.writeInlineStruct(&buf, structName, v.Object, includeTags)
32
33
	default:
34
		buf.WriteByte(' ')
35
		t.writeScalarType(&buf, v)
36
	}
37
38
	return buf.String(), nil
39
}
40
41
func (t *Transpiler) writeInlineType(buf *strings.Builder, name string, v Value, includeTags bool) {
42
	switch v.Kind {
43
	case ObjectValue:
44
		t.writeInlineStruct(buf, name, v.Object, includeTags)
45
46
	case ArrayValue:
47
		buf.WriteByte('[')
48
		if len(v.Array) == 0 {
49
			buf.WriteString("]any")
50
		} else {
51
			buf.WriteByte(']')
52
			t.writeInlineType(buf, name+"Item", v.Array[0], includeTags)
53
		}
54
55
	default:
56
		t.writeScalarType(buf, v)
57
	}
58
}
59
60
func (t *Transpiler) writeInlineStruct(buf *strings.Builder, name string, fields []Field, includeTags bool) {
61
	buf.WriteString("struct {\n")
62
	for _, f := range fields {
63
		fieldName := t.sanitizeFieldName(f.K)
64
		buf.WriteByte('\t')
65
		buf.WriteString(fieldName)
66
		buf.WriteByte(' ')
67
		t.writeInlineType(buf, name+fieldName, f.V, includeTags)
68
		if includeTags {
69
			buf.WriteString(" `json:\"")
70
			buf.WriteString(f.K)
71
			buf.WriteString("\"`")
72
		}
73
		buf.WriteByte('\n')
74
	}
75
	buf.WriteByte('}')
76
}
77
78
func (t *Transpiler) writeScalarType(buf *strings.Builder, v Value) {
79
	switch v.Kind {
80
	case StringValue:
81
		buf.WriteString("string")
82
	case NumberValue:
83
		buf.WriteString("int")
84
	case DecimalValue:
85
		buf.WriteString("float64")
86
	case BoolValue:
87
		buf.WriteString("bool")
88
	default:
89
		buf.WriteString("any")
90
	}
91
}
92
93
func (t *Transpiler) sanitizeFieldName(jsonKey string) string {
94
	if jsonKey == "" {
95
		return "Field"
96
	}
97
98
	var result strings.Builder
99
	result.Grow(len(jsonKey))
100
101
	capitalize := true
102
	for _, r := range jsonKey {
103
		if r == '_' {
104
			capitalize = true
105
			continue
106
		}
107
		if capitalize {
108
			result.WriteRune(unicode.ToUpper(r))
109
			capitalize = false
110
		} else {
111
			result.WriteRune(r)
112
		}
113
	}
114
115
	name := result.String()
116
	if name != "" && isValidIdentifier(name) {
117
		return name
118
	}
119
120
	return t.sanitizeInvalidIdentifier(jsonKey)
121
}
122
123
func (t *Transpiler) sanitizeInvalidIdentifier(jsonKey string) string {
124
	var result strings.Builder
125
	for i, r := range jsonKey {
126
		if unicode.IsLetter(r) || r == '_' || (i > 0 && unicode.IsDigit(r)) {
127
			result.WriteRune(r)
128
		} else if i > 0 && r == '-' {
129
			result.WriteRune('_')
130
		}
131
	}
132
133
	name := result.String()
134
	if name == "" || (!unicode.IsLetter(rune(name[0])) && name[0] != '_') {
135
		var b strings.Builder
136
		b.Grow(len(name) + 1)
137
		b.WriteByte('F')
138
		b.WriteString(name)
139
		return b.String()
140
	}
141
142
	return name
143
}