[mirror] convert json to go types
olexsmir.xyz/json2go
1package json2go
2
3import (
4 "errors"
5 "fmt"
6 "reflect"
7 "strings"
8 "testing"
9)
10
11func field(indentLvl int, name, type_ string, json_ ...string) string {
12 indent := strings.Repeat("\t", indentLvl)
13 if strings.Contains(type_, "struct") {
14 return fmt.Sprintf("\n%s%s %s", indent, name, type_)
15 }
16
17 tag := strings.ToLower(name)
18 if len(json_) == 1 {
19 tag = json_[0]
20 }
21 return fmt.Sprintf("\n%s%s %s `json:\"%s\"`", indent, name, type_, tag)
22}
23
24func TestTransformer_Transform(t *testing.T) {
25 tests := map[string]struct {
26 input string
27 output string
28 structName string
29 err error
30 }{
31 "simple object": {
32 input: `{"name": "Olex", "active": true, "age": 420}`,
33 output: "type Out struct {" +
34 field(1, "Active", "bool") +
35 field(1, "Age", "int") +
36 field(1, "Name", "string") +
37 "\n}",
38 },
39 "invalid json": {
40 err: ErrInvalidJSON,
41 input: `{"invalid":json}`,
42 },
43 "invalid struct name, starts with number": {
44 err: ErrInvalidStructName,
45 structName: "1Name",
46 },
47 "invalid struct name, has space": {
48 err: ErrInvalidStructName,
49 structName: "Name Name2",
50 },
51 "invalid struct name, has non letter/number": {
52 err: ErrInvalidStructName,
53 structName: "Name$",
54 },
55 "snake_case to CamelCase": {
56 input: `{"first_name": "Bob", "last_name": "Bobberson"}`,
57 output: "type Out struct {" +
58 field(1, "FirstName", "string", "first_name") +
59 field(1, "LastName", "string", "last_name") +
60 "\n}",
61 },
62 "nested object and array": {
63 input: `{"user": {"name": "Alice", "score": 95.5}, "tags": ["go", "json"]}`,
64 output: "type Out struct {" +
65 field(1, "Tags", "[]string") +
66 field(1, "User", "struct {") +
67 field(2, "Name", "string") +
68 field(2, "Score", "float64") +
69 "\n\t} `json:\"user\"`" +
70 "\n}",
71 },
72 "empty nested object": {
73 input: `{"user": {}}`,
74 output: "type Out struct {" +
75 field(1, "User", "struct {") +
76 "\n\t} `json:\"user\"`" +
77 "\n}",
78 },
79 "array of object": {
80 input: `[{"name": "John"}, {"name": "Jane"}]`,
81 output: "type Out []struct {" +
82 field(1, "Name", "string") +
83 "\n}",
84 },
85 "empty array": {
86 input: `{"items": []}`,
87 output: "type Out struct {" +
88 field(1, "Items", "[]any") +
89 "\n}",
90 },
91 "null": {
92 input: `{"item": null}`,
93 output: `type Out struct {` +
94 field(1, "Item", "any") +
95 "\n}",
96 },
97 "numbers": {
98 input: `{"pos": 123, "neg": -321, "float": 420.69}`,
99 output: "type Out struct {" +
100 field(1, "Float", "float64") +
101 field(1, "Neg", "int") +
102 field(1, "Pos", "int") +
103 "\n}",
104 },
105 }
106
107 trans := NewTransformer()
108 for tname, tt := range tests {
109 t.Run(tname, func(t *testing.T) {
110 sn := "Out"
111 if tt.structName != "" {
112 sn = tt.structName
113 }
114
115 result, err := trans.Transform(sn, tt.input)
116 assertEqualErr(t, tt.err, err)
117 assertEqual(t, tt.output, result)
118 })
119 }
120}
121
122func assertEqualErr(t *testing.T, expected, actual error) {
123 t.Helper()
124 if expected == nil && actual == nil {
125 return
126 }
127
128 if expected == nil || actual == nil {
129 t.Errorf("expected: %v, got: %v", expected, actual)
130 return
131 }
132
133 if !errors.Is(actual, expected) {
134 t.Errorf("expected error: %v, got: %v", expected, actual)
135 }
136}
137
138func assertEqual[T any](t *testing.T, expected, actual T) {
139 t.Helper()
140 if !reflect.DeepEqual(expected, actual) {
141 t.Errorf("expected: %v, got: %v\n", expected, actual)
142 }
143}