我必须解码具有大结构和大量嵌套数组的JSON。 我已经在UserModel文件中重现了该结构,并且该结构可以工作,除了其中一个属性(邮政编码)位于嵌套数组(位置)中,该数组有时是Int,而其他属性是String。我不知道如何处理这种情况,并尝试了许多不同的解决方案。 我尝试的最后一个来自此博客https://agostini.tech/2017/11/12/swift-4-codable-in-real-life-part-2/ 它建议使用泛型。但是现在,如果不提供Decoder(),就无法初始化Location对象:
任何帮助或任何其他方法将不胜感激。 API调用是这样的:https://api.randomuser.me/?results=100&seed=xmoba 这是我的UserModel文件:
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"]
}
}
答案 0 :(得分:1)
您可以像这样使用泛型:
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)
}
}
}
然后声明postcode: Either<Int, String>
,如果您的模型为Decodable
,而所有其他字段均为Decodable
,则不需要额外的代码。
答案 1 :(得分:0)
如果postcode
既可以是String
也可以是Int
,那么您(至少)有两种可能的解决方案。首先,您可以简单地将所有邮政编码存储为String
,因为所有Int
都可以转换为String
。这似乎是最好的解决方案,因为您几乎不需要对邮政编码执行任何数字运算,尤其是在某些邮政编码可能为String
的情况下。另一种解决方案是为邮政编码创建两个属性,一个属性为String?
,一个属性为Int?
,并且始终仅根据输入数据填充两个属性之一,如Using codable with key that is sometimes an Int and other times a String中所述
该解决方案将所有邮政编码存储为String
:
struct PostModel: Equatable, Decodable {
static func ==(lhs: PostModel, rhs: PostModel) -> Bool {
return lhs.userId == rhs.userId && lhs.id == rhs.id && lhs.title == rhs.title && lhs.body == rhs.body
}
var userId: Int
var id: Int
var title: String
var body: String
var postcode: String
enum CodingKeys: String, CodingKey {
case userId, id, title, body, postcode
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.userId = try container.decode(Int.self, forKey: .userId)
self.id = try container.decode(Int.self, forKey: .id)
self.title = try container.decode(String.self, forKey: .title)
self.body = try container.decode(String.self, forKey: .body)
if let postcode = try? container.decode(String.self, forKey: .postcode) {
self.postcode = postcode
} else {
let numericPostcode = try container.decode(Int.self, forKey: .postcode)
self.postcode = "\(numericPostcode)"
}
}
}
答案 2 :(得分:0)
嗯,这是一个常见的cmd
问题。您只需将您的媒体资源类型设为可以处理IntOrString
或enum
的{{1}}。
String
由于我发现您在问题中发布的模型与您指向的API端点中的模型不匹配,因此我创建了自己的模型和需要解码的JSON。
Int
enum IntOrString: Codable {
case int(Int)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self = try .int(container.decode(Int.self))
} catch DecodingError.typeMismatch {
do {
self = try .string(container.decode(String.self))
} catch DecodingError.typeMismatch {
throw DecodingError.typeMismatch(IntOrString.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload conflicts with expected type, (Int or String)"))
}
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .int(let int):
try container.encode(int)
case .string(let string):
try container.encode(string)
}
}
}
为struct PostModel: Decodable {
let userId: Int
let id: Int
let title: String
let body: String
let postCode: IntOrString
// you don't need to implement init(from decoder: Decoder) throws
// because all the properties are already Decodable
}
时的解码:
postCode
Int
为let jsonData = """
{
"userId": 123,
"id": 1,
"title": "Title",
"body": "Body",
"postCode": 9999
}
""".data(using: .utf8)!
do {
let postModel = try JSONDecoder().decode(PostModel.self, from: jsonData)
if case .int(let int) = postModel.postCode {
print(int) // prints 9999
} else if case .string(let string) = postModel.postCode {
print(string)
}
} catch {
print(error)
}
时的解码:
postCode
答案 3 :(得分:0)
尝试此扩展程序
extension KeyedDecodingContainer{
public func decodeIfPresent(_ type: String.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> String?{
if let resStr = try? decode(type, forKey: key){
return resStr
}else{
if let resInt = try? decode(Int.self, forKey: key){
return String(resInt)
}
return nil
}
}
public func decodeIfPresent(_ type: Int.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int?{
if let resInt = try? decode(type, forKey: key){
return resInt
}else{
if let resStr = try? decode(String.self, forKey: key){
return Int(resStr)
}
return nil
}
}
}
示例
struct Foo:Codable{
let strValue:String?
let intValue:Int?
}
let data = """
{
"strValue": 1,
"intValue": "1"
}
""".data(using: .utf8)
print(try? JSONDecoder().decode(Foo.self, from: data!))
它将显示“ Foo(strValue:Optional(“ 1”),intValue:Optional(1))”