当属性类型可能从 Int 更改为 String 时,如何使用 Decodable 协议解析 JSON? [英] How to parse JSON with Decodable protocol when property types might change from Int to String?

查看:20
本文介绍了当属性类型可能从 Int 更改为 String 时,如何使用 Decodable 协议解析 JSON?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我必须解码具有大结构和大量嵌套数组的 JSON.我已经在我的 UserModel 文件中复制了该结构,并且它可以工作,但嵌套数组(位置)中的一个属性(邮政编码)有时是 Int,而其他一些是 String.我不知道如何处理这种情况并尝试了很多不同的解决方案.我试过的最后一个来自这个博客

任何帮助或任何不同的方法将不胜感激.API 调用是这样的:https://api.randomuser.me/?results=100&seed=xmoba这是我的用户模型文件:

导入基础导入 UIKit导入对象映射器struct PostModel: Equatable, Decodable{静态功能==(lhs:PostModel,rhs:PostModel)->布尔{如果 lhs.userId != rhs.userId {返回假}如果 lhs.id != rhs.id {返回假}如果 lhs.title != rhs.title {返回假}如果 lhs.body != rhs.body {返回假}返回真}var userId : Int变量 id : 整数变量标题:字符串变量体:字符串枚举键:CodingKey {案例用户ID案例ID案例标题箱体}init(来自解码器:解码器)抛出{让容器 = 尝试decoder.container(keyedBy: key.self)让 userId = 尝试 container.decode(Int.self, forKey: .userId)让 id = 尝试 container.decode(Int.self, forKey: .id)let title = try container.decode(String.self, forKey: .title)让 body = try container.decode(String.self, forKey: .body)self.init(userId: userId, id: id, title: title, body: body)}init(userId: Int, id: Int, title: String, body: String) {self.userId = 用户 IDself.id = idself.title = 标题self.body = 身体}初始化?(地图:地图){self.id = 0self.title = ""self.body = ""self.userId = 0}}扩展 PostModel:可映射 {变异功能映射(地图:地图){id <- 地图 ["id"]标题<-地图[标题"]身体 <- 地图 [身体"]用户 ID <- 地图 [用户 ID"]}}

解决方案

你可以像这样使用泛型:

enum {案例左(L)大小写正确(R)}扩展任一:可解码其中 L:可解码,R:可解码 {init(来自解码器:解码器)抛出{让容器 = 尝试decoder.singleValueContainer()如果让左=尝试?容器.解码(L.self){self = .left(left)} else if let right = try?容器.解码(R.self){self = .right(right)} 别的 {throw DecodingError.typeMismatch(Either<L, R>.self, .init(codingPath:decoder.codingPath, debugDescription: "Expected or `\(L.self)` 或 `\(R.self)`"))}}}扩展名:Encodable 其中 L:Encodable,R:Encodable {func encode(to encoder: Encoder) 抛出 {var 容器 = 编码器.singleValueContainer()切换自我{case let .left(left):尝试 container.encode(左)case let .right(right):尝试 container.encode(右)}}}

然后声明 postcode:Either<Int, String> 并且如果您的模型是 Decodable 并且所有其他字段都是 Decodable 也没有额外的需要代码.

I have to decode a JSON with a big structure and a lot of nested arrays. I have reproduced the structure in my UserModel file, and it works, except with one property (postcode) that is in a nested array (Location) that sometimes is an Int and some other is a String. I don't know how to handle this situation and tried a lot of different solutions. The last one I've tried is from this blog https://agostini.tech/2017/11/12/swift-4-codable-in-real-life-part-2/ And it suggests using generics. But now I can't initialize the Location object without providing a Decoder():

Any help or any different approach would be appreciated. The API call is this one: https://api.randomuser.me/?results=100&seed=xmoba This is my UserModel File:

import Foundation
import UIKit
import ObjectMapper

struct PostModel: Equatable, Decodable{

    static func ==(lhs: PostModel, rhs: PostModel) -> Bool {
        if lhs.userId != rhs.userId {
            return false
        }
        if lhs.id != rhs.id {
            return false
        }
        if lhs.title != rhs.title {
            return false
        }
        if lhs.body != rhs.body {
            return false
        }
        return true
    }


    var userId : Int
    var id : Int
    var title : String
    var body : String

    enum key : CodingKey {
        case userId
        case id
        case title
        case body
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: key.self)
        let userId = try container.decode(Int.self, forKey: .userId)
        let id = try container.decode(Int.self, forKey: .id)
        let title = try container.decode(String.self, forKey: .title)
        let body = try container.decode(String.self, forKey: .body)

        self.init(userId: userId, id: id, title: title, body: body)
    }

    init(userId : Int, id : Int, title : String, body : String) {
        self.userId = userId
        self.id = id
        self.title = title
        self.body = body
    }
    init?(map: Map){
        self.id = 0
        self.title = ""
        self.body = ""
        self.userId = 0
    }
}

extension PostModel: Mappable {



    mutating func mapping(map: Map) {
        id       <- map["id"]
        title     <- map["title"]
        body     <- map["body"]
        userId     <- map["userId"]
    }

}

解决方案

You can use generic like this:

enum Either<L, R> {
    case left(L)
    case right(R)
}

extension Either: Decodable where L: Decodable, R: Decodable {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let left = try? container.decode(L.self) {
            self = .left(left)
        } else if let right = try? container.decode(R.self) {
            self = .right(right)
        } else {
            throw DecodingError.typeMismatch(Either<L, R>.self, .init(codingPath: decoder.codingPath, debugDescription: "Expected either `\(L.self)` or `\(R.self)`"))
        }
    }
}

extension Either: Encodable where L: Encodable, R: Encodable {
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case let .left(left):
            try container.encode(left)
        case let .right(right):
            try container.encode(right)
        }
    }
}

And then declare postcode: Either<Int, String> and if your model is Decodable and all other fields are Decodable too no extra code would be needed.

这篇关于当属性类型可能从 Int 更改为 String 时,如何使用 Decodable 协议解析 JSON?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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