This is my model. When I fetch the detail the an error occured.
import Foundation
// MARK: - PublisherModel
public struct PublisherModel: Decodable {
public let userslist: [UserslistModel]
public init(userslist: [UserslistModel]) {
self.userslist = userslist
}
}
// MARK: - UserslistModel
public struct UserslistModel: Decodable, Hashable {
public var id: String
public let full_name: String
public let totalBookViews: String
public let totalBooks: String?
public let full_address: String?
private enum CodingKeys: String, CodingKey {
case id,full_name,totalBookViews,totalBooks,full_address
}
public init(id: String, full_name: String, totalBookViews: String?, totalBooks: String?, full_address: String?) {
self.id = id
self.full_name = full_name
self.totalBookViews = totalBookViews!
self.totalBooks = totalBooks!
self.full_address = full_address!
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
full_name = try container.decode(String.self, forKey: .full_name)
full_address = try container.decode(String.self, forKey: .full_address)
totalBooks = try container.decode(String.self, forKey: .totalBooks)
totalBookViews = try container.decode(String.self, forKey: .totalBookViews)
do {
id = try String(container.decode(Int.self, forKey: .id))
} catch DecodingError.typeMismatch {
id = try container.decode(String.self, forKey: .id)
}
}
}
This is My ViewModel
class PublisherModelVM: ObservableObject {
@Published var datas = [UserslistModel]()
let url = "https://alibrary.in/api/publisherList"
init() {
getData(url: url)
}
func getData(url: String) {
guard let url = URL(string: url) else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
if let data = data {
do {
let results = try JSONDecoder().decode(PublisherModel.self, from: data).userslist
DispatchQueue.main.async {
self.datas = results.reversed()
}
}
catch {
print(error)
}
}
}.resume()
}
}
This is My View here I fetch the details
struct PublisherDataView: View{
@StateObject var list = PublisherModelVM()
@State var logourl: String?
var body: some View{
ScrollView(.horizontal,showsIndicators: false){
HStack{
ForEach( list.datas, id: \.id){ item in
VStack(spacing: 12){
Text(item.full_name )
Text(item.full_address ?? "-")
Text(item.totalBookViews)
Text(item.totalBooks!)
}
.frame(width:275, height: 220)
.background(Color.brown.opacity(0.5)).cornerRadius(12)
}
}
}
}
}
And when run this code the a error show that is:
typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "userslist", intValue: nil), _JSONKey(stringValue: "Index 2", intValue: 2), CodingKeys(stringValue: "totalBookViews", intValue: nil)], debugDescription: "Expected to decode String but found a number instead.", underlyingError: nil))
Where am I doing a mistake?
And whenever I changed the totalBookViews with Int then this new error show:
typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "userslist", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "id", intValue: nil)], debugDescription: "Expected to decode String but found a number instead.", underlyingError: nil))
Where am I wrong? Is it whenever I changed full_address
with String? Then also this issue appeared:
typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "userslist", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "totalBooks", intValue: nil)], debugDescription: "Expected to decode String but found a number instead.", underlyingError: nil))
CodePudding user response:
First of all please delete all init
methods in your structs. The compiler creates them on your behalf, and Codable
doesn't use them anyway.
public init(userslist: [UserslistModel]) {
self.userslist = userslist
}
public init(id: Int, full_name: String, totalBookViews: String, totalBooks: Int, full_address: String) {
self.id = id
self.full_name = full_name
self.totalBookViews = totalBookViews
self.totalBooks = totalBooks
self.full_address = full_address
}
Second of all add the convertFromSnakeCase
strategy to map the snake_case keys to camelCase struct members.
totalBookViews
is sometimes Int
and sometimes String
. It's still more complicating: The string value cannot be directly converted to Int
because it contains letters and whitespace characters.
You have to implement init(from decoder
and decode totalBookViews
conditionally. If it's a string, filter the numeric characters and convert the string to Int
.
Another issue – already mentioned by Nirav D – is that fullAddress
can be nil
. The init
method must consider that, too.
// MARK: - PublisherModel
public struct PublisherModel: Decodable {
public let userslist: [UserslistModel]
}
// MARK: - UserslistModel
public struct UserslistModel: Decodable {
public let id: Int
public let fullName: String
public let totalBookViews: Int
public let totalBooks: Int
public let fullAddress: String
private enum CodingKeys: String, CodingKey {
case id, fullName, totalBookViews, totalBooks, fullAddress
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int.self, forKey: .id)
self.fullName = try container.decode(String.self, forKey: .fullName)
do {
self.totalBookViews = try container.decode(Int.self, forKey: .totalBookViews)
} catch {
let bookViews = try container.decode(String.self, forKey: .totalBookViews).filter(\.isNumber)
guard let bookViewsAmount = Int(bookViews) else {
throw DecodingError.dataCorruptedError(forKey: .totalBookViews, in: container, debugDescription: "Wrong String Format")
}
self.totalBookViews = bookViewsAmount
}
self.totalBooks = try container.decode(Int.self, forKey: .totalBooks)
self.fullAddress = try container.decodeIfPresent(String.self, forKey: .fullAddress) ?? ""
}
}
And create the decoder
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let results = try decoder.decode(PublisherModel.self, from: data).userslist