使用有时是INT的值,而其他时间则是字符串

问题描述 投票:0回答:6

I有时会在JSON中返回特定的键值(在这种情况下)作为INT,而其他时候它将返回与字符串相同的键值。我如何使用Codable解析JSON?

id

i继续收到此错误消息:
struct GeneralProduct: Codable { var price: Double! var id: String? var name: String! private enum CodingKeys: String, CodingKey { case price = "p" case id = "i" case name = "n" } init(price: Double? = nil, id: String? = nil, name: String? = nil) { self.price = price self.id = id self.name = name } }

。它返回数字的原因是因为ID字段为空,并且当ID字段为空时,它默认为返回0作为代码标识为数字的ID。我基本上可以忽略ID密钥,但是编码并不能让我选择忽略它。处理此问题的最佳方法是什么? json是Here。超级简单

工作

Expected to decode String but found a number instead

Error-由于系统中没有ID,因此它返回0作为默认值,该默认值显然是与字符串相对的数字。

{ "p":2.12, "i":"3k3mkfnk3", "n":"Blue Shirt" }
    

{ "p":2.19, "i":0, "n":"Black Shirt" }

json swift swift4 codable
6个回答
93
投票
let json1 = """
{
"p":2.12,
"i":"3k3mkfnk3",
"n":"Blue Shirt"
}
"""

let json2 = """
{
"p":2.12,
"i":0,
"n":"Blue Shirt"
}
"""


Edit/update

当您的API返回

do { let product = try JSONDecoder().decode(GeneralProduct.self, from: Data(json2.utf8)) print(product.price ?? "nil") print(product.id ?? "nil") print(product.name ?? "nil") } catch { print(error) } 时,您也可以简单地将

nil

分配给您的

id
0

这是一种可能的解决方案
do {
    let value = try container.decode(Int.self, forKey: .id)
    id = value == 0 ? nil : String(value)
} catch DecodingError.typeMismatch {
    id = try container.decode(String.self, forKey: .id)
}
,好事是可以是一个通用的解决方案,而不是仅适用于

38
投票
,而是所有具有相同歧义的

GeneralProduct

struct
这是测试:

struct GeneralProduct: Codable { var price:Double? var id:MetadataType? var name:String? private enum CodingKeys: String, CodingKey { case price = "p" case id = "i" case name = "n" } init(price:Double? = nil, id: MetadataType? = nil, name: String? = nil) { self.price = price self.id = id self.name = name } } enum MetadataType: Codable { case int(Int) case string(String) init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() do { self = try .int(container.decode(Int.self)) } catch DecodingError.typeMismatch { do { self = try .string(container.decode(String.self)) } catch DecodingError.typeMismatch { throw DecodingError.typeMismatch(MetadataType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload not of an expected type")) } } } func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch self { case .int(let int): try container.encode(int) case .string(let string): try container.encode(string) } } }
    

无缝从
let decoder = JSONDecoder()
var json =  "{\"p\":2.19,\"i\":0,\"n\":\"Black Shirt\"}"
var product = try! decoder.decode(GeneralProduct.self, from: json.data(using: .utf8)!)
if let id = product.id {
  print(id) // 0
}

json =  "{\"p\":2.19,\"i\":\"hello world\",\"n\":\"Black Shirt\"}"
product = try! decoder.decode(GeneralProduct.self, from: json.data(using: .utf8)!)
if let id = product.id {
  print(id) // hello world
}

31
投票
到同一属性需要编写一些代码。

,但是,由于语言(属性包装纸)的(某种程度上)的新添加,您可以在任何需要的地方重复使用此逻辑非常容易:

String
可以这样实施属性包装器及其支持代码:

// note this is only `Decodable` struct GeneralProduct: Decodable { var price: Double @Flexible var id: Int // note this is an Int var name: String }

原始答案

您可以通过知道如何从任何基本JSON数据类型解码的字符串上使用包装器:字符串,数字,布尔值:
@propertyWrapper struct Flexible<T: FlexibleDecodable>: Decodable {
    var wrappedValue: T
    
    init(from decoder: Decoder) throws {
        wrappedValue = try T(container: decoder.singleValueContainer())
    }
}

protocol FlexibleDecodable {
    init(container: SingleValueDecodingContainer) throws
}

extension Int: FlexibleDecodable {
    init(container: SingleValueDecodingContainer) throws {
        if let int = try? container.decode(Int.self) {
            self = int
        } else if let string = try? container.decode(String.self), let int = Int(string) {
            self = int
        } else {
            throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath, debugDescription: "Invalid int value"))
        }
    }
}

然后,您可以在结构中使用此新类型。一个较小的缺点是结构的消费者需要进行另一个间接的访问以访问包裹的字符串。但是,可以通过将解码的第属属性宣布为私有,并在公共接口中使用计算的属性来避免:

struct RelaxedString: Codable { let value: String init(_ value: String) { self.value = value } init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() // attempt to decode from all JSON primitives if let str = try? container.decode(String.self) { value = str } else if let int = try? container.decode(Int.self) { value = int.description } else if let double = try? container.decode(Double.self) { value = double.description } else if let bool = try? container.decode(Bool.self) { value = bool.description } else { throw DecodingError.typeMismatch(String.self, .init(codingPath: decoder.codingPath, debugDescription: "")) } } func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(value) } }

上述方法的范围:

不需要编写自定义代码,如果要解码的属性数量增加,这可能会变得乏味

可使用 -

RelaxedString
可以在其他结构中无缝使用

可以从字符串或int解码ID仍然是实现细节的事实,
struct GeneralProduct: Codable {
    var price: Double!
    var _id: RelaxedString?
    var name: String!
    
