Хотел бы поделиться с вами некоторыми советами и трюками, которые я использовал на этом примере.
Скачайте Swift Playground со всем кодом из этой статьи:
Codable представлен в Swift 4 с целью заменить старый NSCoding API. В отличие от NSCoding у Codable есть поддержка JSON первого класса, что делает его перспективным вариантом для использования API JSON.
Codable отлично подходит в качестве NSCoding. Если имеется необходимость кодировать или декодировать некоторые локальные данные, которые вы должны контролировать в полной мере, вы можете использовать преимущество автоматического кодирования и декодирования.
В реальном мире все становится достаточно сложным и быстрым. Попытка построить отказоустойчивую систему, которая будет управлять JSON и моделировать все требования к продукту, является проблемой.
Одним из основных недостатков Codable является то, что как только вам понадобится пользовательская логика декодирования — даже для одного ключа — вы должны предоставить все: вручную определить все ключи кодирования и полностью вручную реализовать init(from decoder: Decoder) throws. Это не идеально. Но это по крайней мере так же хорошо (или плохо), как использование сторонних библиотек JSON в Swift. Наличие таковой в строенной библиотеке уже является победой.
Поэтому, если вы хотите начать использовать Codable в своем приложении (и вы уже знакомы со всеми его основными особенностями), вот несколько советов и примеров, которые могут оказаться полезными:
final class Post: Decodable {
let id: Id<Post> // More about this type later.
let title: String
let subtitle: String?
}
[
{
"id": "pos_1",
"title": "Codable: Tips and Tricks"
},
{
"id": "pos_2"
}
]
do {
let posts = try JSONDecoder().decode([Post].self, from: json.data(using: .utf8)!)
} catch {
print(error)
//prints "No value associated with key title (\"title\")."
}
public struct Safe<Base: Decodable>: Decodable {
public let value: Base?
public init(from decoder: Decoder) throws {
do {
let container = try decoder.singleValueContainer()
self.value = try container.decode(Base.self)
} catch {
assertionFailure("ERROR: \(error)")
// TODO: automatically send a report about a corrupted data
self.value = nil
}
}
}
do {
let posts = try JSONDecoder().decode([Safe<Post>].self, from: json.data(using: .utf8)!)
print(posts[0].value!.title) // prints "Codable: Tips and Tricks"
print(posts[1].value) // prints "nil"
} catch {
print(error)
}
public struct Id<Entity>: Hashable {
public let raw: String
public init(_ raw: String) {
self.raw = raw
}
public var hashValue: Int {
return raw.hashValue
}
public static func ==(lhs: Id, rhs: Id) -> Bool {
return lhs.raw == rhs.raw
}
}
extension Id: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let raw = try container.decode(String.self)
if raw.isEmpty {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "Cannot initialize Id from an empty string"
)
}
self.init(raw)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(raw)
}
}
enum System: String, Decodable {
case ios, macos, tvos, watchos
}
struct Location: Decodable {
let latitude: Double
let longitude: Double
}
final class Device: Decodable {
let location: Location
let system: System
}
{
"location": {
"latitude": 37.3317,
"longitude": 122.0302
},
"system": "caros"
}
do {
let device = try JSONDecoder().decode(Device.self, from: json.data(using: .utf8)!)
} catch {
print(error)
// Prints "Cannot initialize System from invalid String value caros"
}
final class Device: Decodable {
let location: Location
let system: System?
init(from decoder: Decoder) throws {
let map = try decoder.container(keyedBy: CodingKeys.self)
self.location = try map.decode(Location.self, forKey: .location)
self.system = try? map.decode(System.self, forKey: .system)
}
private enum CodingKeys: CodingKey {
case location
case system
}
}
self.system = System(rawValue: try map.decode(String.self, forKey: .system))
extension KeyedDecodingContainer {
public func decode<T: Decodable>(_ key: Key, as type: T.Type = T.self) throws -> T {
return try self.decode(T.self, forKey: key)
}
public func decodeIfPresent<T: Decodable>(_ key: KeyedDecodingContainer.Key) throws -> T? {
return try decodeIfPresent(T.self, forKey: key)
}
}
struct PatchParameters: Swift.Encodable {
let name: Parameter<String>?
}
func encoded(_ params: PatchParameters) -> String {
let data = try! JSONEncoder().encode(params)
return String(data: data, encoding: .utf8)!
}
encoded(PatchParameters(name: nil))
// prints "{}"
encoded(PatchParameters(name: .null))
//print "{"name":null}"
encoded(PatchParameters(name: .value("Alex")))
//print "{"name":"Alex"}"
К сожалению, не доступен сервер mySQL