-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathJSON.swift
251 lines (214 loc) · 6.48 KB
/
JSON.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
243
244
245
246
247
248
249
250
251
import Foundation
import CoreFoundation
@dynamicMemberLookup
public enum JSON: Hashable, Sendable {
case dictionary([String: JSON])
case array([JSON])
case string(String)
case number(NSNumber)
case bool(Bool)
case null
public subscript(dynamicMember member: String) -> JSON? {
get {
return self[member]
}
set {
self[member] = newValue
}
}
}
// MARK: -
extension JSON {
public subscript(key: String) -> JSON? {
get {
if case .dictionary(let dict) = self {
return dict[key]
}
return nil
}
set {
if case .dictionary(var dict) = self {
dict[key] = newValue
self = .dictionary(dict)
}
}
}
public subscript(index: Int) -> JSON? {
if case .array(let arr) = self, index < arr.count {
return arr[index]
}
return nil
}
public var stringValue: String? {
if case .string(let str) = self {
return str
}
return nil
}
public var numberValue: NSNumber? {
if case .number(let num) = self {
return num
}
return nil
}
public var doubleValue: Double? {
return numberValue?.doubleValue
}
public var intValue: Int? {
return numberValue?.intValue
}
public var boolValue: Bool? {
if case .bool(let boolean) = self {
return boolean
}
return nil
}
}
// MARK: -
extension JSON {
public func data() -> Data {
return try! JSONSerialization.data(withJSONObject: jsonObject(), options: .fragmentsAllowed)
}
public func jsonObject() -> Any {
switch self {
case .dictionary(let dictionary):
return dictionary.mapValues({ $0.jsonObject() })
case .array(let array):
return array.map({ $0.jsonObject() })
case .string(let string):
return string
case .number(let number):
return number
case .bool(let bool):
return (bool ? kCFBooleanTrue : kCFBooleanFalse) as CFBoolean
case .null:
return NSNull()
}
}
public init(data: Data) throws {
let jsonObject = try JSONSerialization.jsonObject(with: data)
try self.init(jsonObject: jsonObject)
}
public init(jsonObject: Any) throws {
switch jsonObject {
case is NSNull:
self = .null
case let opt as Any? where opt == nil:
self = .null
case let bool as CFBoolean where CFGetTypeID(bool) == CFBooleanGetTypeID():
// number는 boolean으로 변환될 수 있으므로 타입까지 체크.
self = .bool(CFBooleanGetValue(bool))
case let number as NSNumber:
self = .number(number)
case let string as String:
self = .string(string)
case let array as [Any]:
self = .array(try array.map { try JSON(jsonObject: $0) })
case let dictionary as [String: Any]:
self = .dictionary(try dictionary.mapValues { try JSON(jsonObject: $0) })
default:
throw InvalidTypeError(type: type(of: jsonObject), valueDescription: "\(jsonObject)")
}
}
public struct InvalidTypeError: Error {
let type: Any.Type
let valueDescription: String
}
}
// MARK: -
extension JSON: ExpressibleByDictionaryLiteral {
public init(dictionaryLiteral elements: (String, JSONConvertibleValue)...) {
let dict = Dictionary(uniqueKeysWithValues: elements)
.mapValues({ try! JSON(jsonObject: $0) })
self = .dictionary(dict)
}
}
extension JSON: ExpressibleByArrayLiteral {
public init(arrayLiteral elements: JSONConvertibleValue...) {
self = .array(elements.map({ try! JSON(jsonObject: $0) }))
}
}
extension JSON: ExpressibleByStringLiteral {
public init(stringLiteral value: String) {
self = .string(value)
}
}
extension JSON: ExpressibleByFloatLiteral {
public init(floatLiteral value: Double) {
self = .number(NSNumber(value: value))
}
}
extension JSON: ExpressibleByIntegerLiteral {
public init(integerLiteral value: Int) {
self = .number(NSNumber(value: value))
}
}
extension JSON: ExpressibleByBooleanLiteral {
public init(booleanLiteral value: Bool) {
self = .bool(value)
}
}
// MARK: -
extension JSON: CustomStringConvertible {
public var description: String {
return JSONFormatter.string(from: self)
}
}
private struct JSONFormatter {
private var text: String = ""
private var indent: Indent = Indent()
static func string(from json: JSON, indentWidth: Int = 4) -> String {
var formatter = JSONFormatter()
formatter.indent.width = indentWidth
formatter.write(json)
return formatter.text
}
private mutating func write(_ json: JSON) {
switch json {
case .dictionary(let dict):
write(dict)
case .array(let arr):
write(arr)
case .string(let str):
text.write("\"\(str.addingBackslashEncoding())\"")
case .number(let num):
text.write(num.description)
case .bool(let boolean):
text.write(boolean.description)
case .null:
text.write("null")
}
}
private mutating func write(_ dictionary: [String: JSON]) {
if dictionary.isEmpty {
text.write("{}")
return
}
text.write("{\n")
indent.level += 1
for (index, (key, json)) in dictionary.enumerated() {
let isLastElement = index + 1 == dictionary.count
text.write("\(indent)\"\(key)\": ")
write(json)
text.write(isLastElement ? "\n" : ",\n")
}
indent.level -= 1
text.write("\(indent)}")
}
private mutating func write(_ array: [JSON]) {
if array.isEmpty {
text.write("[]")
return
}
text.write("[\n")
indent.level += 1
for (index, json) in array.enumerated() {
let isLastElement = index + 1 == array.count
text.write(indent.toString())
write(json)
text.write(isLastElement ? "\n" : ",\n")
}
indent.level -= 1
text.write("\(indent)]")
}
}