Swift结构:为单个属性处理多种类型

err*_*ata 7 json data-structures swift swift-structs swift4

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

{
    "type": 0.0
}
Run Code Online (Sandbox Code Playgroud)

{
    "type": "12.44591406"
}
Run Code Online (Sandbox Code Playgroud)

我实际上坚持定义我,struct因为我无法弄清楚如何处理这种情况,因为

struct ItemRaw: Codable {
    let parentType: String

    enum CodingKeys: String, CodingKey {
        case parentType = "type"
    }
}
Run Code Online (Sandbox Code Playgroud)

投掷"Expected to decode String but found a number instead.",当然,

struct ItemRaw: Codable {
    let parentType: Float

    enum CodingKeys: String, CodingKey {
        case parentType = "type"
    }
}
Run Code Online (Sandbox Code Playgroud)

因此投掷"Expected to decode Float but found a string/data instead.".

在定义我的时候如何处理这个(和类似的)情况struct呢?

Lat*_*tsu 7

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

{ "edited": false }
{ "edited": 123456 }
Run Code Online (Sandbox Code Playgroud)

如果您只需要能够解码,只需实现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)
    }
}
Run Code Online (Sandbox Code Playgroud)

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

struct Listing: Codable {
    let edited: Edited
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

{"type": "1.234"}
{"type": 1.234}
Run Code Online (Sandbox Code Playgroud)

如果你想从String转换为Double,因为你只需要double值,只需解码字符串然后从中创建一个double.(这就是Itai Ferber正在做的事情,你必须使用try decoder.decode解码所有属性(类型: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.
    }
}
Run Code Online (Sandbox Code Playgroud)

如果需要将类型与值一起存储,则可以使用符合Codable而不是struct的枚举.然后,您可以使用带有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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Ita*_*ber 5

一个简单的解决方案是提供一种实现init(from:),尝试将值解码为a String,如果由于类型错误而失败,则尝试解码为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)"
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @errata呃,好的.如果您有其他属性,是的,您还需要解码并初始化它们,就像在Swift中的任何其他初始化程序一样. (4认同)
  • 虽然这个答案是正确的(只有一个很小的例外),但只有当我的`struct`包含一个属性时它才会起作用.一旦我开始添加更多属性,我就会遇到错误`从初始化程序返回而不初始化所有存储的属性`.我现在明白了这个错误意味着什么,也许我可以在我的问题中说明我将拥有其他属性......不过我会赞成你,但我会接受另一个答案,因为它是在整个背景下并且奖励一个有声誉的新用户:)非常感谢您的帮助! (2认同)