I'm playing around with the new Codable
protocol in Swift 4. I'm pulling JSON data from a web API via URLSession
. Here's some sample data:
我在Swift 4中使用新的Codable协议。我通过URLSession从Web API中提取JSON数据。这是一些示例数据:
{
"image_id": 1,
"resolutions": ["1920x1200", "1920x1080"]
}
I'd like to decode this into structs like this:
我想将其解码为这样的结构:
struct Resolution: Codable {
let x: Int
let y: Int
}
struct Image: Codable {
let image_id: Int
let resolutions: Array<Resolution>
}
But I'm not sure how to convert the resolution strings in the raw data into separate Int
properties in the Resolution
struct. I've read the official documentation and one or two good tutorials, but these focus on cases where the data can be decoded directly, without any intermediate processing (whereas I need to split the string at the x
, convert the results to Int
s and assign them to Resolution.x
and .y
). This question also seems relevant, but the asker wanted to avoid manual decoding, whereas I'm open to that strategy (although I'm not sure how to go about it myself).
但我不确定如何将原始数据中的分辨率字符串转换为Resolution结构中的单独Int属性。我已经阅读了官方文档和一两个好的教程,但这些都集中在可以直接解码数据的情况下,没有任何中间处理(而我需要将字符串拆分为x,将结果转换为Ints并分配他们到Resolution.x和.y)。这个问题似乎也很重要,但是提问者想避免手动解码,而我对这个策略持开放态度(虽然我不知道如何自己解决)。
My decoding step would look like this:
我的解码步骤如下所示:
let image = try JSONDecoder().decode(Image.self, from data)
Where data
is supplied by URLSession.shared.dataTask(with: URL, completionHandler: Data?, URLResponse?, Error?) -> Void)
数据由URLSession.shared.dataTask提供(带:URL,completionHandler:Data?,URLResponse?,错误?) - > Void)
1 个解决方案
#1
8
For each Resolution
, you want to decode a single string, and then parse that into two Int
components. To decode a single value, you want to get a singleValueContainer()
from the decoder
in your implementation of init(from:)
, and then call .decode(String.self)
on it.
对于每个分辨率,您希望解码单个字符串,然后将其解析为两个Int组件。要解码单个值,您希望在init(from :)的实现中从解码器获取singleValueContainer(),然后在其上调用.decode(String.self)。
You can then use components(separatedBy:)
in order to get the components, and then Int
's string initialiser to convert those to integers, throwing a DecodingError.dataCorruptedError
if you run into an incorrectly formatted string.
然后,您可以使用组件(separatedBy :)来获取组件,然后使用Int的字符串初始化程序将它们转换为整数,如果遇到格式不正确的字符串则抛出DecodingError.dataCorruptedError。
Encoding is simpler, as you can just use string interpolation in order to encode a string into a single value container.
编码更简单,因为您可以使用字符串插值将字符串编码为单个值容器。
For example:
import Foundation
struct Resolution {
let width: Int
let height: Int
}
extension Resolution : Codable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let resolutionString = try container.decode(String.self)
let resolutionComponents = resolutionString.components(separatedBy: "x")
guard resolutionComponents.count == 2,
let width = Int(resolutionComponents[0]),
let height = Int(resolutionComponents[1])
else {
throw DecodingError.dataCorruptedError(in: container, debugDescription:
"""
Incorrectly formatted resolution string "\(resolutionString)". \
It must be in the form <width>x<height>, where width and height are \
representable as Ints
"""
)
}
self.width = width
self.height = height
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode("\(width)x\(height)")
}
}
You can then use it like so:
然后您可以这样使用它:
struct Image : Codable {
let imageID: Int
let resolutions: [Resolution]
private enum CodingKeys : String, CodingKey {
case imageID = "image_id", resolutions
}
}
let jsonData = """
{
"image_id": 1,
"resolutions": ["1920x1200", "1920x1080"]
}
""".data(using: .utf8)!
do {
let image = try JSONDecoder().decode(Image.self, from: jsonData)
print(image)
} catch {
print(error)
}
// Image(imageID: 1, resolutions: [
// Resolution(width: 1920, height: 1200),
// Resolution(width: 1920, height: 1080)
// ]
// )
Note we've defined a custom nested CodingKeys
type in Image
so we can have a camelCase property name for imageID
, but specify that the JSON object key is image_id
.
注意我们在Image中定义了一个自定义嵌套CodingKeys类型,因此我们可以为imageID提供camelCase属性名称,但是指定JSON对象键是image_id。
#1
8
For each Resolution
, you want to decode a single string, and then parse that into two Int
components. To decode a single value, you want to get a singleValueContainer()
from the decoder
in your implementation of init(from:)
, and then call .decode(String.self)
on it.
对于每个分辨率,您希望解码单个字符串,然后将其解析为两个Int组件。要解码单个值,您希望在init(from :)的实现中从解码器获取singleValueContainer(),然后在其上调用.decode(String.self)。
You can then use components(separatedBy:)
in order to get the components, and then Int
's string initialiser to convert those to integers, throwing a DecodingError.dataCorruptedError
if you run into an incorrectly formatted string.
然后,您可以使用组件(separatedBy :)来获取组件,然后使用Int的字符串初始化程序将它们转换为整数,如果遇到格式不正确的字符串则抛出DecodingError.dataCorruptedError。
Encoding is simpler, as you can just use string interpolation in order to encode a string into a single value container.
编码更简单,因为您可以使用字符串插值将字符串编码为单个值容器。
For example:
import Foundation
struct Resolution {
let width: Int
let height: Int
}
extension Resolution : Codable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let resolutionString = try container.decode(String.self)
let resolutionComponents = resolutionString.components(separatedBy: "x")
guard resolutionComponents.count == 2,
let width = Int(resolutionComponents[0]),
let height = Int(resolutionComponents[1])
else {
throw DecodingError.dataCorruptedError(in: container, debugDescription:
"""
Incorrectly formatted resolution string "\(resolutionString)". \
It must be in the form <width>x<height>, where width and height are \
representable as Ints
"""
)
}
self.width = width
self.height = height
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode("\(width)x\(height)")
}
}
You can then use it like so:
然后您可以这样使用它:
struct Image : Codable {
let imageID: Int
let resolutions: [Resolution]
private enum CodingKeys : String, CodingKey {
case imageID = "image_id", resolutions
}
}
let jsonData = """
{
"image_id": 1,
"resolutions": ["1920x1200", "1920x1080"]
}
""".data(using: .utf8)!
do {
let image = try JSONDecoder().decode(Image.self, from: jsonData)
print(image)
} catch {
print(error)
}
// Image(imageID: 1, resolutions: [
// Resolution(width: 1920, height: 1200),
// Resolution(width: 1920, height: 1080)
// ]
// )
Note we've defined a custom nested CodingKeys
type in Image
so we can have a camelCase property name for imageID
, but specify that the JSON object key is image_id
.
注意我们在Image中定义了一个自定义嵌套CodingKeys类型,因此我们可以为imageID提供camelCase属性名称,但是指定JSON对象键是image_id。