16

我正在使用 Swift 4 并尝试解析一些 JSON 数据,这些数据显然在某些情况下可能对同一个键具有不同的类型值,例如:

{
    "type": 0.0
}

{
    "type": "12.44591406"
}

我实际上坚持定义我的struct,因为我不知道如何处理这种情况,因为

struct ItemRaw: Codable {
    let parentType: String

    enum CodingKeys: String, CodingKey {
        case parentType = "type"
    }
}

抛出"Expected to decode String but found a number instead.", 自然地,

struct ItemRaw: Codable {
    let parentType: Float

    enum CodingKeys: String, CodingKey {
        case parentType = "type"
    }
}

相应地抛出"Expected to decode Float but found a string/data instead."

在定义我的struct?

4

4 回答 4

20

在尝试解码/编码 Reddit 列表 JSON 响应中的“已编辑”字段时,我遇到了同样的问题。我创建了一个结构,它表示给定键可能存在的动态类型。键可以是布尔值或整数。

{ "edited": false }
{ "edited": 123456 }

如果您只需要能够解码,只需实现 init(from:)。如果您需要双向使用,则需要实现 encode(to:) 函数。

struct Edited: Codable {
    let isEdited: Bool
    let editedTime: Int

    // Where we determine what type the value is
    init(from decoder: Decoder) throws {
        let container =  try decoder.singleValueContainer()

        // Check for a boolean
        do {
            isEdited = try container.decode(Bool.self)
            editedTime = 0
        } catch {
            // Check for an integer
            editedTime = try container.decode(Int.self)
            isEdited = true
        }
    }

    // We need to go back to a dynamic type, so based on the data we have stored, encode to the proper type
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try isEdited ? container.encode(editedTime) : container.encode(false)
    }
}

在我的 Codable 类中,然后我使用我的结构。

struct Listing: Codable {
    let edited: Edited
}

编辑:针对您的场景的更具体的解决方案

我建议在解码时使用 CodingKey 协议和枚举来存储所有属性。当您创建符合 Codable 的内容时,编译器将为您创建一个私有枚举 CodingKeys。这使您可以根据 JSON 对象属性键来决定要执行的操作。

例如,这是我正在解码的 JSON:

{"type": "1.234"}
{"type": 1.234}

如果您想从 String 转换为 Double 因为您只需要 double 值,只需解码字符串,然后从中创建一个 double。(这就是 Itai Ferber 正在做的事情,然后您还必须使用 try decoder.decode(type:forKey:) 解码所有属性)

struct JSONObjectCasted: Codable {
    let type: Double?

    init(from decoder: Decoder) throws {
        // Decode all fields and store them
        let container = try decoder.container(keyedBy: CodingKeys.self) // The compiler creates coding keys for each property, so as long as the keys are the same as the property names, we don't need to define our own enum.

        // First check for a Double
        do {
            type = try container.decode(Double.self, forKey: .type)

        } catch {
            // The check for a String and then cast it, this will throw if decoding fails
            if let typeValue = Double(try container.decode(String.self, forKey: .type)) {
                type = typeValue
            } else {
                // You may want to throw here if you don't want to default the value(in the case that it you can't have an optional).
                type = nil
            }
        }

        // Perform other decoding for other properties.
    }
}

如果您需要将类型与值一起存储,则可以使用符合 Codable 的枚举而不是结构。然后,您可以使用带有 JSONObjectCustomEnum 的“type”属性的 switch 语句并根据情况执行操作。

struct JSONObjectCustomEnum: Codable {
    let type: DynamicJSONProperty
}

// Where I can represent all the types that the JSON property can be. 
enum DynamicJSONProperty: Codable {
    case double(Double)
    case string(String)

    init(from decoder: Decoder) throws {
        let container =  try decoder.singleValueContainer()

        // Decode the double
        do {
            let doubleVal = try container.decode(Double.self)
            self = .double(doubleVal)
        } catch DecodingError.typeMismatch {
            // Decode the string
            let stringVal = try container.decode(String.self)
            self = .string(stringVal)
        }
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .double(let value):
            try container.encode(value)
        case .string(let value):
            try container.encode(value)
        }
    }
}
于 2017-10-15T23:38:29.593 回答
6

一个简单的解决方案是提供一个实现init(from:),尝试将值解码为 a String,如果由于类型错误而失败,则尝试解码为 a Double

public init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    do {
        self.parentType = try container.decode(String.self, forKey: .parentType)
    } catch DecodingError.typeMismatch {
        let value = try container.decode(Double.self, forKey: .parentType)
        self.parentType = "\(value)"
    }
}
于 2017-10-15T19:38:15.563 回答
1

我必须解码PHP/MySQL/PDO作为字符串给出的 double 值,对于这个用例,我必须扩展KeyedDecodingContainer,如下所示:

extension KeyedDecodingContainer {
    func decode(forKey key: KeyedDecodingContainer.Key) throws -> Double {
        do {
            let str = try self.decode(String.self, forKey: key)
            if let dbl = Double(str) {
                return dbl
            }
        } catch DecodingError.typeMismatch {
            return try self.decode(Double.self, forKey: key)
        }
        let context = DecodingError.Context(codingPath: self.codingPath,
                                            debugDescription: "Wrong Money Value")
        throw DecodingError.typeMismatch(Double.self, context)
    }
}

用法:

let data = """
{"value":"1.2"}
""".data(using: .utf8)!

struct Test: Decodable {
    let value: Double
    enum CodingKeys: String, CodingKey {
        case value
    }
    init(from decoder: Decoder) throws {
        self.value = try decoder.container(keyedBy: CodingKeys.self)
                                .decode(forKey: CodingKeys.value)
    }
}
try JSONDecoder().decode(Test.self, from: data).value
于 2018-07-29T16:56:50.393 回答
0

//输出 Json

{
   "software_id": "10",
    "name": "Kroll"
},
{
   "software_id": 580,
    "name": "Synmed"
}

//可编码结构

struct SoftwareDataModel: Codable {

 var softwareId:MyValue?
 var name:String?

 enum CodingKeys: String, CodingKey{
    case softwareId = "software_id"
    case name
 }
}

MYValue 是 Codable Struct 这有助于将您的数据类型转换为“字符串”在这里我只提到字符串和 Int 数据类型。

enum MyValue: Codable {

 case string(String)

 var stringValue: String? {
    switch self {
    case .string(let s):
        return s
    }
 }

init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    if let x = try? container.decode(String.self) {
        self = .string(x)
        return
    }
    if let x = try? container.decode(Int.self) {
        self = .string("\(x)")
        return
    }
    throw DecodingError.typeMismatch(MyValue.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for MyValue"))
}

func encode(to encoder: Encoder) throws {
    var container = encoder.singleValueContainer()
    switch self {
    case .string(let x):
        try container.encode(x)
    }
}

}

// 如何获取 software_id ?

let softwareId =  Struct_object.softwareId?.stringValue ?? "0"
于 2021-09-17T12:36:25.580 回答