根据编码值 Swift 解码 class

Decoding a class based on encoded value Swift

我正在尝试根据编码数据的内容解码特定的 class。

class Vehicle: Codable {
    enum Kind: Int, Codable {
        case car = 0, motorcycle = 1
    }
    
    let brand: String
    let numberOfWheels: Int
}

class Car: Vehicle {}
class MotorCycle: Vehicle {}

如您所见,我有一个用于编码和解码车辆的通用 Vehicle 类型。这适用于如下所示的基本解码。

let car = "{\"kind\": 0, \"brand\": \"Ford\", \"number_of_wheels\": 4}".data(using: .utf8)!
let motorCycle = "{\"kind\": 1, \"brand\": \"Yamaha\", \"number_of_wheels\": 2}".data(using: .utf8)!

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase

// Outputs a Project.Car
let ford = try! decoder.decode(Car.self, from: car)
// Outputs a Project.MotorCycle
let yamaha = try! decoder.decode(MotorCycle.self, from: motorCycle)

但是,如果我想解码一组车辆,但将它们解码为特定类型怎么办?

let combined = "[{\"kind\": 0, \"brand\": \"Ford\", \"number_of_wheels\": 4}, {\"kind\": 1, \"brand\": \"Yamaha\", \"number_of_wheels\": 2}]".data(using: .utf8)!

// Outputs [Project.Vehicle, Project.Vehicle]
print(try! decoder.decode([Vehicle].self, from: combined))

如何使用 JSON 数据中的种类 属性 让解码器输出车辆数组,但类型为车辆。根据示例 [Project.Car, Project.MotorCycle] 如果可能的话。

您没有 class 中定义的种类 属性。 另外,Car和MotorCycle没有区别 我在操场上测试了这个,似乎做你想做的事。

    class Vehicle: Codable {
        enum Kind: Int, Codable {
            case car = 0, motorcycle = 1
        }
        let kind: Kind
        let brand: String
        let numberOfWheels: Int
    }
    
    class Car: Vehicle {}
    class MotorCycle: Vehicle {}

    let car = "{\"kind\": 0, \"brand\": \"Ford\", \"number_of_wheels\": 4}".data(using: .utf8)!
    let motorCycle = "{\"kind\": 1, \"brand\": \"Yamaha\", \"number_of_wheels\": 2}".data(using: .utf8)!
    
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    
    // Outputs a Project.Car
    let ford = try! decoder.decode(Car.self, from: car)
    // Outputs a Project.MotorCycle
    let yamaha = try! decoder.decode(MotorCycle.self, from: motorCycle)
    //But what if I want to decode an array of vehicles, but have them decoded as a specific type?
    
    let combined = "[{\"kind\": 0, \"brand\": \"Ford\", \"number_of_wheels\": 4}, {\"kind\": 1, \"brand\": \"Yamaha\", \"number_of_wheels\": 2}]".data(using: .utf8)!
    
    // Outputs [Project.Vehicle, Project.Vehicle]
    let allVeh = try! decoder.decode([Vehicle].self, from: combined)
    for veh in allVeh where veh.kind == Vehicle.Kind.motorcycle { // } is MotorCycle {
        print(veh.brand, type(of: veh))
    }

它给出: 雅马哈车辆

你也可以这样写

let allMotorcycles = allVeh.filter {[=11=].kind == Vehicle.Kind.motorcycle}
for moto in allMotorcycles  {
    print(moto.brand, type(of: moto))
}

这是一个不使用 Codable 的替代解决方案。我还做了一些更改并引入了一个协议而不是超级 class.

protocol Vehicle: CustomStringConvertible {
    var brand: String { get set }
    var numberOfWheels: Int { get set }
}

extension Vehicle {
    var description: String {
        "\(brand), wheels: \(numberOfWheels), type: \(type(of:self))"
    }
}

不是那么重要,但我将类型从 class 更改为 struct

struct Car: Vehicle {
    var brand: String
    var numberOfWheels: Int
}

struct MotorCycle: Vehicle {
    var brand: String
    var numberOfWheels: Int
}

然后分两步将 json 转换为 Vehicle 数组,使用 JSONSerialization 进行解码,然后 reduce(into:) 创建对象

do {
    if let array = try JSONSerialization.jsonObject(with: combined) as? [[String: Any]] {
        let vehicles = array.reduce(into: [Vehicle]()) {
            if let kindValue = ["kind"] as? Int,
               let kind = VehicleKind(rawValue: kindValue),
               let brand = ["brand"] as? String,
               let numberOfWheels = ["number_of_wheels"] as? Int {
                switch kind {
                case .car:
                    [=12=].append(Car(brand: brand, numberOfWheels: numberOfWheels))
                case .motorcycle:
                    [=12=].append(MotorCycle(brand: brand, numberOfWheels: numberOfWheels))
                }
            }
        }
        for vehicle in vehicles {
            print(vehicle)
        }
    }
} catch {
    print(error)
}

以上代码输出:

Ford, wheels: 4, type: Car
Yamaha, wheels: 2, type: MotorCycle


更新。可编码版本

我还通过引入一种用于解码的单独类型来设法提出一个 Codable 解决方案。设置与之前相同,有一个协议和两个结构。

然后我介绍一个特定的解码类型(但它当然也可以扩展为编码)实现自定义 init(from:)

struct JsonVehicle: Decodable {
    let vehicle: Vehicle

    enum CodingKeys: String, CodingKey {
        case kind
        case brand
        case numberOfWheels
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        let kind = try container.decode(VehicleKind.self, forKey: .kind)
        let brand = try container.decode(String.self, forKey: .brand)
        let wheels = try container.decode(Int.self, forKey: .numberOfWheels)
        switch kind {
        case .car:
            vehicle = Car(brand: brand, numberOfWheels: wheels)
        case .motorcycle:
            vehicle = MotorCycle(brand: brand, numberOfWheels: wheels)
        }
    }
}

同样,最终结果是通过 2 个步骤实现的

do {
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase

    let result = try decoder.decode([JsonVehicle].self, from: combined)
    let vehicles = result.map(\.vehicle)
} catch {
    print(error)
}