Skip to content

Commit 86f3e2b

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

File tree

8 files changed

+98
-38
lines changed

8 files changed

+98
-38
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: 7 additions & 3 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
53+
}
5054

5155
init(descriptor: FieldDescriptor,
5256
generatorOptions: GeneratorOptions,
@@ -199,7 +203,7 @@ class MessageFieldGenerator: FieldGeneratorBase, FieldGenerator {
199203
var usesLocals = false
200204
let conditional: String
201205
if isRepeated { // Also covers maps
202-
conditional = "!\(varName).isEmpty"
206+
conditional = "visitDefaultValues || !\(varName).isEmpty"
203207
} else if hasFieldPresence {
204208
conditional = "let v = \(storedProperty)"
205209
usesLocals = true
@@ -208,9 +212,9 @@ class MessageFieldGenerator: FieldGeneratorBase, FieldGenerator {
208212
// be visted if it is the non default value.
209213
switch fieldDescriptor.type {
210214
case .string, .bytes:
211-
conditional = ("!\(varName).isEmpty")
215+
conditional = ("visitDefaultValues || !\(varName).isEmpty")
212216
default:
213-
conditional = ("\(varName) != \(swiftDefaultValue)")
217+
conditional = ("visitDefaultValues || \(varName) != \(swiftDefaultValue)")
214218
}
215219
}
216220
assert(usesLocals == generateTraverseUsesLocals)

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)

Sources/protoc-gen-swift/OneofGenerator.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class OneofGenerator {
2929
let swiftDefaultValue: String
3030
let protoGenericType: String
3131
let comments: String
32+
let usesDefaultValueFlagForTraversal: Bool = false
3233

3334
var isGroupOrMessage: Bool {
3435
switch fieldDescriptor.type {

0 commit comments

Comments
 (0)