JSONEncoder.swift 14 KB

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