blob: 66b95870e9774e220fe95286d7381d8745c0702c [file] [log] [blame]
khenaidoo106c61a2021-08-11 18:05:46 -04001// Copyright 2019 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package protojson
6
7import (
8 "encoding/base64"
9 "fmt"
10
11 "google.golang.org/protobuf/internal/encoding/json"
12 "google.golang.org/protobuf/internal/encoding/messageset"
13 "google.golang.org/protobuf/internal/errors"
14 "google.golang.org/protobuf/internal/filedesc"
15 "google.golang.org/protobuf/internal/flags"
16 "google.golang.org/protobuf/internal/genid"
17 "google.golang.org/protobuf/internal/order"
18 "google.golang.org/protobuf/internal/pragma"
19 "google.golang.org/protobuf/proto"
20 "google.golang.org/protobuf/reflect/protoreflect"
khenaidoo106c61a2021-08-11 18:05:46 -040021 "google.golang.org/protobuf/reflect/protoregistry"
22)
23
24const defaultIndent = " "
25
26// Format formats the message as a multiline string.
27// This function is only intended for human consumption and ignores errors.
28// Do not depend on the output being stable. It may change over time across
29// different versions of the program.
30func Format(m proto.Message) string {
31 return MarshalOptions{Multiline: true}.Format(m)
32}
33
34// Marshal writes the given proto.Message in JSON format using default options.
35// Do not depend on the output being stable. It may change over time across
36// different versions of the program.
37func Marshal(m proto.Message) ([]byte, error) {
38 return MarshalOptions{}.Marshal(m)
39}
40
41// MarshalOptions is a configurable JSON format marshaler.
42type MarshalOptions struct {
43 pragma.NoUnkeyedLiterals
44
45 // Multiline specifies whether the marshaler should format the output in
46 // indented-form with every textual element on a new line.
47 // If Indent is an empty string, then an arbitrary indent is chosen.
48 Multiline bool
49
50 // Indent specifies the set of indentation characters to use in a multiline
51 // formatted output such that every entry is preceded by Indent and
52 // terminated by a newline. If non-empty, then Multiline is treated as true.
53 // Indent can only be composed of space or tab characters.
54 Indent string
55
56 // AllowPartial allows messages that have missing required fields to marshal
57 // without returning an error. If AllowPartial is false (the default),
58 // Marshal will return error if there are any missing required fields.
59 AllowPartial bool
60
61 // UseProtoNames uses proto field name instead of lowerCamelCase name in JSON
62 // field names.
63 UseProtoNames bool
64
65 // UseEnumNumbers emits enum values as numbers.
66 UseEnumNumbers bool
67
68 // EmitUnpopulated specifies whether to emit unpopulated fields. It does not
69 // emit unpopulated oneof fields or unpopulated extension fields.
70 // The JSON value emitted for unpopulated fields are as follows:
71 // ╔═══════╤════════════════════════════╗
72 // ║ JSON │ Protobuf field ║
73 // ╠═══════╪════════════════════════════╣
74 // ║ false │ proto3 boolean fields ║
75 // ║ 0 │ proto3 numeric fields ║
76 // ║ "" │ proto3 string/bytes fields ║
77 // ║ null │ proto2 scalar fields ║
78 // ║ null │ message fields ║
79 // ║ [] │ list fields ║
80 // ║ {} │ map fields ║
81 // ╚═══════╧════════════════════════════╝
82 EmitUnpopulated bool
83
84 // Resolver is used for looking up types when expanding google.protobuf.Any
85 // messages. If nil, this defaults to using protoregistry.GlobalTypes.
86 Resolver interface {
87 protoregistry.ExtensionTypeResolver
88 protoregistry.MessageTypeResolver
89 }
90}
91
92// Format formats the message as a string.
93// This method is only intended for human consumption and ignores errors.
94// Do not depend on the output being stable. It may change over time across
95// different versions of the program.
96func (o MarshalOptions) Format(m proto.Message) string {
97 if m == nil || !m.ProtoReflect().IsValid() {
98 return "<nil>" // invalid syntax, but okay since this is for debugging
99 }
100 o.AllowPartial = true
101 b, _ := o.Marshal(m)
102 return string(b)
103}
104
105// Marshal marshals the given proto.Message in the JSON format using options in
106// MarshalOptions. Do not depend on the output being stable. It may change over
107// time across different versions of the program.
108func (o MarshalOptions) Marshal(m proto.Message) ([]byte, error) {
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530109 return o.marshal(nil, m)
110}
111
112// MarshalAppend appends the JSON format encoding of m to b,
113// returning the result.
114func (o MarshalOptions) MarshalAppend(b []byte, m proto.Message) ([]byte, error) {
115 return o.marshal(b, m)
khenaidoo106c61a2021-08-11 18:05:46 -0400116}
117
118// marshal is a centralized function that all marshal operations go through.
119// For profiling purposes, avoid changing the name of this function or
120// introducing other code paths for marshal that do not go through this.
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530121func (o MarshalOptions) marshal(b []byte, m proto.Message) ([]byte, error) {
khenaidoo106c61a2021-08-11 18:05:46 -0400122 if o.Multiline && o.Indent == "" {
123 o.Indent = defaultIndent
124 }
125 if o.Resolver == nil {
126 o.Resolver = protoregistry.GlobalTypes
127 }
128
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530129 internalEnc, err := json.NewEncoder(b, o.Indent)
khenaidoo106c61a2021-08-11 18:05:46 -0400130 if err != nil {
131 return nil, err
132 }
133
134 // Treat nil message interface as an empty message,
135 // in which case the output in an empty JSON object.
136 if m == nil {
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530137 return append(b, '{', '}'), nil
khenaidoo106c61a2021-08-11 18:05:46 -0400138 }
139
140 enc := encoder{internalEnc, o}
141 if err := enc.marshalMessage(m.ProtoReflect(), ""); err != nil {
142 return nil, err
143 }
144 if o.AllowPartial {
145 return enc.Bytes(), nil
146 }
147 return enc.Bytes(), proto.CheckInitialized(m)
148}
149
150type encoder struct {
151 *json.Encoder
152 opts MarshalOptions
153}
154
155// typeFieldDesc is a synthetic field descriptor used for the "@type" field.
156var typeFieldDesc = func() protoreflect.FieldDescriptor {
157 var fd filedesc.Field
158 fd.L0.FullName = "@type"
159 fd.L0.Index = -1
160 fd.L1.Cardinality = protoreflect.Optional
161 fd.L1.Kind = protoreflect.StringKind
162 return &fd
163}()
164
165// typeURLFieldRanger wraps a protoreflect.Message and modifies its Range method
166// to additionally iterate over a synthetic field for the type URL.
167type typeURLFieldRanger struct {
168 order.FieldRanger
169 typeURL string
170}
171
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530172func (m typeURLFieldRanger) Range(f func(protoreflect.FieldDescriptor, protoreflect.Value) bool) {
173 if !f(typeFieldDesc, protoreflect.ValueOfString(m.typeURL)) {
khenaidoo106c61a2021-08-11 18:05:46 -0400174 return
175 }
176 m.FieldRanger.Range(f)
177}
178
179// unpopulatedFieldRanger wraps a protoreflect.Message and modifies its Range
180// method to additionally iterate over unpopulated fields.
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530181type unpopulatedFieldRanger struct{ protoreflect.Message }
khenaidoo106c61a2021-08-11 18:05:46 -0400182
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530183func (m unpopulatedFieldRanger) Range(f func(protoreflect.FieldDescriptor, protoreflect.Value) bool) {
khenaidoo106c61a2021-08-11 18:05:46 -0400184 fds := m.Descriptor().Fields()
185 for i := 0; i < fds.Len(); i++ {
186 fd := fds.Get(i)
187 if m.Has(fd) || fd.ContainingOneof() != nil {
188 continue // ignore populated fields and fields within a oneofs
189 }
190
191 v := m.Get(fd)
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530192 isProto2Scalar := fd.Syntax() == protoreflect.Proto2 && fd.Default().IsValid()
193 isSingularMessage := fd.Cardinality() != protoreflect.Repeated && fd.Message() != nil
khenaidoo106c61a2021-08-11 18:05:46 -0400194 if isProto2Scalar || isSingularMessage {
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530195 v = protoreflect.Value{} // use invalid value to emit null
khenaidoo106c61a2021-08-11 18:05:46 -0400196 }
197 if !f(fd, v) {
198 return
199 }
200 }
201 m.Message.Range(f)
202}
203
204// marshalMessage marshals the fields in the given protoreflect.Message.
205// If the typeURL is non-empty, then a synthetic "@type" field is injected
206// containing the URL as the value.
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530207func (e encoder) marshalMessage(m protoreflect.Message, typeURL string) error {
khenaidoo106c61a2021-08-11 18:05:46 -0400208 if !flags.ProtoLegacy && messageset.IsMessageSet(m.Descriptor()) {
209 return errors.New("no support for proto1 MessageSets")
210 }
211
212 if marshal := wellKnownTypeMarshaler(m.Descriptor().FullName()); marshal != nil {
213 return marshal(e, m)
214 }
215
216 e.StartObject()
217 defer e.EndObject()
218
219 var fields order.FieldRanger = m
220 if e.opts.EmitUnpopulated {
221 fields = unpopulatedFieldRanger{m}
222 }
223 if typeURL != "" {
224 fields = typeURLFieldRanger{fields, typeURL}
225 }
226
227 var err error
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530228 order.RangeFields(fields, order.IndexNameFieldOrder, func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
khenaidoo106c61a2021-08-11 18:05:46 -0400229 name := fd.JSONName()
230 if e.opts.UseProtoNames {
231 name = fd.TextName()
232 }
233
234 if err = e.WriteName(name); err != nil {
235 return false
236 }
237 if err = e.marshalValue(v, fd); err != nil {
238 return false
239 }
240 return true
241 })
242 return err
243}
244
245// marshalValue marshals the given protoreflect.Value.
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530246func (e encoder) marshalValue(val protoreflect.Value, fd protoreflect.FieldDescriptor) error {
khenaidoo106c61a2021-08-11 18:05:46 -0400247 switch {
248 case fd.IsList():
249 return e.marshalList(val.List(), fd)
250 case fd.IsMap():
251 return e.marshalMap(val.Map(), fd)
252 default:
253 return e.marshalSingular(val, fd)
254 }
255}
256
257// marshalSingular marshals the given non-repeated field value. This includes
258// all scalar types, enums, messages, and groups.
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530259func (e encoder) marshalSingular(val protoreflect.Value, fd protoreflect.FieldDescriptor) error {
khenaidoo106c61a2021-08-11 18:05:46 -0400260 if !val.IsValid() {
261 e.WriteNull()
262 return nil
263 }
264
265 switch kind := fd.Kind(); kind {
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530266 case protoreflect.BoolKind:
khenaidoo106c61a2021-08-11 18:05:46 -0400267 e.WriteBool(val.Bool())
268
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530269 case protoreflect.StringKind:
khenaidoo106c61a2021-08-11 18:05:46 -0400270 if e.WriteString(val.String()) != nil {
271 return errors.InvalidUTF8(string(fd.FullName()))
272 }
273
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530274 case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
khenaidoo106c61a2021-08-11 18:05:46 -0400275 e.WriteInt(val.Int())
276
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530277 case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
khenaidoo106c61a2021-08-11 18:05:46 -0400278 e.WriteUint(val.Uint())
279
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530280 case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Uint64Kind,
281 protoreflect.Sfixed64Kind, protoreflect.Fixed64Kind:
khenaidoo106c61a2021-08-11 18:05:46 -0400282 // 64-bit integers are written out as JSON string.
283 e.WriteString(val.String())
284
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530285 case protoreflect.FloatKind:
khenaidoo106c61a2021-08-11 18:05:46 -0400286 // Encoder.WriteFloat handles the special numbers NaN and infinites.
287 e.WriteFloat(val.Float(), 32)
288
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530289 case protoreflect.DoubleKind:
khenaidoo106c61a2021-08-11 18:05:46 -0400290 // Encoder.WriteFloat handles the special numbers NaN and infinites.
291 e.WriteFloat(val.Float(), 64)
292
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530293 case protoreflect.BytesKind:
khenaidoo106c61a2021-08-11 18:05:46 -0400294 e.WriteString(base64.StdEncoding.EncodeToString(val.Bytes()))
295
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530296 case protoreflect.EnumKind:
khenaidoo106c61a2021-08-11 18:05:46 -0400297 if fd.Enum().FullName() == genid.NullValue_enum_fullname {
298 e.WriteNull()
299 } else {
300 desc := fd.Enum().Values().ByNumber(val.Enum())
301 if e.opts.UseEnumNumbers || desc == nil {
302 e.WriteInt(int64(val.Enum()))
303 } else {
304 e.WriteString(string(desc.Name()))
305 }
306 }
307
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530308 case protoreflect.MessageKind, protoreflect.GroupKind:
khenaidoo106c61a2021-08-11 18:05:46 -0400309 if err := e.marshalMessage(val.Message(), ""); err != nil {
310 return err
311 }
312
313 default:
314 panic(fmt.Sprintf("%v has unknown kind: %v", fd.FullName(), kind))
315 }
316 return nil
317}
318
319// marshalList marshals the given protoreflect.List.
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530320func (e encoder) marshalList(list protoreflect.List, fd protoreflect.FieldDescriptor) error {
khenaidoo106c61a2021-08-11 18:05:46 -0400321 e.StartArray()
322 defer e.EndArray()
323
324 for i := 0; i < list.Len(); i++ {
325 item := list.Get(i)
326 if err := e.marshalSingular(item, fd); err != nil {
327 return err
328 }
329 }
330 return nil
331}
332
333// marshalMap marshals given protoreflect.Map.
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530334func (e encoder) marshalMap(mmap protoreflect.Map, fd protoreflect.FieldDescriptor) error {
khenaidoo106c61a2021-08-11 18:05:46 -0400335 e.StartObject()
336 defer e.EndObject()
337
338 var err error
Akash Reddy Kankanalac6b6ca12025-06-12 14:26:57 +0530339 order.RangeEntries(mmap, order.GenericKeyOrder, func(k protoreflect.MapKey, v protoreflect.Value) bool {
khenaidoo106c61a2021-08-11 18:05:46 -0400340 if err = e.WriteName(k.String()); err != nil {
341 return false
342 }
343 if err = e.marshalSingular(v, fd.MapValue()); err != nil {
344 return false
345 }
346 return true
347 })
348 return err
349}