Skip to content

Commit ea66560

Browse files
committed
JSON performance: Speed up encoding of submessages
The previous logic created a completely new encoder, encoded the subobject, and then copied the result to append it. The new logic avoids that by traversing the subobject with the current encoder. (This is the same strategy that was already used by the binary encoder.)
1 parent 5c7305a commit ea66560

File tree

2 files changed

+46
-7
lines changed

2 files changed

+46
-7
lines changed

Sources/SwiftProtobuf/JSONEncoder.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,11 @@ internal struct JSONEncoder {
154154
separator = nil
155155
}
156156

157+
internal mutating func startNestedObject() {
158+
data.append(asciiOpenCurlyBracket)
159+
separator = nil
160+
}
161+
157162
/// Append a close curly brace `}` to the JSON.
158163
internal mutating func endObject() {
159164
data.append(asciiCloseCurlyBracket)

Sources/SwiftProtobuf/JSONEncodingVisitor.swift

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,19 @@ internal struct JSONEncodingVisitor: Visitor {
166166

167167
mutating func visitSingularMessageField<M: Message>(value: M, fieldNumber: Int) throws {
168168
try startField(for: fieldNumber)
169-
let json = try value.jsonUTF8Data(options: options)
170-
encoder.append(utf8Data: json)
169+
if let m = value as? _CustomJSONCodable {
170+
let json = try m.encodedJSONString(options: options)
171+
encoder.append(text: json)
172+
} else if let newNameMap = (M.self as? _ProtoNameProviding.Type)?._protobuf_nameMap {
173+
encoder.startNestedObject()
174+
let oldNameMap = self.nameMap
175+
self.nameMap = newNameMap
176+
try value.traverse(visitor: &self)
177+
self.nameMap = oldNameMap
178+
endObject()
179+
} else {
180+
throw JSONEncodingError.missingFieldNames
181+
}
171182
}
172183

173184
mutating func visitSingularGroupField<G: Message>(value: G, fieldNumber: Int) throws {
@@ -274,12 +285,35 @@ internal struct JSONEncodingVisitor: Visitor {
274285
}
275286

276287
mutating func visitRepeatedMessageField<M: Message>(value: [M], fieldNumber: Int) throws {
277-
let localOptions = options
278-
try _visitRepeated(value: value, fieldNumber: fieldNumber) {
279-
(encoder: inout JSONEncoder, v: M) throws in
280-
let json = try v.jsonUTF8Data(options: localOptions)
281-
encoder.append(utf8Data: json)
288+
try startField(for: fieldNumber)
289+
var comma = false
290+
encoder.startArray()
291+
if let _ = M.self as? _CustomJSONCodable.Type {
292+
for v in value {
293+
if comma {
294+
encoder.comma()
295+
}
296+
comma = true
297+
let json = try v.jsonString(options: options)
298+
encoder.append(text: json)
299+
}
300+
} else if let newNameMap = (M.self as? _ProtoNameProviding.Type)?._protobuf_nameMap {
301+
let oldNameMap = self.nameMap
302+
self.nameMap = newNameMap
303+
for v in value {
304+
if comma {
305+
encoder.comma()
306+
}
307+
comma = true
308+
encoder.startNestedObject()
309+
try v.traverse(visitor: &self)
310+
encoder.endObject()
311+
}
312+
self.nameMap = oldNameMap
313+
} else {
314+
throw JSONEncodingError.missingFieldNames
282315
}
316+
encoder.endArray()
283317
}
284318

285319
mutating func visitRepeatedGroupField<G: Message>(value: [G], fieldNumber: Int) throws {

0 commit comments

Comments
 (0)