Swift 4 JSON 解码类型更改的可解码最简单方法
Swift 4 JSON Decodable simplest way to decode type change
使用 Swift 4 的 Codable 协议,在日期和数据转换策略下有很大的水平。
鉴于 JSON:
{
"name": "Bob",
"age": 25,
"tax_rate": "4.25"
}
我想强制转换成如下结构
struct ExampleJson: Decodable {
var name: String
var age: Int
var taxRate: Float
enum CodingKeys: String, CodingKey {
case name, age
case taxRate = "tax_rate"
}
}
日期解码策略可以将基于字符串的日期转换为日期。
有没有什么东西可以用基于 String 的 Float
否则我一直坚持使用 CodingKey 引入字符串并使用计算 get:
enum CodingKeys: String, CodingKey {
case name, age
case sTaxRate = "tax_rate"
}
var sTaxRate: String
var taxRate: Float { return Float(sTaxRate) ?? 0.0 }
这种情况让我做的维护工作比看起来需要的多。
这是最简单的方式还是其他类型转换有类似DateDecodingStrategy的东西?
更新:我应该注意:我也走了覆盖
的路线
init(from decoder:Decoder)
但这是相反的方向,因为它迫使我为自己做这一切。
您始终可以手动解码。所以,给定:
{
"name": "Bob",
"age": 25,
"tax_rate": "4.25"
}
你可以这样做:
struct Example: Codable {
let name: String
let age: Int
let taxRate: Float
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .name)
age = try values.decode(Int.self, forKey: .age)
guard let rate = try Float(values.decode(String.self, forKey: .taxRate)) else {
throw DecodingError.dataCorrupted(.init(codingPath: [CodingKeys.taxRate], debugDescription: "Expecting string representation of Float"))
}
taxRate = rate
}
enum CodingKeys: String, CodingKey {
case name, age
case taxRate = "tax_rate"
}
}
参见Encoding and Decoding Custom Types中的手动编码和解码。
但我同意,考虑到有多少 JSON 来源错误地 return 数值作为字符串,似乎应该有一个更优雅的字符串转换过程等同于 DateDecodingStrategy
.
不幸的是,我认为当前 JSONDecoder
API 中不存在这样的选项。只有一个选项可以 convert exceptional floating-point values 往返于字符串表示形式。
手动解码的另一种可能解决方案是为任何 LosslessStringConvertible
定义一个 Codable
包装器类型,它可以编码到其 String
表示并从其解码:
struct StringCodableMap<Decoded : LosslessStringConvertible> : Codable {
var decoded: Decoded
init(_ decoded: Decoded) {
self.decoded = decoded
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let decodedString = try container.decode(String.self)
guard let decoded = Decoded(decodedString) else {
throw DecodingError.dataCorruptedError(
in: container, debugDescription: """
The string \(decodedString) is not representable as a \(Decoded.self)
"""
)
}
self.decoded = decoded
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(decoded.description)
}
}
然后你就可以有一个这种类型的 属性 并使用自动生成的 Codable
一致性:
struct Example : Codable {
var name: String
var age: Int
var taxRate: StringCodableMap<Float>
private enum CodingKeys: String, CodingKey {
case name, age
case taxRate = "tax_rate"
}
}
尽管很遗憾,现在您必须使用 taxRate.decoded
来与 Float
值进行交互。
但是你总是可以定义一个简单的转发计算 属性 来缓解这个问题:
struct Example : Codable {
var name: String
var age: Int
private var _taxRate: StringCodableMap<Float>
var taxRate: Float {
get { return _taxRate.decoded }
set { _taxRate.decoded = newValue }
}
private enum CodingKeys: String, CodingKey {
case name, age
case _taxRate = "tax_rate"
}
}
虽然这仍然没有它真正应该的那样圆滑——希望 JSONDecoder
API 的更高版本将包含更多自定义解码选项,或者有能力表达Codable
API 本身的类型转换。
然而,创建包装器类型的一个优点是它也可以用于简化手动解码和编码。例如,手动解码:
struct Example : Decodable {
var name: String
var age: Int
var taxRate: Float
private enum CodingKeys: String, CodingKey {
case name, age
case taxRate = "tax_rate"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.age = try container.decode(Int.self, forKey: .age)
self.taxRate = try container.decode(StringCodableMap<Float>.self,
forKey: .taxRate).decoded
}
}
您可以使用 lazy var
将 属性 转换为另一种类型:
struct ExampleJson: Decodable {
var name: String
var age: Int
lazy var taxRate: Float = {
Float(self.tax_rate)!
}()
private var tax_rate: String
}
这种方法的一个缺点是,如果要访问 taxRate
,则无法定义 let
常量,因为第一次访问它时,您正在改变结构。
// Cannot use `let` here
var example = try! JSONDecoder().decode(ExampleJson.self, from: data)
使用Swift5.1,您可以选择以下三种方式之一来解决您的问题。
#1。使用 Decodable
init(from:)
初始化器
当您需要将单个结构、枚举或 class 从 String
转换为 Float
时,请使用此策略。
import Foundation
struct ExampleJson: Decodable {
var name: String
var age: Int
var taxRate: Float
enum CodingKeys: String, CodingKey {
case name, age, taxRate = "tax_rate"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: CodingKeys.name)
age = try container.decode(Int.self, forKey: CodingKeys.age)
let taxRateString = try container.decode(String.self, forKey: CodingKeys.taxRate)
guard let taxRateFloat = Float(taxRateString) else {
let context = DecodingError.Context(codingPath: container.codingPath + [CodingKeys.taxRate], debugDescription: "Could not parse json key to a Float object")
throw DecodingError.dataCorrupted(context)
}
taxRate = taxRateFloat
}
}
用法:
import Foundation
let jsonString = """
{
"name": "Bob",
"age": 25,
"tax_rate": "4.25"
}
"""
let data = jsonString.data(using: String.Encoding.utf8)!
let decoder = JSONDecoder()
let exampleJson = try! decoder.decode(ExampleJson.self, from: data)
dump(exampleJson)
/*
prints:
▿ __lldb_expr_126.ExampleJson
- name: "Bob"
- age: 25
- taxRate: 4.25
*/
#2。使用中间模型
当您的 JSON 中有许多嵌套键或者当您需要从 [=55= 转换许多键(例如从 String
到 Float
)时使用此策略].
import Foundation
fileprivate struct PrivateExampleJson: Decodable {
var name: String
var age: Int
var taxRate: String
enum CodingKeys: String, CodingKey {
case name, age, taxRate = "tax_rate"
}
}
struct ExampleJson: Decodable {
var name: String
var age: Int
var taxRate: Float
init(from decoder: Decoder) throws {
let privateExampleJson = try PrivateExampleJson(from: decoder)
name = privateExampleJson.name
age = privateExampleJson.age
guard let convertedTaxRate = Float(privateExampleJson.taxRate) else {
let context = DecodingError.Context(codingPath: [], debugDescription: "Could not parse json key to a Float object")
throw DecodingError.dataCorrupted(context)
}
taxRate = convertedTaxRate
}
}
用法:
import Foundation
let jsonString = """
{
"name": "Bob",
"age": 25,
"tax_rate": "4.25"
}
"""
let data = jsonString.data(using: String.Encoding.utf8)!
let decoder = JSONDecoder()
let exampleJson = try! decoder.decode(ExampleJson.self, from: data)
dump(exampleJson)
/*
prints:
▿ __lldb_expr_126.ExampleJson
- name: "Bob"
- age: 25
- taxRate: 4.25
*/
#3。使用 KeyedDecodingContainer
扩展方法
从某些 JSON 键的类型转换为模型的 属性 类型(例如 String
到 Float
)时使用此策略是应用程序中的常见模式.
import Foundation
extension KeyedDecodingContainer {
func decode(_ type: Float.Type, forKey key: Key) throws -> Float {
if let stringValue = try? self.decode(String.self, forKey: key) {
guard let floatValue = Float(stringValue) else {
let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Could not parse json key to a Float object")
throw DecodingError.dataCorrupted(context)
}
return floatValue
} else {
let doubleValue = try self.decode(Double.self, forKey: key)
return Float(doubleValue)
}
}
}
struct ExampleJson: Decodable {
var name: String
var age: Int
var taxRate: Float
enum CodingKeys: String, CodingKey {
case name, age, taxRate = "tax_rate"
}
}
用法:
import Foundation
let jsonString = """
{
"name": "Bob",
"age": 25,
"tax_rate": "4.25"
}
"""
let data = jsonString.data(using: String.Encoding.utf8)!
let decoder = JSONDecoder()
let exampleJson = try! decoder.decode(ExampleJson.self, from: data)
dump(exampleJson)
/*
prints:
▿ __lldb_expr_126.ExampleJson
- name: "Bob"
- age: 25
- taxRate: 4.25
*/
如何使用 JSON可解码 Swift 4:
- 获取 JSON 响应并创建结构
- 在结构
中符合可解码class
- this GitHub project中的其他步骤,一个简单的例子
我知道这是一个非常晚的答案,但我只是在几天前才开始研究 Codable
。我遇到了类似的问题。
为了将字符串转换为浮点数,可以写一个扩展到KeyedDecodingContainer
,然后从init(from decoder: Decoder){}
调用扩展中的方法
本期提到的问题,看我下面写的扩展;
extension KeyedDecodingContainer {
func decodeIfPresent(_ type: Float.Type, forKey key: K, transformFrom: String.Type) throws -> Float? {
guard let value = try decodeIfPresent(transformFrom, forKey: key) else {
return nil
}
return Float(value)
}
func decode(_ type: Float.Type, forKey key: K, transformFrom: String.Type) throws -> Float {
guard let valueAsString = try? decode(transformFrom, forKey: key),
let value = Float(valueAsString) else {
throw DecodingError.typeMismatch(
type,
DecodingError.Context(
codingPath: codingPath,
debugDescription: "Decoding of \(type) from \(transformFrom) failed"
)
)
}
return value
}
}
您可以从 init(from decoder: Decoder)
方法中调用此方法。请参阅下面的示例;
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
taxRate = try container.decodeIfPresent(Float.self, forKey: .taxRate, transformFrom: String.self)
}
事实上,您可以使用这种方法将任何类型的数据转换为任何其他类型。您可以转换 string to Date
、string to bool
、string to float
、float to int
等
实际上,要将字符串转换为 Date 对象,我更喜欢这种方法而不是 JSONEncoder().dateEncodingStrategy
,因为如果编写得当,您可以在同一响应中包含不同的日期格式。
希望我有所帮助。
根据 @Neil 的建议将解码方法更新为 return 非可选。
我使用了 Suran 的版本,但将其更新为 return decode() 的非可选值。对我来说,这是最优雅的版本。 Swift5.2.
extension KeyedDecodingContainer {
func decodeIfPresent(_ type: Float.Type, forKey key: K, transformFrom: String.Type) throws -> Float? {
guard let value = try decodeIfPresent(transformFrom, forKey: key) else {
return nil
}
return Float(value)
}
func decode(_ type: Float.Type, forKey key: K, transformFrom: String.Type) throws -> Float {
guard let str = try? decode(transformFrom, forKey: key),
let value = Float(str) else {
throw DecodingError.typeMismatch(Int.self, DecodingError.Context(codingPath: codingPath, debugDescription: "Decoding of \(type) from \(transformFrom) failed"))
}
return value
}
}
以上选项仅处理给定字段始终为字符串的情况。很多次我遇到过输出一次是字符串,有时是数字的 API。所以这是我解决这个问题的建议。由您来改变它以抛出异常或将解码值设置为 nil。
var json = """
{
"title": "Apple",
"id": "20"
}
""";
var jsonWithInt = """
{
"title": "Apple",
"id": 20
}
""";
struct DecodableNumberFromStringToo<T: LosslessStringConvertible & Decodable & Numeric>: Decodable {
var value: T
init(from decoder: Decoder) {
print("Decoding")
if let container = try? decoder.singleValueContainer() {
if let val = try? container.decode(T.self) {
value = val
return
}
if let str = try? container.decode(String.self) {
value = T.init(str) ?? T.zero
return
}
}
value = T.zero
}
}
struct MyData: Decodable {
let title: String
let _id: DecodableNumberFromStringToo<Int>
enum CodingKeys: String, CodingKey {
case title, _id = "id"
}
var id: Int {
return _id.value
}
}
do {
let parsedJson = try JSONDecoder().decode(MyData.self, from: json.data(using: .utf8)!)
print(parsedJson.id)
} catch {
print(error as? DecodingError)
}
do {
let parsedJson = try JSONDecoder().decode(MyData.self, from: jsonWithInt.data(using: .utf8)!)
print(parsedJson.id)
} catch {
print(error as? DecodingError)
}
使用 Swift 4 的 Codable 协议,在日期和数据转换策略下有很大的水平。
鉴于 JSON:
{
"name": "Bob",
"age": 25,
"tax_rate": "4.25"
}
我想强制转换成如下结构
struct ExampleJson: Decodable {
var name: String
var age: Int
var taxRate: Float
enum CodingKeys: String, CodingKey {
case name, age
case taxRate = "tax_rate"
}
}
日期解码策略可以将基于字符串的日期转换为日期。
有没有什么东西可以用基于 String 的 Float
否则我一直坚持使用 CodingKey 引入字符串并使用计算 get:
enum CodingKeys: String, CodingKey {
case name, age
case sTaxRate = "tax_rate"
}
var sTaxRate: String
var taxRate: Float { return Float(sTaxRate) ?? 0.0 }
这种情况让我做的维护工作比看起来需要的多。
这是最简单的方式还是其他类型转换有类似DateDecodingStrategy的东西?
更新:我应该注意:我也走了覆盖
的路线init(from decoder:Decoder)
但这是相反的方向,因为它迫使我为自己做这一切。
您始终可以手动解码。所以,给定:
{
"name": "Bob",
"age": 25,
"tax_rate": "4.25"
}
你可以这样做:
struct Example: Codable {
let name: String
let age: Int
let taxRate: Float
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .name)
age = try values.decode(Int.self, forKey: .age)
guard let rate = try Float(values.decode(String.self, forKey: .taxRate)) else {
throw DecodingError.dataCorrupted(.init(codingPath: [CodingKeys.taxRate], debugDescription: "Expecting string representation of Float"))
}
taxRate = rate
}
enum CodingKeys: String, CodingKey {
case name, age
case taxRate = "tax_rate"
}
}
参见Encoding and Decoding Custom Types中的手动编码和解码。
但我同意,考虑到有多少 JSON 来源错误地 return 数值作为字符串,似乎应该有一个更优雅的字符串转换过程等同于 DateDecodingStrategy
.
不幸的是,我认为当前 JSONDecoder
API 中不存在这样的选项。只有一个选项可以 convert exceptional floating-point values 往返于字符串表示形式。
手动解码的另一种可能解决方案是为任何 LosslessStringConvertible
定义一个 Codable
包装器类型,它可以编码到其 String
表示并从其解码:
struct StringCodableMap<Decoded : LosslessStringConvertible> : Codable {
var decoded: Decoded
init(_ decoded: Decoded) {
self.decoded = decoded
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let decodedString = try container.decode(String.self)
guard let decoded = Decoded(decodedString) else {
throw DecodingError.dataCorruptedError(
in: container, debugDescription: """
The string \(decodedString) is not representable as a \(Decoded.self)
"""
)
}
self.decoded = decoded
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(decoded.description)
}
}
然后你就可以有一个这种类型的 属性 并使用自动生成的 Codable
一致性:
struct Example : Codable {
var name: String
var age: Int
var taxRate: StringCodableMap<Float>
private enum CodingKeys: String, CodingKey {
case name, age
case taxRate = "tax_rate"
}
}
尽管很遗憾,现在您必须使用 taxRate.decoded
来与 Float
值进行交互。
但是你总是可以定义一个简单的转发计算 属性 来缓解这个问题:
struct Example : Codable {
var name: String
var age: Int
private var _taxRate: StringCodableMap<Float>
var taxRate: Float {
get { return _taxRate.decoded }
set { _taxRate.decoded = newValue }
}
private enum CodingKeys: String, CodingKey {
case name, age
case _taxRate = "tax_rate"
}
}
虽然这仍然没有它真正应该的那样圆滑——希望 JSONDecoder
API 的更高版本将包含更多自定义解码选项,或者有能力表达Codable
API 本身的类型转换。
然而,创建包装器类型的一个优点是它也可以用于简化手动解码和编码。例如,手动解码:
struct Example : Decodable {
var name: String
var age: Int
var taxRate: Float
private enum CodingKeys: String, CodingKey {
case name, age
case taxRate = "tax_rate"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.age = try container.decode(Int.self, forKey: .age)
self.taxRate = try container.decode(StringCodableMap<Float>.self,
forKey: .taxRate).decoded
}
}
您可以使用 lazy var
将 属性 转换为另一种类型:
struct ExampleJson: Decodable {
var name: String
var age: Int
lazy var taxRate: Float = {
Float(self.tax_rate)!
}()
private var tax_rate: String
}
这种方法的一个缺点是,如果要访问 taxRate
,则无法定义 let
常量,因为第一次访问它时,您正在改变结构。
// Cannot use `let` here
var example = try! JSONDecoder().decode(ExampleJson.self, from: data)
使用Swift5.1,您可以选择以下三种方式之一来解决您的问题。
#1。使用 Decodable
init(from:)
初始化器
当您需要将单个结构、枚举或 class 从 String
转换为 Float
时,请使用此策略。
import Foundation
struct ExampleJson: Decodable {
var name: String
var age: Int
var taxRate: Float
enum CodingKeys: String, CodingKey {
case name, age, taxRate = "tax_rate"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: CodingKeys.name)
age = try container.decode(Int.self, forKey: CodingKeys.age)
let taxRateString = try container.decode(String.self, forKey: CodingKeys.taxRate)
guard let taxRateFloat = Float(taxRateString) else {
let context = DecodingError.Context(codingPath: container.codingPath + [CodingKeys.taxRate], debugDescription: "Could not parse json key to a Float object")
throw DecodingError.dataCorrupted(context)
}
taxRate = taxRateFloat
}
}
用法:
import Foundation
let jsonString = """
{
"name": "Bob",
"age": 25,
"tax_rate": "4.25"
}
"""
let data = jsonString.data(using: String.Encoding.utf8)!
let decoder = JSONDecoder()
let exampleJson = try! decoder.decode(ExampleJson.self, from: data)
dump(exampleJson)
/*
prints:
▿ __lldb_expr_126.ExampleJson
- name: "Bob"
- age: 25
- taxRate: 4.25
*/
#2。使用中间模型
当您的 JSON 中有许多嵌套键或者当您需要从 [=55= 转换许多键(例如从 String
到 Float
)时使用此策略].
import Foundation
fileprivate struct PrivateExampleJson: Decodable {
var name: String
var age: Int
var taxRate: String
enum CodingKeys: String, CodingKey {
case name, age, taxRate = "tax_rate"
}
}
struct ExampleJson: Decodable {
var name: String
var age: Int
var taxRate: Float
init(from decoder: Decoder) throws {
let privateExampleJson = try PrivateExampleJson(from: decoder)
name = privateExampleJson.name
age = privateExampleJson.age
guard let convertedTaxRate = Float(privateExampleJson.taxRate) else {
let context = DecodingError.Context(codingPath: [], debugDescription: "Could not parse json key to a Float object")
throw DecodingError.dataCorrupted(context)
}
taxRate = convertedTaxRate
}
}
用法:
import Foundation
let jsonString = """
{
"name": "Bob",
"age": 25,
"tax_rate": "4.25"
}
"""
let data = jsonString.data(using: String.Encoding.utf8)!
let decoder = JSONDecoder()
let exampleJson = try! decoder.decode(ExampleJson.self, from: data)
dump(exampleJson)
/*
prints:
▿ __lldb_expr_126.ExampleJson
- name: "Bob"
- age: 25
- taxRate: 4.25
*/
#3。使用 KeyedDecodingContainer
扩展方法
从某些 JSON 键的类型转换为模型的 属性 类型(例如 String
到 Float
)时使用此策略是应用程序中的常见模式.
import Foundation
extension KeyedDecodingContainer {
func decode(_ type: Float.Type, forKey key: Key) throws -> Float {
if let stringValue = try? self.decode(String.self, forKey: key) {
guard let floatValue = Float(stringValue) else {
let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Could not parse json key to a Float object")
throw DecodingError.dataCorrupted(context)
}
return floatValue
} else {
let doubleValue = try self.decode(Double.self, forKey: key)
return Float(doubleValue)
}
}
}
struct ExampleJson: Decodable {
var name: String
var age: Int
var taxRate: Float
enum CodingKeys: String, CodingKey {
case name, age, taxRate = "tax_rate"
}
}
用法:
import Foundation
let jsonString = """
{
"name": "Bob",
"age": 25,
"tax_rate": "4.25"
}
"""
let data = jsonString.data(using: String.Encoding.utf8)!
let decoder = JSONDecoder()
let exampleJson = try! decoder.decode(ExampleJson.self, from: data)
dump(exampleJson)
/*
prints:
▿ __lldb_expr_126.ExampleJson
- name: "Bob"
- age: 25
- taxRate: 4.25
*/
如何使用 JSON可解码 Swift 4:
- 获取 JSON 响应并创建结构
- 在结构 中符合可解码class
- this GitHub project中的其他步骤,一个简单的例子
我知道这是一个非常晚的答案,但我只是在几天前才开始研究 Codable
。我遇到了类似的问题。
为了将字符串转换为浮点数,可以写一个扩展到KeyedDecodingContainer
,然后从init(from decoder: Decoder){}
本期提到的问题,看我下面写的扩展;
extension KeyedDecodingContainer {
func decodeIfPresent(_ type: Float.Type, forKey key: K, transformFrom: String.Type) throws -> Float? {
guard let value = try decodeIfPresent(transformFrom, forKey: key) else {
return nil
}
return Float(value)
}
func decode(_ type: Float.Type, forKey key: K, transformFrom: String.Type) throws -> Float {
guard let valueAsString = try? decode(transformFrom, forKey: key),
let value = Float(valueAsString) else {
throw DecodingError.typeMismatch(
type,
DecodingError.Context(
codingPath: codingPath,
debugDescription: "Decoding of \(type) from \(transformFrom) failed"
)
)
}
return value
}
}
您可以从 init(from decoder: Decoder)
方法中调用此方法。请参阅下面的示例;
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
taxRate = try container.decodeIfPresent(Float.self, forKey: .taxRate, transformFrom: String.self)
}
事实上,您可以使用这种方法将任何类型的数据转换为任何其他类型。您可以转换 string to Date
、string to bool
、string to float
、float to int
等
实际上,要将字符串转换为 Date 对象,我更喜欢这种方法而不是 JSONEncoder().dateEncodingStrategy
,因为如果编写得当,您可以在同一响应中包含不同的日期格式。
希望我有所帮助。
根据 @Neil 的建议将解码方法更新为 return 非可选。
我使用了 Suran 的版本,但将其更新为 return decode() 的非可选值。对我来说,这是最优雅的版本。 Swift5.2.
extension KeyedDecodingContainer {
func decodeIfPresent(_ type: Float.Type, forKey key: K, transformFrom: String.Type) throws -> Float? {
guard let value = try decodeIfPresent(transformFrom, forKey: key) else {
return nil
}
return Float(value)
}
func decode(_ type: Float.Type, forKey key: K, transformFrom: String.Type) throws -> Float {
guard let str = try? decode(transformFrom, forKey: key),
let value = Float(str) else {
throw DecodingError.typeMismatch(Int.self, DecodingError.Context(codingPath: codingPath, debugDescription: "Decoding of \(type) from \(transformFrom) failed"))
}
return value
}
}
以上选项仅处理给定字段始终为字符串的情况。很多次我遇到过输出一次是字符串,有时是数字的 API。所以这是我解决这个问题的建议。由您来改变它以抛出异常或将解码值设置为 nil。
var json = """
{
"title": "Apple",
"id": "20"
}
""";
var jsonWithInt = """
{
"title": "Apple",
"id": 20
}
""";
struct DecodableNumberFromStringToo<T: LosslessStringConvertible & Decodable & Numeric>: Decodable {
var value: T
init(from decoder: Decoder) {
print("Decoding")
if let container = try? decoder.singleValueContainer() {
if let val = try? container.decode(T.self) {
value = val
return
}
if let str = try? container.decode(String.self) {
value = T.init(str) ?? T.zero
return
}
}
value = T.zero
}
}
struct MyData: Decodable {
let title: String
let _id: DecodableNumberFromStringToo<Int>
enum CodingKeys: String, CodingKey {
case title, _id = "id"
}
var id: Int {
return _id.value
}
}
do {
let parsedJson = try JSONDecoder().decode(MyData.self, from: json.data(using: .utf8)!)
print(parsedJson.id)
} catch {
print(error as? DecodingError)
}
do {
let parsedJson = try JSONDecoder().decode(MyData.self, from: jsonWithInt.data(using: .utf8)!)
print(parsedJson.id)
} catch {
print(error as? DecodingError)
}