冗余编码方案常用两种校验码
Swift 4 introduced the Codable
protocol to decode/encode JSON objects. Codable
is tightly integrated into the Swift toolchain. It is widely used on client-side and server-side Swift.
Swift 4引入了Codable
协议来解码/编码JSON对象。 Codable
已紧密集成到Swift工具链中。 它广泛用于客户端和服务器端Swift。
Sometimes, we meet challenges that require us to customize how we use Codable
to suit different advanced scenarios. Here are three hard challenges I’ve met when using Codable
and my clean code solutions.
有时,我们遇到的挑战要求我们自定义如何使用Codable
来适应不同的高级方案。 在使用Codable
和干净代码解决方案时,这是我遇到的三个难题。
相同键的多种值类型 (Multiple Value Types for the Same Key)
假设的API (A hypothetical API)
Consider a hypothetical API that sometimes returns a String
and sometimes returns an Int
for the key id
:
考虑一个假设的API,该API有时返回一个String
,有时返回一个Int
作为键id
:
[
{
"id": "XA134RRW",
"name": "Bob"
},
{
"id": 3003,
"name": "John"
}
]
The most common and straightforward solution is using an Enum to map to the different cases. Do we have the clean code solution to avoid the boilerplate rawValue
it introduces?
最常见,最直接的解决方案是使用枚举映射到不同的情况。 我们是否有干净的代码解决方案来避免它引入的样板rawValue
?
干净的代码解决方案 (The clean code solution)
To reduce the boilerplate and make clean code, I’m using the PropertyWrapper
:
为了减少样板并编写清晰的代码,我使用了PropertyWrapper
:
// MARk: LosslessStringCodable
public typealias LosslessStringCodable = LosslessStringConvertible & Codable
// MARK: DynamicDecoder
public struct DynamicCoder<T: LosslessStringCodable> {
public static func decode(from decoder: Decoder) throws -> T? {
do {
return try T(from: decoder)
} catch {
// Handle different types for the same key error
func decode<T: LosslessStringCodable>(_: T.Type) -> (Decoder) -> LosslessStringCodable? {
return { try? T.init(from: $0) }
}
let types: [(Decoder) -> LosslessStringCodable?] = [
decode(String.self),
decode(Bool.self),
decode(Int.self),
decode(Double.self)
]
guard let rawValue = types.lazy.compactMap({ $0(decoder) }).first,
let value = T.init("\(rawValue)") else {
return nil
}
return value
}
}
}
// MARK: CodableValue
@propertyWrapper
public struct CodableValue<T: LosslessStringCodable>: Codable {
public let wrappedValue: T
public init(wrappedValue: T) {
self.wrappedValue = wrappedValue
}
public init(from decoder: Decoder) throws {
let value: T = try DynamicCoder.decode(from: decoder)!
self.init(wrappedValue: value)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
}
Let’s break down the code bit by bit:
让我们逐一分解代码:
The
LosslessStringCodable
protocol is a combination of theLosslessStringConvertible
andCodable
protocols. For any type that can be String convertible, it can use the generic typeT
.LosslessStringCodable
协议是LosslessStringConvertible
和Codable
协议的组合。 对于任何可以String转换的类型,都可以使用通用类型T
DynamicCoder
accepts the generic typeLosslessStringCodable
. It can decode different types, includingString
,Bool
,Int
, andDouble
, to theLosslessStringCodable
type. It can be easily expanded to other types.DynamicCoder
接受通用类型LosslessStringCodable
。 它可以将不同类型(包括String
,Bool
,Int
和Double
解码为LosslessStringCodable
类型。 它可以轻松扩展为其他类型。The
CodableValue
is a property wrapper that decodes and encodes thewrappedValue
to the desired generic type.所述
CodableValue
是包装器,解码的属性和编码wrappedValue
到所需的通用类型。
使用范例 (Example use)
// MARK: User
struct User: Codable {
@CodableValue
var id: String //This is how we use the property wrapper
let name: String
}
The struct User
uses the CodableValue
property wrapper with the ID as String
value, but it surely can be used as other types.
struct User
使用ID为String
值的CodableValue
属性包装器,但是可以肯定将其用作其他类型。
动态键 (Dynamic Keys)
假设的API (A hypothetical API)
Consider a hypothetical API that returns JSON objects in order as the key:
考虑一个假设的API,该API按顺序返回JSON对象作为键:
{
"queues": [
{
"1": {
"firstName": "Bob",
"lastName": "Dylan"
}
},
{
"2": {
"firstName": "Tom",
"lastName": "Hanks"
}
}
]
}
干净的代码解决方案 (The clean code solution)
Using the dictionary to decode the JSON will be a suitable solution:
使用字典解码JSON将是合适的解决方案:
// MARK: Result
struct Result: Codable {
let queues: [[String: Customer]]
}
// MARK: Customer
struct Customer: Codable {
let firstName: String
let lastName: String
}
使用范例 (Example use)
Convert the JSON to data and use the JSONDecoder
to decode it:
将JSON转换为数据,然后使用JSONDecoder
进行解码:
let jsonWithOrderData = jsonWithOrder.data(using: .utf8)!
let result = try! JSONDecoder().decode(Result.self, from: jsonWithOderData)
print(result)
// Result(queues: [["1": Customer(firstName: "Bob", lastName: "Dylan")],
// ["2": Customer(firstName: "Tom", lastName: "Hanks")]])
具有协议属性的可编码结构 (Codable Struct With Protocol Property)
假设的API (A hypothetical API)
Consider a hypothetical API that returns the published pieces with the different types Article
and Brief
:
考虑一个假设的API,该API返回具有不同类型的Article
和Brief
的已发布Article
:
{
"state": "published",
"contents": [
{
"type": "Article",
"id": "1",
"title": "An article title"
},
{
"type": "Brief",
"id": "2",
"title": "A brief title"
}
]
}
干净的代码解决方案 (The clean code solution)
enum PublicationType: String, Codable {
case article
case brief
public var type: Publication.Type {
switch self {
case .article:
return Article.self
case .brief:
return Brief.self
}
}
}
// MARK: ContentMapper
struct PublicationMapper: Codable {
let publication: Publication
init(_ publication: Publication) {
self.publication = publication
}
enum CodingKeys: String, CodingKey {
case metatype = "type"
case content
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
let typeString = String(describing: type(of: publication)).lowercased()
guard let metatype = PublicationType(rawValue: typeString) else {
throw NSError(domain: "Encoding", code: -1)
}
try container.encode(metatype, forKey: .metatype)
try publication.encode(to: encoder)
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
guard let typeString = try? container.decode(String.self, forKey: .metatype).lowercased(),
let publicationType = PublicationType(rawValue: typeString) else {
throw NSError(domain: "Encoding", code: -1)
}
self.publication = try publicationType.type.init(from: decoder)
}
}
Let’s break down the code:
让我们分解一下代码:
PublicationType
contains two cases:Article
andBrief
. The most important part is it returns different struct types based on the cases.PublicationType
包含两种情况:Article
和Brief
。 最重要的部分是它根据情况返回不同的结构类型。PublicationMapper
has a valuepublication
and also a hidden valuemetatype
that maps the type of the protocol.PublicationMapper
具有值publication
和映射协议类型的隐藏值metatype
类型。PublicationMapper
implements the encoder function to encode thepublication
to the specific struct type.PublicationMapper
实现编码器功能,以将publication
编码为特定的结构类型。PublicationMapper
also implements the decoder function to decode JSON to the particular struct type by detecting the meta-type.PublicationMapper
还实现了解码器功能,通过检测元类型将JSON解码为特定的结构类型。
使用范例 (Example use)
struct Publican: Codable {
let state: String
let contents: [Publication]
enum CodingKeys: String, CodingKey {
case state
case contents
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(state, forKey: .state)
try container.encode(contents.map({ PublicationMapper($0) }), forKey: .contents)
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
state = try container.decode(String.self, forKey: .state)
contents = try container.decode([PublicationMapper].self, forKey: .contents).map({ $0.publication })
}
}
The publican
struct is implemented with the customized CodingKeys
and encoder/decoder functions to map the particular struct into the contents
array.
该publican
结构与实施定制CodingKeys
和编码器/解码器功能来映射特定结构到contents
阵列。
let publicationData = publicationJSON.data(using: .utf8)!
let publican = JSONDecoder().decode(Publican.self, from: publicationData)
print(publican)
//Publican(state: "published", contents: [Article(id: "1", title: "An article title"),
// Brief(id: "2", title: "A brief title")])
结论 (Conclusion)
Using Codable
makes working with JSON serialization much easier. If you’re interested, I published another piece about how Codable compares to SwiftyJSON.
使用Codable
使JSON序列化工作变得更加容易。 如果您有兴趣,我还会发表另一篇文章,介绍Codable与SwiftyJSON的比较。
All the code mentioned in this article can be found on GitHub.
可以在GitHub上找到本文提到的所有代码。
翻译自: https://medium.com/better-programming/3-solutions-help-you-swift-codable-cfd8de188676
冗余编码方案常用两种校验码