-
Notifications
You must be signed in to change notification settings - Fork 29
/
Copy pathReader.swift
242 lines (216 loc) · 7.95 KB
/
Reader.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
@_spi(SmithyReadWrite) import protocol SmithyReadWrite.SmithyReader
import struct Smithy.Document
import typealias SmithyReadWrite.ReadingClosure
import enum SmithyReadWrite.ReaderError
@_spi(SmithyTimestamps) import enum SmithyTimestamps.TimestampFormat
@_spi(SmithyTimestamps) import struct SmithyTimestamps.TimestampFormatter
import struct Foundation.Data
import struct Foundation.Date
import class Foundation.NSNull
import class Foundation.NSNumber
import class Foundation.NSDecimalNumber
import func CoreFoundation.CFGetTypeID
import func CoreFoundation.CFBooleanGetTypeID
@_spi(SmithyReadWrite)
public final class Reader: SmithyReader {
public typealias NodeInfo = SmithyJSON.NodeInfo
public let nodeInfo: NodeInfo
let jsonNode: JSONNode?
public var respectsJSONName = false {
didSet { children.forEach { $0.respectsJSONName = respectsJSONName } }
}
public internal(set) var children = [Reader]()
public internal(set) weak var parent: Reader?
public var hasContent: Bool { jsonNode != nil && jsonNode != .null }
init(nodeInfo: NodeInfo, jsonObject: Any?, parent: Reader? = nil) throws {
self.nodeInfo = nodeInfo
self.jsonNode = try Self.jsonNode(for: jsonObject)
self.respectsJSONName = parent?.respectsJSONName ?? false
self.parent = parent
self.children = try Self.children(from: jsonObject, parent: self)
}
init(nodeInfo: NodeInfo, parent: Reader?) {
self.nodeInfo = nodeInfo
self.jsonNode = nil
self.respectsJSONName = parent?.respectsJSONName ?? false
self.parent = parent
}
private static func jsonNode(for jsonObject: Any?) throws -> JSONNode? {
if jsonObject is [String: Any] {
return .object
} else if jsonObject is [Any] {
return .array
} else if let nsNumber = jsonObject as? NSNumber, CFGetTypeID(nsNumber) == CFBooleanGetTypeID() {
return .bool(nsNumber.boolValue)
} else if let nsNumber = jsonObject as? NSNumber {
return .number(nsNumber)
} else if let string = jsonObject as? String {
return .string(string)
} else if jsonObject is NSNull {
return .null
} else {
throw JSONError.unknownJSONContent
}
}
private static func children(from jsonObject: Any?, parent: Reader) throws -> [Reader] {
if let object = jsonObject as? [String: Any] {
return try object.map { try Reader(nodeInfo: .init($0.key), jsonObject: $0.value, parent: parent) }
} else if let list = jsonObject as? [Any] {
return try list.map { try Reader(nodeInfo: "", jsonObject: $0, parent: parent) }
} else {
return []
}
}
}
public extension Reader {
subscript(nodeInfo: NodeInfo) -> Reader {
if let match = children.first(where: { nodeInfo.name == $0.nodeInfo.name }) {
return match
} else {
// The queried node doesn't exist. Return one that has nil content.
return Reader(nodeInfo: nodeInfo, parent: self)
}
}
func readIfPresent() throws -> String? {
switch jsonNode {
case .string(let string): return string
default: return nil
}
}
func readIfPresent() throws -> Int8? {
switch jsonNode {
case .number(let number): return Int8(truncating: number)
default: return nil
}
}
func readIfPresent() throws -> Int16? {
switch jsonNode {
case .number(let number): return Int16(truncating: number)
default: return nil
}
}
func readIfPresent() throws -> Int? {
switch jsonNode {
case .number(let number): return number.intValue
default: return nil
}
}
func readIfPresent() throws -> Float? {
switch jsonNode {
case .number(let number): return number.floatValue
case .string(let string):
switch string {
case "NaN": return .nan
case "Infinity": return .infinity
case "-Infinity": return -.infinity
default: return nil
}
default: return nil
}
}
func readIfPresent() throws -> Double? {
switch jsonNode {
case .number(let number):
if let decimalNumber = number as? NSDecimalNumber {
return Double("\(decimalNumber.decimalValue)")
} else {
return number.doubleValue
}
case .string(let string):
switch string {
case "NaN": return .nan
case "Infinity": return .infinity
case "-Infinity": return -.infinity
default: return nil
}
default: return nil
}
}
func readIfPresent() throws -> Bool? {
switch jsonNode {
case .bool(let bool): return bool
default: return nil
}
}
func readIfPresent() throws -> Data? {
switch jsonNode {
case .string(let string): return Data(base64Encoded: Data(string.utf8))
default: return nil
}
}
func readIfPresent() throws -> Document? {
guard let jsonObject = self.jsonObject else { return nil }
return try Document.make(from: jsonObject)
}
func readTimestampIfPresent(format: SmithyTimestamps.TimestampFormat) throws -> Date? {
switch jsonNode {
case .string(let string): return TimestampFormatter(format: format).date(from: string)
case .number(let number): return TimestampFormatter(format: format).date(from: "\(number)")
default: return nil
}
}
func readIfPresent<T>() throws -> T? where T: RawRepresentable, T.RawValue == Int {
guard let rawValue: Int = try readIfPresent() else { return nil }
return T(rawValue: rawValue)
}
func readIfPresent<T>() throws -> T? where T: RawRepresentable, T.RawValue == String {
guard let rawValue: String = try readIfPresent() else { return nil }
return T(rawValue: rawValue)
}
func readMapIfPresent<Value>(
valueReadingClosure: (Reader) throws -> Value,
keyNodeInfo: NodeInfo,
valueNodeInfo: NodeInfo,
isFlattened: Bool
) throws -> [String: Value]? {
if jsonNode != .object { return nil }
var dict = [String: Value]()
for mapEntry in children {
do {
let value = try valueReadingClosure(mapEntry)
dict.updateValue(value, forKey: mapEntry.nodeInfo.name)
} catch ReaderError.requiredValueNotPresent {
// This catch will "tolerate" a JSON null value in a map.
// Any other unreadable value is still an error
if !(try mapEntry.readNullIfPresent() ?? false) { throw ReaderError.requiredValueNotPresent }
}
}
return dict
}
func readListIfPresent<Member>(
memberReadingClosure: (Reader) throws -> Member,
memberNodeInfo: NodeInfo,
isFlattened: Bool
) throws -> [Member]? {
if jsonNode != .array { return nil }
return try children.map { try memberReadingClosure($0) }
}
func readNullIfPresent() throws -> Bool? {
guard let jsonNode else { return nil }
return jsonNode == .null
}
// MARK: - Private methods
private var jsonObject: Any? {
guard let jsonNode else { return nil }
switch jsonNode {
case .bool(let bool):
return NSNumber(booleanLiteral: bool)
case .number(let number):
return number
case .string(let string):
return string
case .null:
return NSNull()
case .array:
return children.compactMap { $0.jsonObject }
case .object:
return Dictionary(uniqueKeysWithValues: children.map { ($0.nodeInfo.name, $0.jsonObject) })
}
}
}