当属性类型可能从 Int 更改为 String 时,如何使用 Decodable 协议解析 JSON? [英] How to parse JSON with Decodable protocol when property types might change from Int to String?
问题描述
我必须解码具有大结构和大量嵌套数组的 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屋!