json - 根据编码值 Swift 解码类

标签 json swift decoding codable

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

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](如果可能的话)。

最佳答案

这是一个不使用Codable的替代解决方案。我还做了一些更改并引入了协议(protocol)而不是父类(super 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))"
    }
}

不太重要,但我将类型从类更改为结构

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

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

然后使用 JSONSerialization 进行解码,分两步将 json 转换为 Vehicle 数组,然后使用 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)
}

以上代码输出:

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


更新。可编码版本

我还通过引入一种用于解码的单独类型,设法提出了可编码解决方案。设置与以前相同,有一个协议(protocol)和两个结构。

然后我介绍了一种用于解码的特定类型(当然也可以扩展用于编码),它实现了自定义 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)
        }
    }
}

最终结果还是通过两步实现的

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

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

关于json - 根据编码值 Swift 解码类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65521457/

相关文章:

json - 在 Angular 2 中使用 JSON 正文发出 POST 请求

ios - 获取具有 alpha 值的纯色版本,同时考虑下面的颜色

ios - 无法在 iOS 中将\\u00e2\\u0080\\u0099 解码为 ’

c - 使用 union 和移位读取双平台字节序,安全吗?

java - 发送不带 'root' 的 JSON 对象

c# - ASP.NET ActionResult 对本地主机和托管的 Azure Web 服务提供不同的响应

javascript - 无法从 JSON 创建 Ember 对象

Swift 3 - 检查 WKWebView 是否已加载页面

ios - 在 iOS 设备上显示由 Ruby on Rails Web 应用程序处理的通知

c# - 加载 AAC/MP3 文件 "manually"