Swift 结构:处理单个属性的多种类型 [英] Swift structures: handling multiple types for a single property

查看:19
本文介绍了Swift 结构:处理单个属性的多种类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am using Swift 4 and trying to parse some JSON data which apparently in some cases can have different type values for the same key, e.g.:

{
    "type": 0.0
}

{
    "type": "12.44591406"
}

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

I am actually stuck with defining my struct because I cannot figure out how to handle this case because

struct ItemRaw: Codable {
    let parentType: String

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

throws 期望解码字符串,但找到了一个数字.",很自然地,

throws "Expected to decode String but found a number instead.", and naturally,

struct ItemRaw: Codable {
    let parentType: Float

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

抛出 预期解码 Float 但找到了一个字符串/数据." 相应地.

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

How can I handle this (and similar) cases when defining my struct?

推荐答案

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

I ran into the same issue when trying to decode/encode the "edited" field on a Reddit Listing JSON response. I created a struct that represents the dynamic type that could exist for the given key. The key can have either a boolean or an integer.

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

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

If you only need to be able to decode, just implement init(from:). If you need to go both ways, you will need to implement encode(to:) function.

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 类中,然后我使用我的结构.

Inside my Codable class, I then use my struct.

struct Listing: Codable {
    let edited: Edited
}

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

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

I recommend using the CodingKey protocol and an enum to store all the properties when decoding. When you create something that conforms to Codable the compiler will create a private enum CodingKeys for you. This lets you decide on what to do based on the JSON Object property key.

举个例子,这是我正在解码的JSON:

Just for example, this is the JSON I am decoding:

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

如果您想从字符串转换为双精度值,因为您只需要双精度值,只需对字符串进行解码,然后从中创建双精度值.(这就是 Itai Ferber 正在做的,然后您还必须使用 trydecoder.decode(type:forKey:) 解码所有属性)

If you want to cast from a String to a Double because you only want the double value, just decode the string and then create a double from it. (This is what Itai Ferber is doing, you would then have to decode all properties as well using 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 语句,并根据案例执行操作.

If you need to store the type along with the value, you can use an enum that conforms to Codable instead of the struct. You could then just use a switch statement with the "type" property of JSONObjectCustomEnum and perform actions based upon the case.

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)
        }
    }
}

这篇关于Swift 结构:处理单个属性的多种类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