Message+JSONAdditions.swift 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. // Sources/SwiftProtobuf/Message+JSONAdditions.swift - JSON format primitive types
  2. //
  3. // Copyright (c) 2014 - 2016 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. /// Extensions to `Message` to support JSON encoding/decoding.
  12. ///
  13. // -----------------------------------------------------------------------------
  14. import Foundation
  15. /// JSON encoding and decoding methods for messages.
  16. extension Message {
  17. /// Returns a string containing the JSON serialization of the message.
  18. ///
  19. /// Unlike binary encoding, presence of required fields is not enforced when
  20. /// serializing to JSON.
  21. ///
  22. /// - Returns: A string containing the JSON serialization of the message.
  23. /// - Parameters:
  24. /// - options: The JSONEncodingOptions to use.
  25. /// - Throws: ``SwiftProtobufError`` or ``JSONEncodingError`` if encoding fails.
  26. public func jsonString(
  27. options: JSONEncodingOptions = JSONEncodingOptions()
  28. ) throws -> String {
  29. if let m = self as? (any _CustomJSONCodable) {
  30. return try m.encodedJSONString(options: options)
  31. }
  32. let data: [UInt8] = try jsonUTF8Bytes(options: options)
  33. return String(decoding: data, as: UTF8.self)
  34. }
  35. /// Returns a `SwiftProtobufContiguousBytes` containing the UTF-8 JSON serialization of the message.
  36. ///
  37. /// Unlike binary encoding, presence of required fields is not enforced when
  38. /// serializing to JSON.
  39. ///
  40. /// - Returns: A `SwiftProtobufContiguousBytes` containing the JSON serialization of the message.
  41. /// - Parameters:
  42. /// - options: The JSONEncodingOptions to use.
  43. /// - Throws: ``SwiftProtobufError`` or ``JSONEncodingError`` if encoding fails.
  44. public func jsonUTF8Bytes<Bytes: SwiftProtobufContiguousBytes>(
  45. options: JSONEncodingOptions = JSONEncodingOptions()
  46. ) throws -> Bytes {
  47. if let m = self as? (any _CustomJSONCodable) {
  48. let string = try m.encodedJSONString(options: options)
  49. return Bytes(string.utf8)
  50. }
  51. var visitor = try JSONEncodingVisitor(type: Self.self, options: options)
  52. visitor.startObject(message: self)
  53. try traverse(visitor: &visitor)
  54. visitor.endObject()
  55. return Bytes(visitor.dataResult)
  56. }
  57. /// Creates a new message by decoding the given string containing a
  58. /// serialized message in JSON format.
  59. ///
  60. /// - Parameter jsonString: The JSON-formatted string to decode.
  61. /// - Parameter options: The JSONDecodingOptions to use.
  62. /// - Throws: ``SwiftProtobufError`` or ``JSONDecodingError`` if decoding fails.
  63. public init(
  64. jsonString: String,
  65. options: JSONDecodingOptions = JSONDecodingOptions()
  66. ) throws {
  67. try self.init(jsonString: jsonString, extensions: nil, options: options)
  68. }
  69. /// Creates a new message by decoding the given string containing a
  70. /// serialized message in JSON format.
  71. ///
  72. /// - Parameter jsonString: The JSON-formatted string to decode.
  73. /// - Parameter extensions: An ExtensionMap for looking up extensions by name
  74. /// - Parameter options: The JSONDecodingOptions to use.
  75. /// - Throws: ``SwiftProtobufError`` or ``JSONDecodingError`` if decoding fails.
  76. public init(
  77. jsonString: String,
  78. extensions: (any ExtensionMap)? = nil,
  79. options: JSONDecodingOptions = JSONDecodingOptions()
  80. ) throws {
  81. if jsonString.isEmpty {
  82. throw JSONDecodingError.truncated
  83. }
  84. if let data = jsonString.data(using: String.Encoding.utf8) {
  85. try self.init(jsonUTF8Bytes: data, extensions: extensions, options: options)
  86. } else {
  87. throw JSONDecodingError.truncated
  88. }
  89. }
  90. /// Creates a new message by decoding the given `SwiftProtobufContiguousBytes`
  91. /// containing a serialized message in JSON format, interpreting the data as UTF-8 encoded
  92. /// text.
  93. ///
  94. /// - Parameter jsonUTF8Bytes: The JSON-formatted data to decode, represented
  95. /// as UTF-8 encoded text.
  96. /// - Parameter options: The JSONDecodingOptions to use.
  97. /// - Throws: ``SwiftProtobufError`` or ``JSONDecodingError`` if decoding fails.
  98. public init<Bytes: SwiftProtobufContiguousBytes>(
  99. jsonUTF8Bytes: Bytes,
  100. options: JSONDecodingOptions = JSONDecodingOptions()
  101. ) throws {
  102. try self.init(jsonUTF8Bytes: jsonUTF8Bytes, extensions: nil, options: options)
  103. }
  104. /// Creates a new message by decoding the given `SwiftProtobufContiguousBytes`
  105. /// containing a serialized message in JSON format, interpreting the data as UTF-8 encoded
  106. /// text.
  107. ///
  108. /// - Parameter jsonUTF8Bytes: The JSON-formatted data to decode, represented
  109. /// as UTF-8 encoded text.
  110. /// - Parameter extensions: The extension map to use with this decode
  111. /// - Parameter options: The JSONDecodingOptions to use.
  112. /// - Throws: ``SwiftProtobufError`` or ``JSONDecodingError`` if decoding fails.
  113. public init<Bytes: SwiftProtobufContiguousBytes>(
  114. jsonUTF8Bytes: Bytes,
  115. extensions: (any ExtensionMap)? = nil,
  116. options: JSONDecodingOptions = JSONDecodingOptions()
  117. ) throws {
  118. self.init()
  119. try jsonUTF8Bytes.withUnsafeBytes { (body: UnsafeRawBufferPointer) in
  120. // Empty input is valid for binary, but not for JSON.
  121. guard body.count > 0 else {
  122. throw JSONDecodingError.truncated
  123. }
  124. var decoder = JSONDecoder(
  125. source: body,
  126. options: options,
  127. messageType: Self.self,
  128. extensions: extensions
  129. )
  130. if decoder.scanner.skipOptionalNull() {
  131. if let customCodable = Self.self as? any _CustomJSONCodable.Type,
  132. let message = try customCodable.decodedFromJSONNull()
  133. {
  134. self = message as! Self
  135. } else {
  136. throw JSONDecodingError.illegalNull
  137. }
  138. } else {
  139. try decoder.decodeFullObject(message: &self)
  140. }
  141. if !decoder.scanner.complete {
  142. throw JSONDecodingError.trailingGarbage
  143. }
  144. }
  145. }
  146. }