首页 > 解决方案 > 基于编码值Swift解码类

问题描述

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

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 数据中的 kind 属性让解码器输出一系列车辆,但输入车辆。[Project.Car, Project.MotorCycle]如果可能的话,按照例子。

标签: jsonswiftdecodingcodable

解决方案


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

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

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

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

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

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

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

上面的代码输出:

福特, 轮子: 4, 类型: Car
Yamaha, 轮子: 2, 类型: 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)
}

推荐阅读