目前,绝大多数移动应用程序都是客户端服务器。 到处都有加载,同步,发送事件,并且与服务器交互的主要方式是使用json格式交换数据。
密钥解码
基金会有两种机制来对死数据进行序列化。 旧的是NSJsonSerialization
,新的是Codable
。 优势列表中的最后一个具有奇妙的功能,例如,基于实现Codable
( Encodable
, Decodable
)的结构(或类)和用于解码数据的初始化器,自动为json数据生成密钥。
一切似乎都很好,您可以使用和享受,但是现实并非如此简单。
在服务器上经常可以看到以下形式的json:
{"topLevelObject": { "underlyingObject": 1 }, "Error": { "ErrorCode": 400, "ErrorDescription": "SomeDescription" } }
这是来自其中一个项目服务器的几乎真实的示例。
对于JsonDecoder
类, JsonDecoder
可以指定使用snake_case键的工作,但是如果我们有UpperCamelCase,破折号大写字母,甚至是普通的大杂烩,但又不想手动编写键怎么办?
幸运的是,Apple提供了在使用JSONDecoder.KeyDecodingStrategy
将键映射与CodingKeys
结构进行匹配之前配置键映射的JSONDecoder.KeyDecodingStrategy
。 我们将利用这一点。
首先,创建一个实现CodingKey
协议的结构,因为标准库中没有该结构:
struct AnyCodingKey: CodingKey { var stringValue: String var intValue: Int? init(_ base: CodingKey) { self.init(stringValue: base.stringValue, intValue: base.intValue) } init(stringValue: String) { self.stringValue = stringValue } init(intValue: Int) { self.stringValue = "\(intValue)" self.intValue = intValue } init(stringValue: String, intValue: Int?) { self.stringValue = stringValue self.intValue = intValue } }
然后有必要分别处理每种情况的密钥。 主要的:
snake_case,破折号蛇格,lowerCamelCase和UpperCamelCase。 检查,运行,一切正常。
然后,我们遇到了一个相当令人期待的问题:camelCase'ah的缩写(记住众多id
, Id
, ID
)。 为了使其正常工作,您需要正确地转换它们并引入规则- 缩写被转换为camelCase,仅保留首字母大写,并且myABBRKey会变成myAbbrKey 。
该解决方案适用于多种情况的组合。
注意:实现将提供到.custom
密钥解码策略中。
static func convertToProperLowerCamelCase(keys: [CodingKey]) -> CodingKey { guard let last = keys.last else { assertionFailure() return AnyCodingKey(stringValue: "") } if let fromUpper = convertFromUpperCamelCase(initial: last.stringValue) { return AnyCodingKey(stringValue: fromUpper) } else if let fromSnake = convertFromSnakeCase(initial: last.stringValue) { return AnyCodingKey(stringValue: fromSnake) } else { return AnyCodingKey(last) } }
日期解码
下一个常规问题是日期的传递方式。 服务器上有很多微服务,团队数量略少,但数量也相当可观,最后,我们面临着许多日期格式,例如“是的,我使用标准”。 另外,有人以字符串形式传递日期,有人以纪元时间传递。 结果,我们再次遇到了字符串-数字-时区-毫秒分隔符组合的DateDecoder
,iOS中的DateDecoder
抱怨并且需要严格的日期格式。 这里的解决方案很简单,只需搜索我们寻找特定格式的符号并将它们组合起来,即可获得必要的结果。 这些格式已经成功并完全涵盖了我的案例。
注意:这是自定义DateFormatter初始化程序。 它只是将格式设置为创建的格式化程序。
static let onlyDate = DateFormatter(format: "yyyy-MM-dd") static let full = DateFormatter(format: "yyyy-MM-dd'T'HH:mm:ss.SSSx") static let noWMS = DateFormatter(format: "yyyy-MM-dd'T'HH:mm:ssZ") static let noWTZ = DateFormatter(format: "yyyy-MM-dd'T'HH:mm:ss.SSS") static let noWMSnoWTZ = DateFormatter(format: "yyyy-MM-dd'T'HH:mm:ss")
我们使用JSONDecoder.DateDecodingStrategy
将其附加到我们的解码器,并获得一个解码器,该解码器可以处理几乎所有内容并将其转换为对我们而言可消化的格式。
性能测试
对大小为7944字节的json字符串执行了测试。
如我们所见,由于必须对json中每个键进行强制验证,因此自定义Decoder
速度要慢20%。 但是,由于不必通过实现Codable
显式注册数据结构的键,这是一笔不小的费用。 通过添加此解码器,在项目中大大减少了样板的数量。 我应该使用它来节省开发人员时间,但会降低性能吗? 由您决定。
github库中的完整示例代码
英文文章