/src/swift-protobuf/Sources/SwiftProtobuf/JSONEncoder.swift
Line | Count | Source (jump to first uncovered line) |
1 | | // Sources/SwiftProtobuf/JSONEncoder.swift - JSON Encoding support |
2 | | // |
3 | | // Copyright (c) 2014 - 2019 Apple Inc. and the project authors |
4 | | // Licensed under Apache License v2.0 with Runtime Library Exception |
5 | | // |
6 | | // See LICENSE.txt for license information: |
7 | | // https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt |
8 | | // |
9 | | // ----------------------------------------------------------------------------- |
10 | | /// |
11 | | /// JSON serialization engine. |
12 | | /// |
13 | | // ----------------------------------------------------------------------------- |
14 | | |
15 | | import Foundation |
16 | | |
17 | | private let asciiZero = UInt8(ascii: "0") |
18 | | private let asciiOne = UInt8(ascii: "1") |
19 | | private let asciiTwo = UInt8(ascii: "2") |
20 | | private let asciiThree = UInt8(ascii: "3") |
21 | | private let asciiFour = UInt8(ascii: "4") |
22 | | private let asciiFive = UInt8(ascii: "5") |
23 | | private let asciiSix = UInt8(ascii: "6") |
24 | | private let asciiSeven = UInt8(ascii: "7") |
25 | | private let asciiEight = UInt8(ascii: "8") |
26 | | private let asciiNine = UInt8(ascii: "9") |
27 | | private let asciiMinus = UInt8(ascii: "-") |
28 | | private let asciiPlus = UInt8(ascii: "+") |
29 | | private let asciiEquals = UInt8(ascii: "=") |
30 | | private let asciiColon = UInt8(ascii: ":") |
31 | | private let asciiComma = UInt8(ascii: ",") |
32 | | private let asciiDoubleQuote = UInt8(ascii: "\"") |
33 | | private let asciiBackslash = UInt8(ascii: "\\") |
34 | | private let asciiForwardSlash = UInt8(ascii: "/") |
35 | | private let asciiOpenSquareBracket = UInt8(ascii: "[") |
36 | | private let asciiCloseSquareBracket = UInt8(ascii: "]") |
37 | | private let asciiOpenCurlyBracket = UInt8(ascii: "{") |
38 | | private let asciiCloseCurlyBracket = UInt8(ascii: "}") |
39 | | private let asciiUpperA = UInt8(ascii: "A") |
40 | | private let asciiUpperB = UInt8(ascii: "B") |
41 | | private let asciiUpperC = UInt8(ascii: "C") |
42 | | private let asciiUpperD = UInt8(ascii: "D") |
43 | | private let asciiUpperE = UInt8(ascii: "E") |
44 | | private let asciiUpperF = UInt8(ascii: "F") |
45 | | private let asciiUpperZ = UInt8(ascii: "Z") |
46 | | private let asciiLowerA = UInt8(ascii: "a") |
47 | | private let asciiLowerZ = UInt8(ascii: "z") |
48 | | |
49 | 2 | private let base64Digits: [UInt8] = { |
50 | 2 | var digits = [UInt8]() |
51 | 2 | digits.append(contentsOf: asciiUpperA...asciiUpperZ) |
52 | 2 | digits.append(contentsOf: asciiLowerA...asciiLowerZ) |
53 | 2 | digits.append(contentsOf: asciiZero...asciiNine) |
54 | 2 | digits.append(asciiPlus) |
55 | 2 | digits.append(asciiForwardSlash) |
56 | 2 | return digits |
57 | 2 | }() |
58 | | |
59 | 2 | private let hexDigits: [UInt8] = { |
60 | 2 | var digits = [UInt8]() |
61 | 2 | digits.append(contentsOf: asciiZero...asciiNine) |
62 | 2 | digits.append(contentsOf: asciiUpperA...asciiUpperF) |
63 | 2 | return digits |
64 | 2 | }() |
65 | | |
66 | | internal struct JSONEncoder { |
67 | 12.6M | private var data = [UInt8]() |
68 | | private var separator: UInt8? |
69 | | |
70 | 12.5M | internal init() {} |
71 | | |
72 | 606k | internal var dataResult: [UInt8] { data } |
73 | | |
74 | | internal var stringResult: String { |
75 | 4.37M | get { |
76 | 4.37M | String(decoding: data, as: UTF8.self) |
77 | 4.37M | } |
78 | | } |
79 | | |
80 | 106k | internal var bytesResult: [UInt8] { data } |
81 | | |
82 | | /// Append a `StaticString` to the JSON text. Because |
83 | | /// `StaticString` is already UTF8 internally, this is faster |
84 | | /// than appending a regular `String`. |
85 | 8.92G | internal mutating func append(staticText: StaticString) { |
86 | 8.92G | let buff = UnsafeBufferPointer(start: staticText.utf8Start, count: staticText.utf8CodeUnitCount) |
87 | 8.92G | data.append(contentsOf: buff) |
88 | 8.92G | } |
89 | | |
90 | | /// Append a `_NameMap.Name` to the JSON text surrounded by quotes. |
91 | | /// As with StaticString above, a `_NameMap.Name` provides pre-converted |
92 | | /// UTF8 bytes, so this is much faster than appending a regular |
93 | | /// `String`. |
94 | 1.38M | internal mutating func appendQuoted(name: _NameMap.Name) { |
95 | 1.38M | data.append(asciiDoubleQuote) |
96 | 1.38M | data.append(contentsOf: name.utf8Buffer) |
97 | 1.38M | data.append(asciiDoubleQuote) |
98 | 1.38M | } |
99 | | |
100 | | /// Append a `String` to the JSON text. |
101 | 19.1M | internal mutating func append(text: String) { |
102 | 19.1M | data.append(contentsOf: text.utf8) |
103 | 19.1M | } |
104 | | |
105 | | /// Append a raw utf8 in a `Data` to the JSON text. |
106 | 0 | internal mutating func append(utf8Data: Data) { |
107 | 0 | data.append(contentsOf: utf8Data) |
108 | 0 | } |
109 | | |
110 | | /// Append a raw utf8 in a `[UInt8]` to the JSON text. |
111 | 426k | internal mutating func append(utf8Bytes: [UInt8]) { |
112 | 426k | data.append(contentsOf: utf8Bytes) |
113 | 426k | } |
114 | | |
115 | | /// Begin a new field whose name is given as a `_NameMap.Name`. |
116 | 313k | internal mutating func startField(name: _NameMap.Name) { |
117 | 313k | if let s = separator { |
118 | 42.5k | data.append(s) |
119 | 313k | } |
120 | 313k | appendQuoted(name: name) |
121 | 313k | data.append(asciiColon) |
122 | 313k | separator = asciiComma |
123 | 313k | } |
124 | | |
125 | | /// Begin a new field whose name is given as a `String`. |
126 | 33.5k | internal mutating func startField(name: String) { |
127 | 33.5k | if let s = separator { |
128 | 25.5k | data.append(s) |
129 | 33.5k | } |
130 | 33.5k | data.append(asciiDoubleQuote) |
131 | 33.5k | // Can avoid overhead of putStringValue, since |
132 | 33.5k | // the JSON field names are always clean ASCII. |
133 | 33.5k | data.append(contentsOf: name.utf8) |
134 | 33.5k | append(staticText: "\":") |
135 | 33.5k | separator = asciiComma |
136 | 33.5k | } |
137 | | |
138 | | /// Begin a new extension field. |
139 | 79.6k | internal mutating func startExtensionField(name: String) { |
140 | 79.6k | if let s = separator { |
141 | 62.9k | data.append(s) |
142 | 79.6k | } |
143 | 79.6k | append(staticText: "\"[") |
144 | 79.6k | data.append(contentsOf: name.utf8) |
145 | 79.6k | append(staticText: "]\":") |
146 | 79.6k | separator = asciiComma |
147 | 79.6k | } |
148 | | |
149 | | /// Append an open square bracket `[` to the JSON. |
150 | 1.33M | internal mutating func startArray() { |
151 | 1.33M | data.append(asciiOpenSquareBracket) |
152 | 1.33M | separator = nil |
153 | 1.33M | } |
154 | | |
155 | | /// Append a close square bracket `]` to the JSON. |
156 | 1.33M | internal mutating func endArray() { |
157 | 1.33M | data.append(asciiCloseSquareBracket) |
158 | 1.33M | separator = asciiComma |
159 | 1.33M | } |
160 | | |
161 | | /// Append a comma `,` to the JSON. |
162 | 1.72M | internal mutating func comma() { |
163 | 1.72M | data.append(asciiComma) |
164 | 1.72M | } |
165 | | |
166 | | /// Append an open curly brace `{` to the JSON. |
167 | | /// Assumes this object is part of an array of objects. |
168 | 352k | internal mutating func startArrayObject() { |
169 | 352k | if let s = separator { |
170 | 344k | data.append(s) |
171 | 352k | } |
172 | 352k | data.append(asciiOpenCurlyBracket) |
173 | 352k | separator = nil |
174 | 352k | } |
175 | | |
176 | | /// Append an open curly brace `{` to the JSON. |
177 | 842k | internal mutating func startObject() { |
178 | 842k | data.append(asciiOpenCurlyBracket) |
179 | 842k | separator = nil |
180 | 842k | } |
181 | | |
182 | | /// Append a close curly brace `}` to the JSON. |
183 | 11.1M | internal mutating func endObject() { |
184 | 11.1M | data.append(asciiCloseCurlyBracket) |
185 | 11.1M | separator = asciiComma |
186 | 11.1M | } |
187 | | |
188 | | /// Write a JSON `null` token to the output. |
189 | 201 | internal mutating func putNullValue() { |
190 | 201 | append(staticText: "null") |
191 | 201 | } |
192 | | |
193 | | /// Append a float value to the output. |
194 | | /// This handles Nan and infinite values by |
195 | | /// writing well-known string values. |
196 | 673k | internal mutating func putFloatValue(value: Float) { |
197 | 673k | if value.isNaN { |
198 | 9.36k | append(staticText: "\"NaN\"") |
199 | 673k | } else if !value.isFinite { |
200 | 52.6k | if value < 0 { |
201 | 32.4k | append(staticText: "\"-Infinity\"") |
202 | 52.6k | } else { |
203 | 20.2k | append(staticText: "\"Infinity\"") |
204 | 52.6k | } |
205 | 673k | } else { |
206 | 620k | data.append(contentsOf: value.debugDescription.utf8) |
207 | 673k | } |
208 | 673k | } |
209 | | |
210 | | /// Append a double value to the output. |
211 | | /// This handles Nan and infinite values by |
212 | | /// writing well-known string values. |
213 | 1.08M | internal mutating func putDoubleValue(value: Double) { |
214 | 1.08M | if value.isNaN { |
215 | 8.52k | append(staticText: "\"NaN\"") |
216 | 1.08M | } else if !value.isFinite { |
217 | 50.2k | if value < 0 { |
218 | 34.0k | append(staticText: "\"-Infinity\"") |
219 | 50.2k | } else { |
220 | 16.2k | append(staticText: "\"Infinity\"") |
221 | 50.2k | } |
222 | 1.08M | } else { |
223 | 1.03M | data.append(contentsOf: value.debugDescription.utf8) |
224 | 1.08M | } |
225 | 1.08M | } |
226 | | |
227 | | /// Append a UInt64 to the output (without quoting). |
228 | 1.42M | private mutating func appendUInt(value: UInt64) { |
229 | 1.42M | if value >= 10 { |
230 | 407k | appendUInt(value: value / 10) |
231 | 1.42M | } |
232 | 1.42M | data.append(asciiZero + UInt8(value % 10)) |
233 | 1.42M | } |
234 | | |
235 | | /// Append an Int64 to the output (without quoting). |
236 | 4.20M | private mutating func appendInt(value: Int64) { |
237 | 4.20M | if value < 0 { |
238 | 562k | data.append(asciiMinus) |
239 | 562k | // This is the twos-complement negation of value, |
240 | 562k | // computed in a way that won't overflow a 64-bit |
241 | 562k | // signed integer. |
242 | 562k | appendUInt(value: 1 + ~UInt64(bitPattern: value)) |
243 | 4.20M | } else { |
244 | 3.63M | appendUInt(value: UInt64(bitPattern: value)) |
245 | 4.20M | } |
246 | 4.20M | } |
247 | | |
248 | | /// Write an Enum as an Int. |
249 | 0 | internal mutating func putEnumInt(value: Int) { |
250 | 0 | appendInt(value: Int64(value)) |
251 | 0 | } |
252 | | |
253 | | /// Write an `Int64` using protobuf JSON quoting conventions. |
254 | 3.50M | internal mutating func putQuotedInt64(value: Int64) { |
255 | 3.50M | data.append(asciiDoubleQuote) |
256 | 3.50M | appendInt(value: value) |
257 | 3.50M | data.append(asciiDoubleQuote) |
258 | 3.50M | } |
259 | | |
260 | 0 | internal mutating func putNonQuotedInt64(value: Int64) { |
261 | 0 | appendInt(value: value) |
262 | 0 | } |
263 | | |
264 | | /// Write an `Int32` with quoting suitable for |
265 | | /// using the value as a map key. |
266 | 25.4k | internal mutating func putQuotedInt32(value: Int32) { |
267 | 25.4k | data.append(asciiDoubleQuote) |
268 | 25.4k | appendInt(value: Int64(value)) |
269 | 25.4k | data.append(asciiDoubleQuote) |
270 | 25.4k | } |
271 | | |
272 | | /// Write an `Int32` in the default format. |
273 | 1.89M | internal mutating func putNonQuotedInt32(value: Int32) { |
274 | 1.89M | appendInt(value: Int64(value)) |
275 | 1.89M | } |
276 | | |
277 | | /// Write a `UInt64` using protobuf JSON quoting conventions. |
278 | 4.15M | internal mutating func putQuotedUInt64(value: UInt64) { |
279 | 4.15M | data.append(asciiDoubleQuote) |
280 | 4.15M | appendUInt(value: value) |
281 | 4.15M | data.append(asciiDoubleQuote) |
282 | 4.15M | } |
283 | | |
284 | 0 | internal mutating func putNonQuotedUInt64(value: UInt64) { |
285 | 0 | appendUInt(value: value) |
286 | 0 | } |
287 | | |
288 | | /// Write a `UInt32` with quoting suitable for |
289 | | /// using the value as a map key. |
290 | 100k | internal mutating func putQuotedUInt32(value: UInt32) { |
291 | 100k | data.append(asciiDoubleQuote) |
292 | 100k | appendUInt(value: UInt64(value)) |
293 | 100k | data.append(asciiDoubleQuote) |
294 | 100k | } |
295 | | |
296 | | /// Write a `UInt32` in the default format. |
297 | 315k | internal mutating func putNonQuotedUInt32(value: UInt32) { |
298 | 315k | appendUInt(value: UInt64(value)) |
299 | 315k | } |
300 | | |
301 | | /// Write a `Bool` with quoting suitable for |
302 | | /// using the value as a map key. |
303 | 2.92k | internal mutating func putQuotedBoolValue(value: Bool) { |
304 | 2.92k | data.append(asciiDoubleQuote) |
305 | 2.92k | putNonQuotedBoolValue(value: value) |
306 | 2.92k | data.append(asciiDoubleQuote) |
307 | 2.92k | } |
308 | | |
309 | | /// Write a `Bool` in the default format. |
310 | 499k | internal mutating func putNonQuotedBoolValue(value: Bool) { |
311 | 499k | if value { |
312 | 254k | append(staticText: "true") |
313 | 499k | } else { |
314 | 245k | append(staticText: "false") |
315 | 499k | } |
316 | 499k | } |
317 | | |
318 | | /// Append a string value escaping special characters as needed. |
319 | 173k | internal mutating func putStringValue(value: String) { |
320 | 173k | data.append(asciiDoubleQuote) |
321 | 142M | for c in value.unicodeScalars { |
322 | 142M | switch c.value { |
323 | 142M | // Special two-byte escapes |
324 | 142M | case 8: append(staticText: "\\b") |
325 | 142M | case 9: append(staticText: "\\t") |
326 | 142M | case 10: append(staticText: "\\n") |
327 | 142M | case 12: append(staticText: "\\f") |
328 | 142M | case 13: append(staticText: "\\r") |
329 | 142M | case 34: append(staticText: "\\\"") |
330 | 142M | case 92: append(staticText: "\\\\") |
331 | 142M | case 0...31, 127...159: // Hex form for C0 control chars |
332 | 138M | append(staticText: "\\u00") |
333 | 138M | data.append(hexDigits[Int(c.value / 16)]) |
334 | 138M | data.append(hexDigits[Int(c.value & 15)]) |
335 | 142M | case 23...126: |
336 | 3.52M | data.append(UInt8(truncatingIfNeeded: c.value)) |
337 | 142M | case 0x80...0x7ff: |
338 | 20.9k | data.append(0xc0 + UInt8(truncatingIfNeeded: c.value >> 6)) |
339 | 20.9k | data.append(0x80 + UInt8(truncatingIfNeeded: c.value & 0x3f)) |
340 | 142M | case 0x800...0xffff: |
341 | 14.4k | data.append(0xe0 + UInt8(truncatingIfNeeded: c.value >> 12)) |
342 | 14.4k | data.append(0x80 + UInt8(truncatingIfNeeded: (c.value >> 6) & 0x3f)) |
343 | 14.4k | data.append(0x80 + UInt8(truncatingIfNeeded: c.value & 0x3f)) |
344 | 142M | default: |
345 | 20.5k | data.append(0xf0 + UInt8(truncatingIfNeeded: c.value >> 18)) |
346 | 20.5k | data.append(0x80 + UInt8(truncatingIfNeeded: (c.value >> 12) & 0x3f)) |
347 | 20.5k | data.append(0x80 + UInt8(truncatingIfNeeded: (c.value >> 6) & 0x3f)) |
348 | 20.5k | data.append(0x80 + UInt8(truncatingIfNeeded: c.value & 0x3f)) |
349 | 142M | } |
350 | 142M | } |
351 | 173k | data.append(asciiDoubleQuote) |
352 | 173k | } |
353 | | |
354 | | /// Append a bytes value using protobuf JSON Base-64 encoding. |
355 | 13.6k | internal mutating func putBytesValue<Bytes: SwiftProtobufContiguousBytes>(value: Bytes) { |
356 | 13.6k | data.append(asciiDoubleQuote) |
357 | 13.6k | if value.count > 0 { |
358 | 10.7k | value.withUnsafeBytes { (body: UnsafeRawBufferPointer) in |
359 | 10.7k | if let p = body.baseAddress, body.count > 0 { |
360 | 10.7k | var t: Int = 0 |
361 | 10.7k | var bytesInGroup: Int = 0 |
362 | 153k | for i in 0..<body.count { |
363 | 153k | if bytesInGroup == 3 { |
364 | 44.4k | data.append(base64Digits[(t >> 18) & 63]) |
365 | 44.4k | data.append(base64Digits[(t >> 12) & 63]) |
366 | 44.4k | data.append(base64Digits[(t >> 6) & 63]) |
367 | 44.4k | data.append(base64Digits[t & 63]) |
368 | 44.4k | t = 0 |
369 | 44.4k | bytesInGroup = 0 |
370 | 153k | } |
371 | 153k | t = (t << 8) + Int(p[i]) |
372 | 153k | bytesInGroup += 1 |
373 | 153k | } |
374 | 10.7k | switch bytesInGroup { |
375 | 10.7k | case 3: |
376 | 3.08k | data.append(base64Digits[(t >> 18) & 63]) |
377 | 3.08k | data.append(base64Digits[(t >> 12) & 63]) |
378 | 3.08k | data.append(base64Digits[(t >> 6) & 63]) |
379 | 3.08k | data.append(base64Digits[t & 63]) |
380 | 10.7k | case 2: |
381 | 3.00k | t <<= 8 |
382 | 3.00k | data.append(base64Digits[(t >> 18) & 63]) |
383 | 3.00k | data.append(base64Digits[(t >> 12) & 63]) |
384 | 3.00k | data.append(base64Digits[(t >> 6) & 63]) |
385 | 3.00k | data.append(asciiEquals) |
386 | 10.7k | case 1: |
387 | 4.69k | t <<= 16 |
388 | 4.69k | data.append(base64Digits[(t >> 18) & 63]) |
389 | 4.69k | data.append(base64Digits[(t >> 12) & 63]) |
390 | 4.69k | data.append(asciiEquals) |
391 | 4.69k | data.append(asciiEquals) |
392 | 10.7k | default: |
393 | 0 | break |
394 | 10.7k | } |
395 | 10.7k | } |
396 | 10.7k | } |
397 | 13.6k | } |
398 | 13.6k | data.append(asciiDoubleQuote) |
399 | 13.6k | } |
400 | | } |