all repos

json2go @ v0.2.0

convert json to go type annotations

json2go/transpiler.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
refactor: use an actual parser instead of reflection..., 11 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
// Nested types are emitted after the parent struct.
15
func (t *Transpiler) Transpile(structName string, v Value, includeTags bool) (string, error) {
16
	var buf strings.Builder
17
	var nested strings.Builder
18
19
	switch v.Kind {
20
	case ArrayValue:
21
		if len(v.Array) == 0 {
22
			buf.WriteString("type ")
23
			buf.WriteString(structName)
24
			buf.WriteString(" []any")
25
		} else {
26
			itemType := t.writeWithNested(&buf, &nested, structName+"Item", v.Array[0], includeTags)
27
			buf.WriteString("type ")
28
			buf.WriteString(structName)
29
			buf.WriteString(" []")
30
			buf.WriteString(itemType)
31
		}
32
33
	case ObjectValue:
34
		t.writeStructDef(&buf, &nested, structName, v.Object, includeTags)
35
36
	default:
37
		scalarType := t.inferType(structName, v)
38
		buf.WriteString("type ")
39
		buf.WriteString(structName)
40
		buf.WriteByte(' ')
41
		buf.WriteString(scalarType)
42
	}
43
44
	if nested.Len() > 0 && buf.Len() > 0 {
45
		buf.WriteString("\n\n")
46
		buf.WriteString(nested.String())
47
	}
48
	return buf.String(), nil
49
}
50
51
func (t *Transpiler) writeWithNested(buf, nested *strings.Builder, name string, v Value, includeTags bool) string {
52
	switch v.Kind {
53
	case ObjectValue:
54
		t.writeStructDef(nested, nested, name, v.Object, includeTags)
55
		return name
56
57
	case ArrayValue:
58
		if len(v.Array) == 0 {
59
			return "[]any"
60
		}
61
		itemType := t.writeWithNested(buf, nested, name+"Item", v.Array[0], includeTags)
62
		return "[]" + itemType
63
	}
64
	return t.inferType(name, v)
65
}
66
67
func (t *Transpiler) writeStructDef(buf, nested *strings.Builder, name string, fields []Field, includeTags bool) {
68
	if buf.Len() > 0 {
69
		buf.WriteString("\n\n")
70
	}
71
72
	buf.WriteString("type ")
73
	buf.WriteString(name)
74
	buf.WriteString(" struct {\n")
75
	for _, f := range fields {
76
		fieldName := t.sanitizeFieldName(f.K)
77
		fieldType := t.writeWithNested(buf, nested, name+fieldName, f.V, includeTags)
78
		if includeTags {
79
			buf.WriteByte('\t')
80
			buf.WriteString(fieldName)
81
			buf.WriteByte(' ')
82
			buf.WriteString(fieldType)
83
			buf.WriteString(" `json:\"")
84
			buf.WriteString(f.K)
85
			buf.WriteString("\"`\n")
86
		} else {
87
			buf.WriteByte('\t')
88
			buf.WriteString(fieldName)
89
			buf.WriteByte(' ')
90
			buf.WriteString(fieldType)
91
			buf.WriteByte('\n')
92
		}
93
	}
94
	buf.WriteString("}")
95
}
96
97
func (t *Transpiler) inferType(name string, v Value) string {
98
	switch v.Kind {
99
	case ObjectValue:
100
		return name
101
	case ArrayValue:
102
		if len(v.Array) == 0 {
103
			return "[]any"
104
		}
105
		itemType := t.inferType(name+"Item", v.Array[0])
106
		return "[]" + itemType
107
	case StringValue:
108
		return "string"
109
	case NumberValue:
110
		return "int"
111
	case DecimalValue:
112
		return "float64"
113
	case BoolValue:
114
		return "bool"
115
	case NullValue:
116
		return "any"
117
	default:
118
		return "any"
119
	}
120
}
121
122
func (t *Transpiler) sanitizeFieldName(jsonKey string) string {
123
	if jsonKey == "" {
124
		return "Field"
125
	}
126
127
	var result strings.Builder
128
	result.Grow(len(jsonKey))
129
130
	capitalize := true
131
	for _, r := range jsonKey {
132
		if r == '_' {
133
			capitalize = true
134
			continue
135
		}
136
		if capitalize {
137
			result.WriteRune(unicode.ToUpper(r))
138
			capitalize = false
139
		} else {
140
			result.WriteRune(r)
141
		}
142
	}
143
144
	name := result.String()
145
	if name != "" && isValidIdentifier(name) {
146
		return name
147
	}
148
149
	return t.sanitizeInvalidIdentifier(jsonKey)
150
}
151
152
func (t *Transpiler) sanitizeInvalidIdentifier(jsonKey string) string {
153
	var result strings.Builder
154
	for i, r := range jsonKey {
155
		if unicode.IsLetter(r) || r == '_' || (i > 0 && unicode.IsDigit(r)) {
156
			result.WriteRune(r)
157
		} else if i > 0 && r == '-' {
158
			result.WriteRune('_')
159
		}
160
	}
161
162
	name := result.String()
163
	if name == "" || (!unicode.IsLetter(rune(name[0])) && name[0] != '_') {
164
		var b strings.Builder
165
		b.Grow(len(name) + 1)
166
		b.WriteByte('F')
167
		b.WriteString(name)
168
		return b.String()
169
	}
170
171
	return name
172
}