在Swift中通过改变值类型来解码json

hec6srdp  于 2023-05-19  发布在  Swift
关注(0)|答案(2)|浏览(161)

我在我的应用程序上创建了一个注册表视图,我试图解码一个json,但根据响应的不同,密钥的类型不同。
如果注册成功:

{
    "data": true,
    "success": true
}

如果注册失败的原因有很多:

{
    "data": ["email is not valid", "password must have more than 4 digits"],
    "success": false
}

如果注册失败,只有一个原因:

{
    "data": "email is not valid",
    "success": false
}

如何在Swift中构建一个结构体来处理这种情况?

struct Response: Codable {
   var data: // what type to use: String? [String]? Bool?
   var success: Bool
}

我问了后端开发人员,但目前,在后端没有任何变化,所以我必须处理它...

3yhwsihp

3yhwsihp1#

您可以引入一个枚举,其中包含所有可能的选项,并提供init的自定义实现(来自decoder:解码器)

enum DataType: Codable {
    case success(Bool)
    case singleError(String)
    case multiError([String])
    case undefined
}

struct Response: Codable {

    enum CodingKeys: String, CodingKey {
        case data
        case success
    }

    var data: DataType
    var success: Bool

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        success = try container.decode(Bool.self, forKey: .success)
    
        if let bool = try? container.decode(Bool.self, forKey: .data) {
            data = DataType.success(bool)
        } else if let string = try? container.decode(String.self, forKey: .data) {
            data = DataType.singleError(string)
        } else if let strings = try? container.decode([String].self, forKey: .data) {
            data = DataType.multiError(strings)
        } else {
            data = .undefined
        }
    }
}

或者,你可以在你的结构中有单独的属性,并相应地设置它们。Enum看起来更迅捷。
如果你可以像Joakim建议的那样忽略Bool,那么你可以简单地使用它(不需要枚举):

struct Response: Codable {
    
    var data: [String]
    var success: Bool

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        success = try container.decode(Bool.self, forKey: .success)
    
        if let string = try? container.decode(String.self, forKey: .data) {
            data = [string]
        } else if let strings = try? container.decode([String].self, forKey: .data) {
            data = strings
        } else {
            data = []
        }
    }
}
ldioqlga

ldioqlga2#

我会为这个结果定义一个枚举。例如,

enum ResponseResult<Success> {
    case success(Success)
    case failure([String])
}

然后我会有一个自定义的解码器:

struct ResponseObject<T: Decodable>: Decodable {
    let result: ResponseResult<T>
    
    enum CodingKeys: String, CodingKey {
        case data, success
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let success = try container.decode(Bool.self, forKey: .success)
        if success {
            result = .success(try container.decode(T.self, forKey: .data))
            return
        }
        
        if !container.contains(.data) {
            result = .failure([])
        } else if let message = try? container.decode(String.self, forKey: .data) {
            result = .failure([message])
        } else if let messages = try? container.decode([String].self, forKey: .data) {
            result = .failure(messages)
        } else if let wasNull = try? container.decodeNil(forKey: .data), wasNull {
            result = .failure([])
        } else {
            throw DecodingError.dataCorruptedError(forKey: .data, in: container, debugDescription: "Expected string, array of strings, null, or no key named 'data'")
        }
    }
}

注意,您提到这是针对这个特定端点的,它在成功时返回Bool。但是如果他们已经为注册端点做了这件事,我敢打赌他们也为其他端点做了这件事。所以,上面是通用的,在这种情况下,你可以使用如下:

let registrationResponse = try JSONDecoder().decode(ResponseObject<Bool>.self, from: data)

switch registrationResponse.result {
case .success(let payload):
    print(payload)
case .failure(let messages):
    print(messages.joined(separator: " ")) 
}

但是它可以很容易地与其他Success有效负载类型一起使用。
注意,在本例中,您可能不关心success有效负载(即,如果注册成功,则successdata密钥都可能是true;如果不成功,那么data将包含错误消息),但这是您和后端工程师之间的对话。但是您可能希望对使用这种不一致错误消息模式的其他端点使用“让我提取有效负载”模式。
有关此自定义解码模式的详细信息,请参阅Encoding and Decoding Custom Types

相关问题