-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathjson.go
More file actions
175 lines (155 loc) · 4.28 KB
/
json.go
File metadata and controls
175 lines (155 loc) · 4.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
package gobspect
import (
"encoding/base64"
"encoding/json"
"fmt"
)
// ToJSON serializes a Value as a discriminated-union JSON object. Every node
// carries a "kind" field with the concrete type name in lowercase snake_case.
// See the package documentation for the full field mapping per kind.
func ToJSON(v Value) ([]byte, error) {
m, err := valueToJSONMap(v)
if err != nil {
return nil, err
}
return json.Marshal(m)
}
// ToJSONIndent is like [ToJSON] but applies indentation. prefix and indent
// follow the same semantics as [encoding/json.MarshalIndent].
func ToJSONIndent(v Value, prefix, indent string) ([]byte, error) {
m, err := valueToJSONMap(v)
if err != nil {
return nil, err
}
return json.MarshalIndent(m, prefix, indent)
}
// valueToJSONMap converts a Value to a map[string]any suitable for JSON encoding.
func valueToJSONMap(v Value) (map[string]any, error) {
switch v := v.(type) {
case BoolValue:
return map[string]any{"kind": "bool", "v": v.V}, nil
case IntValue:
return map[string]any{"kind": "int", "v": v.V}, nil
case UintValue:
return map[string]any{"kind": "uint", "v": v.V}, nil
case FloatValue:
return map[string]any{"kind": "float", "v": v.V}, nil
case ComplexValue:
return map[string]any{"kind": "complex", "real": v.Real, "imag": v.Imag}, nil
case StringValue:
return map[string]any{"kind": "string", "v": v.V}, nil
case BytesValue:
return map[string]any{
"kind": "bytes",
"v": base64.StdEncoding.EncodeToString(v.V),
"encoding": "base64",
}, nil
case NilValue:
return map[string]any{"kind": "nil"}, nil
case InterfaceValue:
inner, err := valueToJSONMap(v.Value)
if err != nil {
return nil, fmt.Errorf("interface value: %w", err)
}
return map[string]any{
"kind": "interface",
"typeName": v.TypeName,
"value": inner,
}, nil
case OpaqueValue:
return map[string]any{
"kind": "opaque",
"typeName": v.TypeName,
"typeId": v.GobTypeID,
"encoding": v.Encoding,
"raw": base64.StdEncoding.EncodeToString(v.Raw),
"decoded": normalizeOpaqueDecoded(v.Decoded),
}, nil
case StructValue:
fields := make([]map[string]any, 0, len(v.Fields))
for _, f := range v.Fields {
fv, err := valueToJSONMap(f.Value)
if err != nil {
return nil, fmt.Errorf("struct field %q: %w", f.Name, err)
}
fields = append(fields, map[string]any{"name": f.Name, "value": fv})
}
return map[string]any{
"kind": "struct",
"typeName": v.TypeName,
"typeId": v.GobTypeID,
"fields": fields,
}, nil
case MapValue:
entries := make([]map[string]any, 0, len(v.Entries))
for _, e := range v.Entries {
k, err := valueToJSONMap(e.Key)
if err != nil {
return nil, fmt.Errorf("map key: %w", err)
}
val, err := valueToJSONMap(e.Value)
if err != nil {
return nil, fmt.Errorf("map value: %w", err)
}
entries = append(entries, map[string]any{"key": k, "value": val})
}
return map[string]any{
"kind": "map",
"typeName": v.TypeName,
"typeId": v.GobTypeID,
"keyType": v.KeyType,
"elemType": v.ElemType,
"entries": entries,
}, nil
case SliceValue:
elems, err := valuesToJSONMaps(v.Elems)
if err != nil {
return nil, err
}
return map[string]any{
"kind": "slice",
"typeName": v.TypeName,
"typeId": v.GobTypeID,
"elemType": v.ElemType,
"elems": elems,
}, nil
case ArrayValue:
elems, err := valuesToJSONMaps(v.Elems)
if err != nil {
return nil, err
}
return map[string]any{
"kind": "array",
"typeName": v.TypeName,
"typeId": v.GobTypeID,
"elemType": v.ElemType,
"len": v.Len,
"elems": elems,
}, nil
default:
return nil, fmt.Errorf("unknown Value type %T", v)
}
}
func valuesToJSONMaps(vals []Value) ([]map[string]any, error) {
result := make([]map[string]any, 0, len(vals))
for _, v := range vals {
m, err := valueToJSONMap(v)
if err != nil {
return nil, err
}
result = append(result, m)
}
return result, nil
}
// normalizeOpaqueDecoded ensures d is a JSON-safe value. If d cannot be
// marshaled by encoding/json (e.g., a *big.Int that slipped past a decoder),
// it is converted to its fmt.Sprint string form.
func normalizeOpaqueDecoded(d any) any {
if d == nil {
return nil
}
if _, err := json.Marshal(d); err != nil {
return fmt.Sprint(d)
}
return d
}