Skip to content

Commit 8b8a084

Browse files
Add some helper methods for implementing JSONCodable
Will use these in an upcoming commit.
1 parent bddad8c commit 8b8a084

File tree

2 files changed

+191
-2
lines changed

2 files changed

+191
-2
lines changed

Sources/AblyChat/DefaultMessages.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,12 @@ internal final class DefaultMessages: Messages, EmitsDiscontinuities {
7777
throw ARTErrorInfo.create(withCode: 50000, status: 500, message: "Received incoming message without clientId")
7878
}
7979

80-
let metadata: Metadata? = if let metadataJSONObject = data["metadata"]?.objectValue {
80+
let metadata: Metadata? = if let metadataJSONObject = try data.optionalObjectValueForKey("metadata") {
8181
try metadataJSONObject.mapValues { try MetadataValue(jsonValue: $0) }
8282
} else {
8383
nil
8484
}
85-
let headers: Headers? = if let headersJSONObject = extras["headers"]?.objectValue {
85+
let headers: Headers? = if let headersJSONObject = try extras.optionalObjectValueForKey("headers") {
8686
try headersJSONObject.mapValues { try HeadersValue(jsonValue: $0) }
8787
} else {
8888
nil

Sources/AblyChat/JSONCodable.swift

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ internal protocol JSONObjectDecodable: JSONDecodable {
2525

2626
internal enum JSONValueDecodingError: Error {
2727
case valueIsNotObject
28+
case noValueForKey(String)
29+
case wrongTypeForKey(String, actualValue: JSONValue)
2830
}
2931

3032
// Default implementation of `JSONDecodable` conformance for `JSONObjectDecodable`
@@ -39,3 +41,190 @@ internal extension JSONObjectDecodable {
3941
}
4042

4143
internal typealias JSONObjectCodable = JSONObjectDecodable & JSONObjectEncodable
44+
45+
// MARK: - Extracting values from a dictionary
46+
47+
/// This extension adds some helper methods for extracting values from a dictionary of `JSONValue` values; you may find them helpful when implementing `JSONCodable`.
48+
internal extension [String: JSONValue] {
49+
/// If this dictionary contains a value for `key`, and this value has case `object`, this returns the associated value.
50+
///
51+
/// - Throws:
52+
/// - `JSONValueDecodingError.noValueForKey` if the key is absent
53+
/// - `JSONValueDecodingError.wrongTypeForKey` if the value does not have case `object`
54+
func objectValueForKey(_ key: String) throws -> [String: JSONValue] {
55+
guard let value = self[key] else {
56+
throw JSONValueDecodingError.noValueForKey(key)
57+
}
58+
59+
guard case let .object(objectValue) = value else {
60+
throw JSONValueDecodingError.wrongTypeForKey(key, actualValue: value)
61+
}
62+
63+
return objectValue
64+
}
65+
66+
/// If this dictionary contains a value for `key`, and this value has case `object`, this returns the associated value. If this dictionary does not contain a value for `key`, or if the value for key has case `null`, it returns `nil`.
67+
///
68+
/// - Throws: `JSONValueDecodingError.wrongTypeForKey` if the value does not have case `object` or `null`
69+
func optionalObjectValueForKey(_ key: String) throws -> [String: JSONValue]? {
70+
guard let value = self[key] else {
71+
return nil
72+
}
73+
74+
if case .null = value {
75+
return nil
76+
}
77+
78+
guard case let .object(objectValue) = value else {
79+
throw JSONValueDecodingError.wrongTypeForKey(key, actualValue: value)
80+
}
81+
82+
return objectValue
83+
}
84+
85+
/// If this dictionary contains a value for `key`, and this value has case `array`, this returns the associated value.
86+
///
87+
/// - Throws:
88+
/// - `JSONValueDecodingError.noValueForKey` if the key is absent
89+
/// - `JSONValueDecodingError.wrongTypeForKey` if the value does not have case `array`
90+
func arrayValueForKey(_ key: String) throws -> [JSONValue] {
91+
guard let value = self[key] else {
92+
throw JSONValueDecodingError.noValueForKey(key)
93+
}
94+
95+
guard case let .array(arrayValue) = value else {
96+
throw JSONValueDecodingError.wrongTypeForKey(key, actualValue: value)
97+
}
98+
99+
return arrayValue
100+
}
101+
102+
/// If this dictionary contains a value for `key`, and this value has case `array`, this returns the associated value. If this dictionary does not contain a value for `key`, or if the value for key has case `null`, it returns `nil`.
103+
///
104+
/// - Throws: `JSONValueDecodingError.wrongTypeForKey` if the value does not have case `array` or `null`
105+
func optionalArrayValueForKey(_ key: String) throws -> [JSONValue]? {
106+
guard let value = self[key] else {
107+
return nil
108+
}
109+
110+
if case .null = value {
111+
return nil
112+
}
113+
114+
guard case let .array(arrayValue) = value else {
115+
throw JSONValueDecodingError.wrongTypeForKey(key, actualValue: value)
116+
}
117+
118+
return arrayValue
119+
}
120+
121+
/// If this dictionary contains a value for `key`, and this value has case `string`, this returns the associated value.
122+
///
123+
/// - Throws:
124+
/// - `JSONValueDecodingError.noValueForKey` if the key is absent
125+
/// - `JSONValueDecodingError.wrongTypeForKey` if the value does not have case `string`
126+
func stringValueForKey(_ key: String) throws -> String {
127+
guard let value = self[key] else {
128+
throw JSONValueDecodingError.noValueForKey(key)
129+
}
130+
131+
guard case let .string(stringValue) = value else {
132+
throw JSONValueDecodingError.wrongTypeForKey(key, actualValue: value)
133+
}
134+
135+
return stringValue
136+
}
137+
138+
/// If this dictionary contains a value for `key`, and this value has case `string`, this returns the associated value. If this dictionary does not contain a value for `key`, or if the value for key has case `null`, it returns `nil`.
139+
///
140+
/// - Throws: `JSONValueDecodingError.wrongTypeForKey` if the value does not have case `string` or `null`
141+
func optionalStringValueForKey(_ key: String) throws -> String? {
142+
guard let value = self[key] else {
143+
return nil
144+
}
145+
146+
if case .null = value {
147+
return nil
148+
}
149+
150+
guard case let .string(stringValue) = value else {
151+
throw JSONValueDecodingError.wrongTypeForKey(key, actualValue: value)
152+
}
153+
154+
return stringValue
155+
}
156+
157+
/// If this dictionary contains a value for `key`, and this value has case `number`, this returns the associated value.
158+
///
159+
/// - Throws:
160+
/// - `JSONValueDecodingError.noValueForKey` if the key is absent
161+
/// - `JSONValueDecodingError.wrongTypeForKey` if the value does not have case `number`
162+
func numberValueForKey(_ key: String) throws -> Double {
163+
guard let value = self[key] else {
164+
throw JSONValueDecodingError.noValueForKey(key)
165+
}
166+
167+
guard case let .number(numberValue) = value else {
168+
throw JSONValueDecodingError.wrongTypeForKey(key, actualValue: value)
169+
}
170+
171+
return numberValue
172+
}
173+
174+
/// If this dictionary contains a value for `key`, and this value has case `number`, this returns the associated value. If this dictionary does not contain a value for `key`, or if the value for key has case `null`, it returns `nil`.
175+
///
176+
/// - Throws: `JSONValueDecodingError.wrongTypeForKey` if the value does not have case `number` or `null`
177+
func optionalNumberValueForKey(_ key: String) throws -> Double? {
178+
guard let value = self[key] else {
179+
return nil
180+
}
181+
182+
if case .null = value {
183+
return nil
184+
}
185+
186+
guard case let .number(numberValue) = value else {
187+
throw JSONValueDecodingError.wrongTypeForKey(key, actualValue: value)
188+
}
189+
190+
return numberValue
191+
}
192+
193+
/// If this dictionary contains a value for `key`, and this value has case `bool`, this returns the associated value.
194+
///
195+
/// - Throws:
196+
/// - `JSONValueDecodingError.noValueForKey` if the key is absent
197+
/// - `JSONValueDecodingError.wrongTypeForKey` if the value does not have case `bool`
198+
func boolValueForKey(_ key: String) throws -> Bool {
199+
guard let value = self[key] else {
200+
throw JSONValueDecodingError.noValueForKey(key)
201+
}
202+
203+
guard case let .bool(boolValue) = value else {
204+
throw JSONValueDecodingError.wrongTypeForKey(key, actualValue: value)
205+
}
206+
207+
return boolValue
208+
}
209+
210+
/// If this dictionary contains a value for `key`, and this value has case `bool`, this returns the associated value.
211+
///
212+
/// - Throws:
213+
/// - `JSONValueDecodingError.noValueForKey` if the key is absent
214+
/// - `JSONValueDecodingError.wrongTypeForKey` if the value does not have case `bool`
215+
func optionalBoolValueForKey(_ key: String) throws -> Bool? {
216+
guard let value = self[key] else {
217+
return nil
218+
}
219+
220+
if case .null = value {
221+
return nil
222+
}
223+
224+
guard case let .bool(boolValue) = value else {
225+
throw JSONValueDecodingError.wrongTypeForKey(key, actualValue: value)
226+
}
227+
228+
return boolValue
229+
}
230+
}

0 commit comments

Comments
 (0)