Skip to content

Commit 59a6212

Browse files
committed
Add option to include fields that are equal to the default value in the JSON encoding
1 parent 4ac8a2e commit 59a6212

File tree

7 files changed

+79
-45
lines changed

7 files changed

+79
-45
lines changed

Sources/SwiftProtobuf/JSONEncodingOptions.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ public struct JSONEncodingOptions: Sendable {
4040
/// by keys in lexographical order. This is an implementation detail
4141
/// and subject to change.
4242
public var useDeterministicOrdering: Bool = false
43+
44+
/// Include fields that are equal to the default value in the JSON encoding.
45+
/// This only applies to non-optional fields. Optional fields that are unset will still be
46+
/// omitted from encoded JSON.
47+
public var includeDefaultValues: Bool = false
4348

4449
public init() {}
4550
}

Sources/SwiftProtobuf/JSONEncodingVisitor.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ internal struct JSONEncodingVisitor: Visitor {
2121
private var nameMap: _NameMap
2222
private var extensions: ExtensionFieldValueSet?
2323
private let options: JSONEncodingOptions
24+
let traversalOptions: TraversalOptions
2425

2526
/// The JSON text produced by the visitor, as raw UTF8 bytes.
2627
var dataResult: [UInt8] {
@@ -41,6 +42,7 @@ internal struct JSONEncodingVisitor: Visitor {
4142
throw JSONEncodingError.missingFieldNames
4243
}
4344
self.options = options
45+
traversalOptions = TraversalOptions(visitDefaultValues: options.includeDefaultValues)
4446
}
4547

4648
mutating func startArray() {
@@ -143,7 +145,6 @@ internal struct JSONEncodingVisitor: Visitor {
143145
fieldNumber: Int,
144146
encode: (inout JSONEncoder, T) throws -> ()
145147
) throws {
146-
assert(!value.isEmpty)
147148
try startField(for: fieldNumber)
148149
var comma = false
149150
encoder.startArray()
@@ -318,7 +319,6 @@ internal struct JSONEncodingVisitor: Visitor {
318319
}
319320

320321
mutating func visitRepeatedMessageField<M: Message>(value: [M], fieldNumber: Int) throws {
321-
assert(!value.isEmpty)
322322
try startField(for: fieldNumber)
323323
var comma = false
324324
encoder.startArray()
@@ -351,7 +351,6 @@ internal struct JSONEncodingVisitor: Visitor {
351351
}
352352

353353
mutating func visitRepeatedGroupField<G: Message>(value: [G], fieldNumber: Int) throws {
354-
assert(!value.isEmpty)
355354
// Google does not serialize groups into JSON
356355
}
357356

Sources/SwiftProtobuf/Visitor.swift

Lines changed: 21 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import Foundation
3131
/// used for serialization. It is implemented by each serialization protocol:
3232
/// Protobuf Binary, Protobuf Text, JSON, and the Hash encoder.
3333
public protocol Visitor {
34+
35+
var traversalOptions: TraversalOptions { get }
3436

3537
/// Called for each non-repeated float field
3638
///
@@ -445,8 +447,27 @@ public protocol Visitor {
445447
mutating func visitUnknown(bytes: Data) throws
446448
}
447449

450+
/// Provides options for how visitor traversal should be carried out
451+
public struct TraversalOptions {
452+
static let `default` = TraversalOptions()
453+
454+
/// Determines if non-optional fields that are equal to their default values should be visited.
455+
/// Defaults to `false`.
456+
public var visitDefaultValues: Bool
457+
458+
public init(visitDefaultValues: Bool = false) {
459+
self.visitDefaultValues = visitDefaultValues
460+
}
461+
}
462+
463+
448464
/// Forwarding default implementations of some visitor methods, for convenience.
449465
extension Visitor {
466+
467+
// Use the default traversal options if not set
468+
public var traversalOptions: TraversalOptions {
469+
.default
470+
}
450471

451472
// Default definitions of numeric serializations.
452473
//
@@ -492,126 +513,108 @@ extension Visitor {
492513
// repeated values differently from singular, so overrides these.
493514

494515
public mutating func visitRepeatedFloatField(value: [Float], fieldNumber: Int) throws {
495-
assert(!value.isEmpty)
496516
for v in value {
497517
try visitSingularFloatField(value: v, fieldNumber: fieldNumber)
498518
}
499519
}
500520

501521
public mutating func visitRepeatedDoubleField(value: [Double], fieldNumber: Int) throws {
502-
assert(!value.isEmpty)
503522
for v in value {
504523
try visitSingularDoubleField(value: v, fieldNumber: fieldNumber)
505524
}
506525
}
507526

508527
public mutating func visitRepeatedInt32Field(value: [Int32], fieldNumber: Int) throws {
509-
assert(!value.isEmpty)
510528
for v in value {
511529
try visitSingularInt32Field(value: v, fieldNumber: fieldNumber)
512530
}
513531
}
514532

515533
public mutating func visitRepeatedInt64Field(value: [Int64], fieldNumber: Int) throws {
516-
assert(!value.isEmpty)
517534
for v in value {
518535
try visitSingularInt64Field(value: v, fieldNumber: fieldNumber)
519536
}
520537
}
521538

522539
public mutating func visitRepeatedUInt32Field(value: [UInt32], fieldNumber: Int) throws {
523-
assert(!value.isEmpty)
524540
for v in value {
525541
try visitSingularUInt32Field(value: v, fieldNumber: fieldNumber)
526542
}
527543
}
528544

529545
public mutating func visitRepeatedUInt64Field(value: [UInt64], fieldNumber: Int) throws {
530-
assert(!value.isEmpty)
531546
for v in value {
532547
try visitSingularUInt64Field(value: v, fieldNumber: fieldNumber)
533548
}
534549
}
535550

536551
public mutating func visitRepeatedSInt32Field(value: [Int32], fieldNumber: Int) throws {
537-
assert(!value.isEmpty)
538552
for v in value {
539553
try visitSingularSInt32Field(value: v, fieldNumber: fieldNumber)
540554
}
541555
}
542556

543557
public mutating func visitRepeatedSInt64Field(value: [Int64], fieldNumber: Int) throws {
544-
assert(!value.isEmpty)
545558
for v in value {
546559
try visitSingularSInt64Field(value: v, fieldNumber: fieldNumber)
547560
}
548561
}
549562

550563
public mutating func visitRepeatedFixed32Field(value: [UInt32], fieldNumber: Int) throws {
551-
assert(!value.isEmpty)
552564
for v in value {
553565
try visitSingularFixed32Field(value: v, fieldNumber: fieldNumber)
554566
}
555567
}
556568

557569
public mutating func visitRepeatedFixed64Field(value: [UInt64], fieldNumber: Int) throws {
558-
assert(!value.isEmpty)
559570
for v in value {
560571
try visitSingularFixed64Field(value: v, fieldNumber: fieldNumber)
561572
}
562573
}
563574

564575
public mutating func visitRepeatedSFixed32Field(value: [Int32], fieldNumber: Int) throws {
565-
assert(!value.isEmpty)
566576
for v in value {
567577
try visitSingularSFixed32Field(value: v, fieldNumber: fieldNumber)
568578
}
569579
}
570580

571581
public mutating func visitRepeatedSFixed64Field(value: [Int64], fieldNumber: Int) throws {
572-
assert(!value.isEmpty)
573582
for v in value {
574583
try visitSingularSFixed64Field(value: v, fieldNumber: fieldNumber)
575584
}
576585
}
577586

578587
public mutating func visitRepeatedBoolField(value: [Bool], fieldNumber: Int) throws {
579-
assert(!value.isEmpty)
580588
for v in value {
581589
try visitSingularBoolField(value: v, fieldNumber: fieldNumber)
582590
}
583591
}
584592

585593
public mutating func visitRepeatedStringField(value: [String], fieldNumber: Int) throws {
586-
assert(!value.isEmpty)
587594
for v in value {
588595
try visitSingularStringField(value: v, fieldNumber: fieldNumber)
589596
}
590597
}
591598

592599
public mutating func visitRepeatedBytesField(value: [Data], fieldNumber: Int) throws {
593-
assert(!value.isEmpty)
594600
for v in value {
595601
try visitSingularBytesField(value: v, fieldNumber: fieldNumber)
596602
}
597603
}
598604

599605
public mutating func visitRepeatedEnumField<E: Enum>(value: [E], fieldNumber: Int) throws {
600-
assert(!value.isEmpty)
601606
for v in value {
602607
try visitSingularEnumField(value: v, fieldNumber: fieldNumber)
603608
}
604609
}
605610

606611
public mutating func visitRepeatedMessageField<M: Message>(value: [M], fieldNumber: Int) throws {
607-
assert(!value.isEmpty)
608612
for v in value {
609613
try visitSingularMessageField(value: v, fieldNumber: fieldNumber)
610614
}
611615
}
612616

613617
public mutating func visitRepeatedGroupField<G: Message>(value: [G], fieldNumber: Int) throws {
614-
assert(!value.isEmpty)
615618
for v in value {
616619
try visitSingularGroupField(value: v, fieldNumber: fieldNumber)
617620
}
@@ -623,73 +626,59 @@ extension Visitor {
623626
// overridden by Protobuf Binary and Text.
624627

625628
public mutating func visitPackedFloatField(value: [Float], fieldNumber: Int) throws {
626-
assert(!value.isEmpty)
627629
try visitRepeatedFloatField(value: value, fieldNumber: fieldNumber)
628630
}
629631

630632
public mutating func visitPackedDoubleField(value: [Double], fieldNumber: Int) throws {
631-
assert(!value.isEmpty)
632633
try visitRepeatedDoubleField(value: value, fieldNumber: fieldNumber)
633634
}
634635

635636
public mutating func visitPackedInt32Field(value: [Int32], fieldNumber: Int) throws {
636-
assert(!value.isEmpty)
637637
try visitRepeatedInt32Field(value: value, fieldNumber: fieldNumber)
638638
}
639639

640640
public mutating func visitPackedInt64Field(value: [Int64], fieldNumber: Int) throws {
641-
assert(!value.isEmpty)
642641
try visitRepeatedInt64Field(value: value, fieldNumber: fieldNumber)
643642
}
644643

645644
public mutating func visitPackedUInt32Field(value: [UInt32], fieldNumber: Int) throws {
646-
assert(!value.isEmpty)
647645
try visitRepeatedUInt32Field(value: value, fieldNumber: fieldNumber)
648646
}
649647

650648
public mutating func visitPackedUInt64Field(value: [UInt64], fieldNumber: Int) throws {
651-
assert(!value.isEmpty)
652649
try visitRepeatedUInt64Field(value: value, fieldNumber: fieldNumber)
653650
}
654651

655652
public mutating func visitPackedSInt32Field(value: [Int32], fieldNumber: Int) throws {
656-
assert(!value.isEmpty)
657653
try visitPackedInt32Field(value: value, fieldNumber: fieldNumber)
658654
}
659655

660656
public mutating func visitPackedSInt64Field(value: [Int64], fieldNumber: Int) throws {
661-
assert(!value.isEmpty)
662657
try visitPackedInt64Field(value: value, fieldNumber: fieldNumber)
663658
}
664659

665660
public mutating func visitPackedFixed32Field(value: [UInt32], fieldNumber: Int) throws {
666-
assert(!value.isEmpty)
667661
try visitPackedUInt32Field(value: value, fieldNumber: fieldNumber)
668662
}
669663

670664
public mutating func visitPackedFixed64Field(value: [UInt64], fieldNumber: Int) throws {
671-
assert(!value.isEmpty)
672665
try visitPackedUInt64Field(value: value, fieldNumber: fieldNumber)
673666
}
674667

675668
public mutating func visitPackedSFixed32Field(value: [Int32], fieldNumber: Int) throws {
676-
assert(!value.isEmpty)
677669
try visitPackedInt32Field(value: value, fieldNumber: fieldNumber)
678670
}
679671

680672
public mutating func visitPackedSFixed64Field(value: [Int64], fieldNumber: Int) throws {
681-
assert(!value.isEmpty)
682673
try visitPackedInt64Field(value: value, fieldNumber: fieldNumber)
683674
}
684675

685676
public mutating func visitPackedBoolField(value: [Bool], fieldNumber: Int) throws {
686-
assert(!value.isEmpty)
687677
try visitRepeatedBoolField(value: value, fieldNumber: fieldNumber)
688678
}
689679

690680
public mutating func visitPackedEnumField<E: Enum>(value: [E],
691681
fieldNumber: Int) throws {
692-
assert(!value.isEmpty)
693682
try visitRepeatedEnumField(value: value, fieldNumber: fieldNumber)
694683
}
695684

Sources/protoc-gen-swift/FieldGenerator.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import SwiftProtobuf
2121
/// Interface for field generators.
2222
protocol FieldGenerator {
2323
var number: Int { get }
24+
25+
/// If the field uses the `visitDefaultValues` flag in its `generateTraverse` implementation.
26+
var usesDefaultValueFlagForTraversal: Bool { get }
2427

2528
/// Name mapping entry for the field.
2629
var fieldMapNames: String { get }

Sources/protoc-gen-swift/MessageFieldGenerator.swift

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ class MessageFieldGenerator: FieldGeneratorBase, FieldGenerator {
4747
return false
4848
}
4949
}
50+
51+
var usesDefaultValueFlagForTraversal: Bool {
52+
!hasFieldPresence || (!isGroupOrMessage && fieldDescriptor.file.syntax == .proto2)
53+
}
5054

5155
init(descriptor: FieldDescriptor,
5256
generatorOptions: GeneratorOptions,
@@ -194,31 +198,59 @@ class MessageFieldGenerator: FieldGeneratorBase, FieldGenerator {
194198
traitsArg = ""
195199
}
196200

197-
let varName = hasFieldPresence ? "v" : storedProperty
198-
199201
var usesLocals = false
200-
let conditional: String
202+
let conditionals: [(condition: String, varName: String)]
201203
if isRepeated { // Also covers maps
202-
conditional = "!\(varName).isEmpty"
204+
conditionals = [
205+
(condition: "visitDefaultValues || !\(storedProperty).isEmpty", varName: storedProperty)
206+
]
203207
} else if hasFieldPresence {
204-
conditional = "let v = \(storedProperty)"
205208
usesLocals = true
209+
if !isGroupOrMessage, fieldDescriptor.file.syntax == .proto2 {
210+
conditionals = [
211+
(condition: "let v = \(storedProperty)", varName: "v"),
212+
(condition: "visitDefaultValues", varName: swiftName),
213+
]
214+
} else {
215+
conditionals = [
216+
(condition: "let v = \(storedProperty)", varName: "v"),
217+
]
218+
}
206219
} else {
207220
// At this point, the fields would be a primative type, and should only
208221
// be visted if it is the non default value.
209222
switch fieldDescriptor.type {
210223
case .string, .bytes:
211-
conditional = ("!\(varName).isEmpty")
224+
conditionals = [
225+
(condition: "visitDefaultValues || !\(storedProperty).isEmpty", varName: storedProperty)
226+
]
212227
default:
213-
conditional = ("\(varName) != \(swiftDefaultValue)")
228+
conditionals = [
229+
(condition: "visitDefaultValues || \(storedProperty) != \(swiftDefaultValue)", varName: storedProperty)
230+
]
214231
}
215232
}
216233
assert(usesLocals == generateTraverseUsesLocals)
217234
let prefix = usesLocals ? "try { " : ""
218235
let suffix = usesLocals ? " }()" : ""
236+
for (i, conditional) in conditionals.enumerated() {
237+
if i == 0 {
238+
p.print("\(prefix)if \(conditional.condition) {")
239+
} else {
240+
p.print("} else if \(conditional.condition) {")
241+
}
242+
p.printIndented("try visitor.\(visitMethod)(\(traitsArg)value: \(conditional.varName), fieldNumber: \(number))")
243+
if i == conditionals.count - 1 {
244+
p.print("}\(suffix)")
245+
}
219246

220-
p.print("\(prefix)if \(conditional) {")
221-
p.printIndented("try visitor.\(visitMethod)(\(traitsArg)value: \(varName), fieldNumber: \(number))")
222-
p.print("}\(suffix)")
247+
}
248+
// p.print("\(prefix)if \(conditional) {")
249+
// p.printIndented("try visitor.\(visitMethod)(\(traitsArg)value: \(varName), fieldNumber: \(number))")
250+
// if shouldIncludeElseWithDefaultValueVisit {
251+
// p.print("} else {")
252+
// p.printIndented("try visitor.\(visitMethod)(\(traitsArg)value: \(swiftDefaultValue), fieldNumber: \(number))")
253+
// }
254+
// p.print("}\(suffix)")
223255
}
224256
}

Sources/protoc-gen-swift/MessageGenerator.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,12 @@ class MessageGenerator {
322322
/// - Parameter p: The code printer.
323323
private func generateTraverse(printer p: inout CodePrinter) {
324324
p.print("\(visibility)func traverse<V: \(namer.swiftProtobufModulePrefix)Visitor>(visitor: inout V) throws {")
325+
325326
p.withIndentation { p in
327+
if fields.contains(where: { $0.usesDefaultValueFlagForTraversal }) {
328+
p.print("let visitDefaultValues = visitor.traversalOptions.visitDefaultValues")
329+
}
330+
326331
generateWithLifetimeExtension(printer: &p, throws: true) { p in
327332
if let storage = storage {
328333
storage.generatePreTraverse(printer: &p)

0 commit comments

Comments
 (0)