all repos

json2go @ a1fb55eb960ec5d95dd1606e19a9e1e150f1a1d1

convert json to go type annotations

json2go/transpiler.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
fix!: indentation, 18 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, 0)
27
		}
28
29
	case ObjectValue:
30
		buf.WriteByte(' ')
31
		t.writeInlineStruct(&buf, structName, v.Object, includeTags, 0)
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, depth int) {
42
	switch v.Kind {
43
	case ObjectValue:
44
		t.writeInlineStruct(buf, name, v.Object, includeTags, depth)
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, depth)
53
		}
54
55
	default:
56
		t.writeScalarType(buf, v)
57
	}
58
}
59
60
func (t *Transpiler) writeIndent(buf *strings.Builder, depth int) {
61
	for i := 0; i < depth; i++ {
62
		buf.WriteByte('\t')
63
	}
64
}
65
66
func (t *Transpiler) writeInlineStruct(buf *strings.Builder, name string, fields []Field, includeTags bool, depth int) {
67
	buf.WriteString("struct {\n")
68
	for _, f := range fields {
69
		fieldName := t.sanitizeFieldName(f.K)
70
		t.writeIndent(buf, depth+1)
71
		buf.WriteString(fieldName)
72
		buf.WriteByte(' ')
73
		t.writeInlineType(buf, name+fieldName, f.V, includeTags, depth+1)
74
		if includeTags {
75
			buf.WriteString(" `json:\"")
76
			buf.WriteString(f.K)
77
			buf.WriteString("\"`")
78
		}
79
		buf.WriteByte('\n')
80
	}
81
	t.writeIndent(buf, depth)
82
	buf.WriteByte('}')
83
}
84
85
func (t *Transpiler) writeScalarType(buf *strings.Builder, v Value) {
86
	switch v.Kind {
87
	case StringValue:
88
		buf.WriteString("string")
89
	case NumberValue:
90
		buf.WriteString("int")
91
	case DecimalValue:
92
		buf.WriteString("float64")
93
	case BoolValue:
94
		buf.WriteString("bool")
95
	default:
96
		buf.WriteString("any")
97
	}
98
}
99
100
func (t *Transpiler) sanitizeFieldName(jsonKey string) string {
101
	if jsonKey == "" {
102
		return "Field"
103
	}
104
105
	var result strings.Builder
106
	result.Grow(len(jsonKey))
107
108
	capitalize := true
109
	for _, r := range jsonKey {
110
		if r == '_' {
111
			capitalize = true
112
			continue
113
		}
114
		if capitalize {
115
			result.WriteRune(unicode.ToUpper(r))
116
			capitalize = false
117
		} else {
118
			result.WriteRune(r)
119
		}
120
	}
121
122
	name := result.String()
123
	if name != "" && isValidIdentifier(name) {
124
		return name
125
	}
126
127
	return t.sanitizeInvalidIdentifier(jsonKey)
128
}
129
130
func (t *Transpiler) sanitizeInvalidIdentifier(jsonKey string) string {
131
	var result strings.Builder
132
	for i, r := range jsonKey {
133
		if unicode.IsLetter(r) || r == '_' || (i > 0 && unicode.IsDigit(r)) {
134
			result.WriteRune(r)
135
		} else if i > 0 && r == '-' {
136
			result.WriteRune('_')
137
		}
138
	}
139
140
	name := result.String()
141
	if name == "" || (!unicode.IsLetter(rune(name[0])) && name[0] != '_') {
142
		var b strings.Builder
143
		b.Grow(len(name) + 1)
144
		b.WriteByte('F')
145
		b.WriteString(name)
146
		return b.String()
147
	}
148
149
	return name
150
}