ProtobufJSONEncoding.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. // ProtobufRuntime/Sources/Protobuf/ProtobufJSONEncoding.swift - JSON Encoding support
  2. //
  3. // This source file is part of the Swift.org open source project
  4. //
  5. // Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
  6. // Licensed under Apache License v2.0 with Runtime Library Exception
  7. //
  8. // See http://swift.org/LICENSE.txt for license information
  9. // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
  10. //
  11. // -----------------------------------------------------------------------------
  12. ///
  13. /// JSON serialization engine.
  14. ///
  15. // -----------------------------------------------------------------------------
  16. import Swift
  17. public struct ProtobufJSONEncodingVisitor: ProtobufVisitor {
  18. private var encoder = ProtobufJSONEncoder()
  19. public var result: String {return encoder.result}
  20. public init() {}
  21. public init(message: ProtobufJSONMessageBase) throws {
  22. try withAbstractVisitor {(visitor: inout ProtobufVisitor) in
  23. try message.traverse(visitor: &visitor)
  24. }
  25. }
  26. public init(group: ProtobufGroupBase) throws {
  27. try withAbstractVisitor {(visitor: inout ProtobufVisitor) in
  28. try group.traverse(visitor: &visitor)
  29. }
  30. }
  31. mutating public func withAbstractVisitor(clause: (inout ProtobufVisitor) throws -> ()) throws {
  32. encoder.startObject()
  33. var visitor: ProtobufVisitor = self
  34. try clause(&visitor)
  35. encoder.json = (visitor as! ProtobufJSONEncodingVisitor).encoder.json
  36. encoder.endObject()
  37. }
  38. mutating public func visitUnknown(bytes: [UInt8]) {
  39. // JSON encoding has no provision for carrying proto2 unknown fields
  40. }
  41. mutating public func visitSingularField<S: ProtobufTypeProperties>(fieldType: S.Type, value: S.BaseType, protoFieldNumber: Int, protoFieldName: String, jsonFieldName: String, swiftFieldName: String) throws {
  42. encoder.startField(name: jsonFieldName)
  43. try S.serializeJSONValue(encoder: &encoder, value: value)
  44. }
  45. mutating public func visitRepeatedField<S: ProtobufTypeProperties>(fieldType: S.Type, value: [S.BaseType], protoFieldNumber: Int, protoFieldName: String, jsonFieldName: String, swiftFieldName: String) throws {
  46. encoder.startField(name: jsonFieldName)
  47. var arraySeparator = ""
  48. encoder.append(text: "[")
  49. for v in value {
  50. encoder.append(text: arraySeparator)
  51. try S.serializeJSONValue(encoder: &encoder, value: v)
  52. arraySeparator = ","
  53. }
  54. encoder.append(text: "]")
  55. }
  56. mutating public func visitPackedField<S: ProtobufTypeProperties>(fieldType: S.Type, value: [S.BaseType], protoFieldNumber: Int, protoFieldName: String, jsonFieldName: String, swiftFieldName: String) throws {
  57. try visitRepeatedField(fieldType: fieldType, value: value, protoFieldNumber: protoFieldNumber, protoFieldName: protoFieldName, jsonFieldName: jsonFieldName, swiftFieldName: swiftFieldName)
  58. }
  59. mutating public func visitSingularMessageField<M: ProtobufMessage>(value: M, protoFieldNumber: Int, protoFieldName: String, jsonFieldName: String, swiftFieldName: String) throws {
  60. encoder.startField(name: jsonFieldName)
  61. // Note: We ask the message to serialize itself instead of
  62. // using ProtobufJSONEncodingVisitor(message:) since
  63. // some messages override the JSON format at this point.
  64. try M.serializeJSONValue(encoder: &encoder, value: value)
  65. }
  66. mutating public func visitRepeatedMessageField<M: ProtobufMessage>(value: [M], protoFieldNumber: Int, protoFieldName: String, jsonFieldName: String, swiftFieldName: String) throws {
  67. encoder.startField(name: jsonFieldName)
  68. var arraySeparator = ""
  69. encoder.append(text: "[")
  70. for v in value {
  71. encoder.append(text: arraySeparator)
  72. // Note: We ask the message to serialize itself instead of
  73. // using ProtobufJSONEncodingVisitor(message:) since
  74. // some messages override the JSON format at this point.
  75. try M.serializeJSONValue(encoder: &encoder, value: v)
  76. arraySeparator = ","
  77. }
  78. encoder.append(text: "]")
  79. }
  80. // Note that JSON encoding for groups is not officially supported
  81. // by any Google spec. But it's trivial to support it here.
  82. mutating public func visitSingularGroupField<G: ProtobufGroup>(value: G, protoFieldNumber: Int, protoFieldName: String, jsonFieldName: String, swiftFieldName: String) throws {
  83. encoder.startField(name: jsonFieldName)
  84. // Groups have no special JSON support, so we use only the generic traversal mechanism here
  85. let t = try ProtobufJSONEncodingVisitor(group: value).result
  86. encoder.append(text: t)
  87. }
  88. mutating public func visitRepeatedGroupField<G: ProtobufGroup>(value: [G], protoFieldNumber: Int, protoFieldName: String, jsonFieldName: String, swiftFieldName: String) throws {
  89. encoder.startField(name: jsonFieldName)
  90. var arraySeparator = ""
  91. encoder.append(text: "[")
  92. for v in value {
  93. encoder.append(text: arraySeparator)
  94. // Groups have no special JSON support, so we use only the generic traversal mechanism here
  95. let t = try ProtobufJSONEncodingVisitor(group: v).result
  96. encoder.append(text: t)
  97. arraySeparator = ","
  98. }
  99. encoder.append(text: "]")
  100. }
  101. mutating public func visitMapField<KeyType: ProtobufMapKeyType, ValueType: ProtobufMapValueType>(fieldType: ProtobufMap<KeyType, ValueType>.Type, value: ProtobufMap<KeyType, ValueType>.BaseType, protoFieldNumber: Int, protoFieldName: String, jsonFieldName: String, swiftFieldName: String) throws where KeyType.BaseType: Hashable {
  102. encoder.startField(name: jsonFieldName)
  103. var arraySeparator = ""
  104. encoder.append(text: "{")
  105. for (k,v) in value {
  106. encoder.append(text: arraySeparator)
  107. KeyType.serializeJSONMapKeyValue(encoder: &encoder, value: k)
  108. encoder.append(text: ":")
  109. try ValueType.serializeJSONValue(encoder: &encoder, value: v)
  110. arraySeparator = ","
  111. }
  112. encoder.append(text: "}")
  113. }
  114. }
  115. public struct ProtobufJSONEncoder {
  116. fileprivate var json: [String] = []
  117. private var separator: String = ""
  118. public init() {}
  119. public var result: String { return json.joined(separator: "") }
  120. mutating func append(text: String) {
  121. json.append(text)
  122. }
  123. mutating func appendTokens(tokens: [ProtobufJSONToken]) {
  124. for t in tokens {
  125. switch t {
  126. case .beginArray: append(text: "[")
  127. case .beginObject: append(text: "{")
  128. case .boolean(let v):
  129. // Note that quoted boolean map keys get stored as .string()
  130. putBoolValue(value: v, quote: false)
  131. case .colon: append(text: ":")
  132. case .comma: append(text: ",")
  133. case .endArray: append(text: "]")
  134. case .endObject: append(text: "}")
  135. case .null: putNullValue()
  136. case .number(let v): append(text: v)
  137. case .string(let v): putStringValue(value: v)
  138. }
  139. }
  140. }
  141. mutating func startField(name: String) {
  142. append(text: separator + "\"" + name + "\":")
  143. separator = ","
  144. }
  145. public mutating func startObject() {
  146. append(text: "{")
  147. separator = ""
  148. }
  149. public mutating func endObject() {
  150. append(text: "}")
  151. separator = ","
  152. }
  153. mutating func putNullValue() {
  154. append(text: "null")
  155. }
  156. mutating func putFloatValue(value: Float, quote: Bool) {
  157. putDoubleValue(value: Double(value), quote: quote)
  158. }
  159. mutating func putDoubleValue(value: Double, quote: Bool) {
  160. if value.isNaN {
  161. append(text: "\"NaN\"")
  162. } else if !value.isFinite {
  163. if value < 0 {
  164. append(text: "\"-Infinity\"")
  165. } else {
  166. append(text: "\"Infinity\"")
  167. }
  168. } else {
  169. // TODO: Be smarter here about choosing significant digits
  170. // See: protoc source has C++ code for this with interesting ideas
  171. let s: String
  172. if value < Double(Int64.max) && value > Double(Int64.min) && value == Double(Int64(value)) {
  173. s = String(Int64(value))
  174. } else {
  175. s = String(value)
  176. }
  177. if quote {
  178. append(text: "\"" + s + "\"")
  179. } else {
  180. append(text: s)
  181. }
  182. }
  183. }
  184. mutating func putInt64(value: Int64, quote: Bool) {
  185. // Always quote integers with abs value > 2^53
  186. if quote || value > 0x1FFFFFFFFFFFFF || value < -0x1FFFFFFFFFFFFF {
  187. append(text: "\"" + String(value) + "\"")
  188. } else {
  189. append(text: String(value))
  190. }
  191. }
  192. mutating func putUInt64(value: UInt64, quote: Bool) {
  193. if quote || value > 0x1FFFFFFFFFFFFF { // 2^53 - 1
  194. append(text: "\"" + String(value) + "\"")
  195. } else {
  196. append(text: String(value))
  197. }
  198. }
  199. mutating func putBoolValue(value: Bool, quote: Bool) {
  200. if quote {
  201. append(text: value ? "\"true\"" : "\"false\"")
  202. } else {
  203. append(text: value ? "true" : "false")
  204. }
  205. }
  206. mutating func putStringValue(value: String) {
  207. let hexDigits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
  208. append(text: "\"")
  209. for c in value.unicodeScalars {
  210. switch c.value {
  211. // Special two-byte escapes
  212. case 8: append(text: "\\b")
  213. case 9: append(text: "\\t")
  214. case 10: append(text: "\\n")
  215. case 12: append(text: "\\f")
  216. case 13: append(text: "\\r")
  217. case 34: append(text: "\\\"")
  218. case 92: append(text: "\\\\")
  219. case 0...31, 127...159: // Hex form for C0 and C1 control chars
  220. let digit1 = hexDigits[Int(c.value / 16)]
  221. let digit2 = hexDigits[Int(c.value & 15)]
  222. append(text: "\\u00\(digit1)\(digit2)")
  223. case 0...127: // ASCII
  224. append(text: String(c))
  225. default: // Non-ASCII
  226. append(text: String(c))
  227. }
  228. }
  229. append(text: "\"")
  230. }
  231. mutating func putBytesValue(value: [UInt8]) {
  232. var out: String = ""
  233. if value.count > 0 {
  234. let digits: [Character] = ["A", "B", "C", "D", "E", "F",
  235. "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q",
  236. "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b",
  237. "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
  238. "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x",
  239. "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8",
  240. "9", "+", "/"]
  241. var t: Int = 0
  242. for (i,v) in value.enumerated() {
  243. if i > 0 && i % 3 == 0 {
  244. out.append(digits[(t >> 18) & 63])
  245. out.append(digits[(t >> 12) & 63])
  246. out.append(digits[(t >> 6) & 63])
  247. out.append(digits[t & 63])
  248. t = 0
  249. }
  250. t <<= 8
  251. t += Int(v)
  252. }
  253. switch value.count % 3 {
  254. case 0:
  255. out.append(digits[(t >> 18) & 63])
  256. out.append(digits[(t >> 12) & 63])
  257. out.append(digits[(t >> 6) & 63])
  258. out.append(digits[t & 63])
  259. case 1:
  260. t <<= 16
  261. out.append(digits[(t >> 18) & 63])
  262. out.append(digits[(t >> 12) & 63])
  263. out.append(Character("="))
  264. out.append(Character("="))
  265. default:
  266. t <<= 8
  267. out.append(digits[(t >> 18) & 63])
  268. out.append(digits[(t >> 12) & 63])
  269. out.append(digits[(t >> 6) & 63])
  270. out.append(Character("="))
  271. }
  272. }
  273. append(text: "\"" + out + "\"")
  274. }
  275. }