-
Notifications
You must be signed in to change notification settings - Fork 0
/
message-marshal.go
233 lines (186 loc) · 8.86 KB
/
message-marshal.go
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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
// Copyright © 2024 Aperture Robotics, LLC.
// Copyright © 2021 The Things Industries B.V.
// SPDX-License-Identifier: Apache-2.0
package json
import (
"github.com/aperturerobotics/protobuf-go-lite/compiler/protogen"
"google.golang.org/protobuf/reflect/protoreflect"
)
func (g *jsonGenerator) genMessageMarshaler(message *protogen.Message) {
g.P("// MarshalProtoJSON marshals the ", message.GoIdent, " message to JSON.")
g.P("func (x *", message.GoIdent, ") MarshalProtoJSON(s *", jsonPluginPackage.Ident("MarshalState"), ") {")
g.P("if x == nil {")
g.P("s.WriteNil()")
g.P("return")
g.P("}")
g.P("s.WriteObjectStart()")
// If the message doesn't have any fields, there's nothing to do.
if len(message.Fields) == 0 {
g.P("s.WriteObjectEnd()")
g.P("}") // end func (x *{message.GoIdent}) MarshalProtoJSON()
g.P()
return
}
// wroteField keeps track of whether we wrote a field, so that we know when to add a comma before the next.
g.P("var wroteField bool")
nextField:
for _, field := range message.Fields {
var (
fieldGoName interface{} = fieldGoName(field)
nullable = fieldIsNullable(field)
fieldJsonName = field.Desc.JSONName()
)
if field.Desc.IsMap() {
// If the field is a map, the field type is a MapEntry message.
// In the MapEntry message, the first field is the key, and the second field is the value.
key := field.Message.Fields[0]
value := field.Message.Fields[1]
// We emit the field if the map is not nil
g.P("if x.", fieldGoName, ` != nil || s.HasField("`, fieldJsonName, `") {`)
// Write a comma if this isn't the first field.
g.P("s.WriteMoreIf(&wroteField)")
// Write the field name and a colon.
g.P(`s.WriteObjectField("`, fieldJsonName, `")`)
g.P("s.WriteObjectStart()")
// wroteElement keeps track of whether we wrote an element of the map, so that we know when to add a comma before the next.
g.P("var wroteElement bool")
g.P("for k, v := range x.", fieldGoName, " {")
// Write a comma if this isn't the first element of the map.
g.P("s.WriteMoreIf(&wroteElement)")
// Write the key and a a colon. Since they key can be of other types than string, we use the library to convert those.
g.P("s.WriteObject", g.libNameForField(key), "Field(k)")
switch value.Desc.Kind() {
default:
// Scalar types can be written by the library.
g.P("s.Write", g.libNameForField(value), "(v)")
case protoreflect.EnumKind:
g.P("v.MarshalProtoJSON(s)")
case protoreflect.MessageKind:
g.P(`v.MarshalProtoJSON(s.WithField("`, fieldJsonName, `"))`)
}
g.P("}") // end for k, v := range x.{fieldGoName} {
g.P("s.WriteObjectEnd()")
g.P("}") // end if x.{fieldGoName} != nil {
continue nextField
}
if field.Desc.IsList() {
// We emit the field if the list is not empty or if it's specified in the field mask.
g.P("if len(x.", fieldGoName, `) > 0 || s.HasField("`, fieldJsonName, `") {`)
// Write a comma if this isn't the first field.
g.P("s.WriteMoreIf(&wroteField)")
// Write the field name and a colon.
g.P(`s.WriteObjectField("`, fieldJsonName, `")`)
switch field.Desc.Kind() {
default:
g.P("s.Write", g.libNameForField(field), "Array(x.", fieldGoName, ")")
case protoreflect.EnumKind:
g.P("s.WriteArrayStart()")
// wroteElement keeps track of whether we wrote an element of the list, so that we know when to add a comma before the next.
g.P("var wroteElement bool")
g.P("for _, element := range x.", fieldGoName, " {")
// Write a comma if this isn't the first element of the list.
g.P("s.WriteMoreIf(&wroteElement)")
g.P("element.MarshalProtoJSON(s)")
g.P("}") // end for _, element := range x.{fieldGoName} {
g.P("s.WriteArrayEnd()")
case protoreflect.MessageKind:
g.P("s.WriteArrayStart()")
// wroteElement keeps track of whether we wrote an element of the list, so that we know when to add a comma before the next.
g.P("var wroteElement bool")
g.P("for _, element := range x.", fieldGoName, " {")
// Write a comma if this isn't the first element of the list.
g.P("s.WriteMoreIf(&wroteElement)")
// If the list element is of type message, and the message has a marshaler, use that.
g.P(`element.MarshalProtoJSON(s.WithField("`, fieldJsonName, `"))`)
// Otherwise delegate to the library.
// g.P("// NOTE: ", field.Message.GoIdent.GoName, " does not seem to implement MarshalProtoJSON.")
// g.P(jsonPluginPackage.Ident("MarshalMessage"), "(s, ", ifThenElse(nullable, "", "&"), "element)")
g.P("}") // end for _, element := range x.{fieldGoName} {
g.P("s.WriteArrayEnd()")
}
g.P("}") // end if len(x.{fieldGoName}) > 0 {
continue nextField
}
// The identifier of the message is x, but in case of a oneof, we'll be operating on ov.
messageOrOneofIdent := "x"
// If this is the first field in a oneof, write the if statement that checks for nil
// and start the switch statement for the oneof type.
if field.Oneof != nil && field == field.Oneof.Fields[0] {
// NOTE: we don't support field masks here (yet).
g.P("if x.", field.Oneof.GoName, " != nil {")
g.P("switch ov := x.", field.Oneof.GoName, ".(type) {")
}
if field.Oneof != nil {
// If we're in a oneof, check if this is the field that's set in the oneof.
g.P("case *", field.GoIdent.GoName, ":")
messageOrOneofIdent = "ov"
} else {
// If we're not in a oneof, start "if not zero value".
if nullable {
// If this field is nullable, we emit it if it's not nil or if it's specified in the field mask.
g.P("if ", messageOrOneofIdent, ".", fieldGoName, ` != nil || s.HasField("`, fieldJsonName, `") {`)
} else {
// If this field is not nullable, we emit it if it's not the zero value or if it's specified in the field mask.
switch field.Desc.Kind() {
case protoreflect.BoolKind:
g.P("if ", messageOrOneofIdent, ".", fieldGoName, ` || s.HasField("`, fieldJsonName, `") {`)
case protoreflect.EnumKind:
g.P("if ", messageOrOneofIdent, ".", fieldGoName, ` != 0 || s.HasField("`, fieldJsonName, `") {`)
case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind,
protoreflect.Uint32Kind, protoreflect.Fixed32Kind,
protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind,
protoreflect.Uint64Kind, protoreflect.Fixed64Kind,
protoreflect.FloatKind,
protoreflect.DoubleKind:
g.P("if ", messageOrOneofIdent, ".", fieldGoName, ` != 0 || s.HasField("`, fieldJsonName, `") {`)
case protoreflect.StringKind:
g.P("if ", messageOrOneofIdent, ".", fieldGoName, ` != "" || s.HasField("`, fieldJsonName, `") {`)
case protoreflect.BytesKind:
g.P("if len(", messageOrOneofIdent, ".", fieldGoName, `) > 0 || s.HasField("`, fieldJsonName, `") {`)
case protoreflect.MessageKind:
// For not-nullable messages we have a dummy check.
g.P("if true { ")
}
}
}
// Write a comma if this isn't the first field.
g.P("s.WriteMoreIf(&wroteField)")
// Write the field name and a colon.
g.P(`s.WriteObjectField("`, fieldJsonName, `")`)
switch field.Desc.Kind() {
default:
// Scalar types can be written by the library.
g.P("s.Write", g.libNameForField(field), "(", messageOrOneofIdent, ".", fieldGoName, ")")
case protoreflect.EnumKind:
// If the field is of type enum, and the enum has a marshaler, use that.
g.P(messageOrOneofIdent, ".", fieldGoName, ".MarshalProtoJSON(s)")
// Otherwise we write the enum with the standard settings.
// g.P("s.WriteEnum(int32(", messageOrOneofIdent, ".", fieldGoName, "), ", field.Enum.GoIdent, "_name)")
case protoreflect.MessageKind:
// If the field is of type message, and the message has a marshaler, use that.
g.P(messageOrOneofIdent, ".", fieldGoName, `.MarshalProtoJSON(s.WithField("`, fieldJsonName, `"))`)
// Otherwise delegate to the library.
// g.P("// NOTE: ", field.Message.GoIdent.GoName, " does not seem to implement MarshalProtoJSON.")
// g.P(jsonPluginPackage.Ident("MarshalMessage"), "(s, ", ifThenElse(nullable, "", "&"), messageOrOneofIdent, ".", fieldGoName, ")")
}
// If we're not in a oneof, end the "if not zero".
if field.Oneof == nil {
g.P("}") // end if x.{field.GoName} != zero value {
}
// If this is the last field in the oneof, close the switch and if statements.
if field.Oneof != nil && field == field.Oneof.Fields[len(field.Oneof.Fields)-1] {
g.P("}") // end switch v := x.{field.Oneof.GoName}.(type) {
g.P("}") // end if x.{field.Oneof.GoName} != nil {
}
}
g.P("s.WriteObjectEnd()")
g.P("}") // end func (x *{message.GoIdent}) MarshalProtoJSON()
g.P()
}
func (g *jsonGenerator) genStdMessageMarshaler(message *protogen.Message) {
g.P("// MarshalJSON marshals the ", message.GoIdent, " to JSON.")
g.P("func (x *", message.GoIdent, ") MarshalJSON() ([]byte, error) {")
g.P("return ", jsonPluginPackage.Ident("DefaultMarshalerConfig"), ".Marshal(x)")
g.P("}")
g.P()
}