    var id: String? {
        get { _id?.value }
        set { _id = newValue.map(RelaxedString.init) }
    }

    private enum CodingKeys: String, CodingKey {
        case price = "p"
        case _id = "i"
        case name = "n"
    }

    init(price: Double? = nil, id: String? = nil, name: String? = nil) {
        self.price = price
        self._id = id.map(RelaxedString.init)
        self.name = name
    }
}
的消费者不知道/关心ID可以来自字符串或INT

公共接口公开字符串值,这使消费者代码保持简单,因为它不必处理多种类型的数据
  1. 
    
  2. i创建了这个要点,该要点具有一个可以处理的值wrapperster 以下类型
  3. init(from decoder: Decoder)
  4. https://gist.github.com/amrangry/89097b86514b3477779dd28bba3f23
    
        
  5. 基于@cristik的答案,我提出了另一种使用
RelaxedString

7
投票
GeneralProduct

用法是
case stringValue(String)
case intValue(Int)
case doubleValue(Double)
case boolValue(Bool)

也像-i think -一样工作

@propertyWrapper

5
投票

非常简单地解释它,

即可,如果失败,则会给出默认值。
在这里,值必须为cgfloat,否则是nil。
@propertyWrapper struct StringForcible: Codable { var wrappedValue: String? enum CodingKeys: CodingKey {} init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if let string = try? container.decode(String.self) { wrappedValue = string } else if let integer = try? container.decode(Int.self) { wrappedValue = "\(integer)" } else if let double = try? container.decode(Double.self) { wrappedValue = "\(double)" } else if container.decodeNil() { wrappedValue = nil } else { throw DecodingError.typeMismatch(String.self, .init(codingPath: container.codingPath, debugDescription: "Could not decode incoming value to String. It is not a type of String, Int or Double.")) } } func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(wrappedValue) } init() { self.wrappedValue = nil } }

然后你可以拥有
struct SomeDTO: Codable {
   @StringForcible var id: String? 
}

默认当然可以是您想要的任何东西

struct AnotherDTO: Codable {
    var some: SomeDTO?
}

0
投票

.decode

struct CGFloatOrNil: Codable { let value: CGFloat? init(_ value: CGFloat?) { self.value = value } init(from decoder: Decoder) throws { if let cgf = try? decoder.singleValueContainer().decode(CGFloat.self) { value = cgf } else { value = nil } } func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(value) } }

    

您可以使用此pod

https://github.com/muhammadali2012/model

在您的代码属性上添加这些属性包装器,这些属性不确定。即

struct SomeGraph: Codable { let X: [Date] let Y: [CGFloatOrNil] }

即使您从JSON甚至NILL获得INT,甚至不存在键,您也会将ID作为字符串。
	

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.