Swift: getting error while decoding using CodingKey - ios

I have created a model and I need to try to add value to one of the CodingKey's case
struct Model {
let name: String
let location: String
let descriptions: String
}
enum CodingKeys: String, CodingKey {
case name = "NAME"
case location = "LOCATION"
case descriptions = "INFO-\(NSLocalizedString("Language", comment: ""))"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
.
.
descriptions = try container.decode(String.self, forKey: .descriptions)
}
Now compiler gives me this error
🛑 Raw value for enum case must be a literal
How can I fix this error?

You don't have to use an enum for CodingKeys. You can also use a struct with static lets. It's just a bit more boilerplate:
struct Model: Decodable {
let name: String
let location: String
let descriptions: String
struct CodingKeys: CodingKey {
let intValue: Int? = nil
let stringValue: String
init(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) {
return nil
}
static let name = CodingKeys(stringValue: "name")
static let location = CodingKeys(stringValue: "location")
static let description = CodingKeys(stringValue: "INFO-\(NSLocalizedString("Language", comment: ""))")
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
location = try container.decode(String.self, forKey: .location)
descriptions = try container.decode(String.self, forKey: .description)
}
}

Related

How to decode an object that already has a different decode logic for API?

So I have decoded a JSON like so,
class MatchDetailResponse: Codable {
static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("MatchDetailResponse")
var homeTeam: Team?
var awayTeam: Team?
enum CodingKeys: String, CodingKey {
case homeTeam
case awayTeam
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(homeTeam, forKey: .homeTeam)
try container.encode(awayTeam, forKey: .awayTeam)
}
private enum TopLevelKeys: String, CodingKey {
case teams = "Teams"
case matchDetail = "Matchdetail"
}
private enum MatchDetailKeys: String, CodingKey {
case homeTeam = "Team_Home"
case awayTeam = "Team_Away"
}
private struct DynamicCodingKeys: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: TopLevelKeys.self)
let matchDetailContainer = try container.nestedContainer(keyedBy: MatchDetailKeys.self, forKey: .matchDetail)
let homeTeamId = try matchDetailContainer.decode(String.self, forKey: MatchDetailKeys.homeTeam)
let awayTeamId = try matchDetailContainer.decode(String.self, forKey: MatchDetailKeys.awayTeam)
let teamContainer = try container.nestedContainer(keyedBy: DynamicCodingKeys.self, forKey: .teams)
//Assign the home team & away team
for key in teamContainer.allKeys {
if key.stringValue == homeTeamId {
homeTeam = try teamContainer.decode(Team.self, forKey: DynamicCodingKeys(stringValue: key.stringValue)!)
}
else if key.stringValue == awayTeamId {
awayTeam = try teamContainer.decode(Team.self, forKey: DynamicCodingKeys(stringValue: key.stringValue)!)
}
}
}
}
As you see I get a response from an API and I am decoding it and everything works fine. Now my requirement is to store it. So I have encoded it like so. Now the problem is when it will try to decode the stored version, as the decode function is built for the API response, it's not able to decode it.
My requirement is this, how to decode a stored Codable struct, and how exactly we do it? Any help would be appreciated.

Convert dynamic server response in to model class in swift

The following is the server response handling parent structure...
struct ServerResponse<T: Codable>: Codable {
let status: Bool?
let message: String?
let data: T?
enum CodingKeys: String, CodingKey {
case status = "status"
case message = "message"
case data = "data"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
status = try values.decodeIfPresent(Bool.self, forKey: .status)
message = try values.decodeIfPresent(String.self, forKey: .message)
data = try values.decodeIfPresent(T.self, forKey: .data)
}
}
AppUserResponse strucure:
struct AppUserResponse: Codable {
let accessToken : String?
let askForMobileNo : Int?
let tokenType : String?
let user : AppUser?
enum CodingKeys: String, CodingKey {
case accessToken = "access_token"
case askForMobileNo = "ask_for_mobile_no"
case tokenType = "token_type"
case user = "user"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
accessToken = try values.decodeIfPresent(String.self, forKey: .accessToken)
askForMobileNo = try values.decodeIfPresent(Int.self, forKey: .askForMobileNo)
tokenType = try values.decodeIfPresent(String.self, forKey: .tokenType)
user = try values.decodeIfPresent(AppUser.self, forKey: .user)
}
}
struct AppUser: Codable {
let createdAt : String?
let deviceToken : String?
let deviceType : String?
let email : String?
let emailVerifiedAt : String?
let firstName : String?
let id : Int?
let lastName : String?
let mobile_no : String?
let mobileVerified : Int?
let mobileVerifiedAt : String?
let provider : String?
let providerId : String?
let status : Int?
let updatedAt : String?
enum CodingKeys: String, CodingKey {
case createdAt = "created_at"
case deviceToken = "device_token"
case deviceType = "device_type"
case email = "email"
case emailVerifiedAt = "email_verified_at"
case firstName = "first_name"
case id = "id"
case lastName = "last_name"
case mobile_no = "mobile_no"
case mobileVerified = "mobile_verified"
case mobileVerifiedAt = "mobile_verified_at"
case provider = "provider"
case providerId = "provider_id"
case status = "status"
case updatedAt = "updated_at"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
createdAt = try values.decodeIfPresent(String.self, forKey: .createdAt)
deviceToken = try values.decodeIfPresent(String.self, forKey: .deviceToken)
deviceType = try values.decodeIfPresent(String.self, forKey: .deviceType)
email = try values.decodeIfPresent(String.self, forKey: .email)
emailVerifiedAt = try values.decodeIfPresent(String.self, forKey: .emailVerifiedAt)
firstName = try values.decodeIfPresent(String.self, forKey: .firstName)
id = try values.decodeIfPresent(Int.self, forKey: .id)
lastName = try values.decodeIfPresent(String.self, forKey: .lastName)
mobile_no = try values.decodeIfPresent(String.self, forKey: .mobile_no)
mobileVerified = try values.decodeIfPresent(Int.self, forKey: .mobileVerified)
mobileVerifiedAt = try values.decodeIfPresent(String.self, forKey: .mobileVerifiedAt)
provider = try values.decodeIfPresent(String.self, forKey: .provider)
providerId = try values.decodeIfPresent(String.self, forKey: .providerId)
status = try values.decodeIfPresent(Int.self, forKey: .status)
updatedAt = try values.decodeIfPresent(String.self, forKey: .updatedAt)
}
}
TempUserResponse Structure
struct TempUserResponse : Codable {
let askForMobileNo : Int?
let provider : String?
let providerId : String?
let tempUser : TempUser?
enum CodingKeys: String, CodingKey {
case askForMobileNo = "ask_for_mobile_no"
case provider = "provider"
case providerId = "provider_id"
case tempUser = "temp_user"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
askForMobileNo = try values.decodeIfPresent(Int.self, forKey: .askForMobileNo)
provider = try values.decodeIfPresent(String.self, forKey: .provider)
providerId = try values.decodeIfPresent(String.self, forKey: .providerId)
tempUser = try values.decodeIfPresent(TempUser.self, forKey: .tempUser)
}
}
struct TempUser : Codable {
let email : String?
let name : String?
enum CodingKeys: String, CodingKey {
case email = "email"
case name = "name"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
email = try values.decodeIfPresent(String.self, forKey: .email)
name = try values.decodeIfPresent(String.self, forKey: .name)
}
}
PROBLEM
in app we have 2 buttons, 1. FB login and 2. Google Login
When I try login with new user, server return this response(at this time user is not registered so I am getting "tempUser" in "data")
{
"status": true,
"message": "User social email address verified, mobile number unverified!.",
"data": {
"temp_user": {
"name": "dhaval solanki",
"email": "dhaval.sassyinfotech#gmail.com"
},
"provider": "google",
"provider_id": "112620316711299944315",
"ask_for_mobile_no": 1
}
}
When I login with registered user, I am getting following response...
{
"status": true,
"message": "User logged in successfully.",
"data": {
"user": {
"id": 60,
"first_name": "Ankit",
"last_name": "Joshi",
"mobile_no": "9876543211"
},
"access_token": "eyJLCJhbGciOiJIUzI1NR..",
"token_type": "bearer",
"ask_for_mobile_no": 0
}
}
Using following to convert response into model class(I have used Alamofire for api call)
let decoder = JSONDecoder()
do {
let responseData = try decoder.decode(ServerResponse <TempUserResponse>.self, from: serverData)
success(responseData as AnyObject)
} catch {
print("Error = \(error)")
do {
let responseData = try decoder.decode(ServerResponse<AppUserResponse>.self, from: serverData)
success(responseData as AnyObject)
} catch {
failure(error.localizedDescription as AnyObject)
}
}
When I run this code it always goes and try to convert response into ServerResponse<TempUserResponse> even if the response is of type ServerResponse<AppUserResponse>.
So how can I manage both of the response by converting it into respected model class?
The decoding of your JSON response always succeeds in the first try (with TempUserResponse type) because all your properties are optional and decoded using decodeIfPresent<T>(_:forKey:) function.
So, JSONDecoder assumes that the value of the key data in your root object is a TempUserResponse instance but with all of is properties set to nil. (none of the property keys are present in the JSON)
In order to avoid that behavior you can set a property that make sense to you, to be mandatory in TempUserResponse, like for example tempUser:
struct TempUserResponse : Codable {
let askForMobileNo: Int?
let provider: String?
let providerId: String?
let tempUser: TempUser
enum CodingKeys: String, CodingKey {
case askForMobileNo = "ask_for_mobile_no"
case provider = "provider"
case providerId = "provider_id"
case tempUser = "temp_user"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
askForMobileNo = try values.decodeIfPresent(Int.self, forKey: .askForMobileNo)
provider = try values.decodeIfPresent(String.self, forKey: .provider)
providerId = try values.decodeIfPresent(String.self, forKey: .providerId)
tempUser = try values.decode(TempUser.self, forKey: .tempUser)
}
}
That way the decoding will succeed if the tempUser key is present in the JSON and will fail when there is not and fall back to the AppUserResponse decoding.
Update: Another solution would be to merge both structs into one with all of its properties as optionals.
That way your model will be a lot simpler:
struct UserResponse: Codable {
let accessToken: String?
let askForMobileNo: Int?
let tokenType: String?
let user: AppUser?
let provider: String?
let providerId: String?
let tempUser: TempUser?
enum CodingKeys: String, CodingKey {
case accessToken = "access_token"
case askForMobileNo = "ask_for_mobile_no"
case tokenType = "token_type"
case user = "user"
case provider = "provider"
case providerId = "provider_id"
case tempUser = "temp_user"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
accessToken = try values.decodeIfPresent(String.self, forKey: .accessToken)
askForMobileNo = try values.decodeIfPresent(Int.self, forKey: .askForMobileNo)
tokenType = try values.decodeIfPresent(String.self, forKey: .tokenType)
user = try values.decodeIfPresent(AppUser.self, forKey: .user)
provider = try values.decodeIfPresent(String.self, forKey: .provider)
providerId = try values.decodeIfPresent(String.self, forKey: .providerId)
tempUser = try values.decodeIfPresent(TempUser.self, forKey: .tempUser)
}
}
Note: Structs that not changed are not included.
The decoding code will look like this:
let decoder = JSONDecoder()
do {
let responseData = try decoder.decode(ServerResponse<UserResponse>.self, from: Data(jsonString.utf8))
print(responseData)
} catch {
print(error)
}
and you can just check if user or tempUser property exist to determine your case.
You can have one extra layer class like
struct FailableResponse <T:Codable,E:Codable> : Codable {
var success:T?
var failure:E?
public init(from decoder:Decoder) throws {
let singleValue = try decoder.singleValueContainer()
success = try singleValue.decode(T.self)
failure = try singleValue.decode(E.self)
}
}
And Pass Your correct response and wrong response as T and E
Note: Not tested in xcode but should work
Replace TempUserResponse model class
struct TempUserResponse : Codable {
let askForMobileNo : Int?
let provider : String?
let providerId : String?
let tempUser : TempUser?
var appUser: AppUser? = nil
enum CodingKeys: String, CodingKey {
case askForMobileNo = "ask_for_mobile_no"
case provider = "provider"
case providerId = "provider_id"
case tempUser = "temp_user"
case appUser = "user"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
askForMobileNo = try values.decodeIfPresent(Int.self, forKey: .askForMobileNo)
provider = try values.decodeIfPresent(String.self, forKey: .provider)
providerId = try values.decodeIfPresent(String.self, forKey: .providerId)
tempUser = try values.decodeIfPresent(TempUser.self, forKey: .tempUser)
if tempUser == nil{
appUser = try values.decodeIfPresent(AppUser.self, forKey: .appUser)
}
}
}
Response in model class
do {
let responseData = try decoder.decode(ServerResponse <TempUserResponse>.self, from: serverData)
print("Response Success: \(responseData)")
if let tempUser = responseData.data?.tempUser{
print("TempUser: \(tempUser)")
}
if let appuser = responseData.data?.appUser{
print("AppUser : \(appuser)")
}
} catch {
print("Error = \(error)")
}

Parse JSON with JSONDecoder in Swift 5 using structs

I get from a Rest API a result in json format. Now I wanna parse this JSON with the JSONDecoder but I don't really understand the structure of my response.
For that I already tried to create structs to get the "name" of "FirstUser".
{
"User":[
{
"FirstUser":{
"name":"John"
},
"Information":"XY",
"SecondUser":{
"name":"Tom"
}
Json
{
"User":[
{
"FirstUser":{
"name":"John"
},
"Information":"XY",
"SecondUser":{
"name":"Tom"
}
}
]
}
Model
// MARK: - Empty
struct Root: Codable {
let user: [User]
enum CodingKeys: String, CodingKey {
case user = "User"
}
}
// MARK: - User
struct User: Codable {
let firstUser: FirstUserClass
let information: String
let secondUser: FirstUserClass
enum CodingKeys: String, CodingKey {
case firstUser = "FirstUser"
case information = "Information"
case secondUser = "SecondUser"
}
}
// MARK: - FirstUserClass
struct FirstUserClass: Codable {
let name: String
}
Parse
do {
let res = try JSONDecoder().decode(Root.self, from: data)
print(res.first?.firstUser.name)
} catch {
print(error)
}
If I create model using previous json
Using this link [blog]: http://www.jsoncafe.com to generate Codable structure or Any Format
Model
import Foundation
struct RootClass : Codable {
let user : [Users]?
enum CodingKeys: String, CodingKey {
case user = "User"
}
init(from decoder: Decoder) throws {
let values = try? decoder.container(keyedBy: CodingKeys.self)
user = try? values?.decodeIfPresent([Users].self, forKey: .user)
}
}
struct Users : Codable {
let firstUser : FirstUser?
let information : String?
let secondUser : SecondUser?
enum CodingKeys: String, CodingKey {
case firstUser = "FirstUser"
case information = "Information"
case secondUser = "SecondUser"
}
init(from decoder: Decoder) throws {
let values = try? decoder.container(keyedBy: CodingKeys.self)
firstUser = try? FirstUser(from: decoder)
information = try? values?.decodeIfPresent(String.self, forKey: .information)
secondUser = try? SecondUser(from: decoder)
}
}
struct SecondUser : Codable {
let name : String?
enum CodingKeys: String, CodingKey {
case name = "name"
}
init(from decoder: Decoder) throws {
let values = try? decoder.container(keyedBy: CodingKeys.self)
name = try? values?.decodeIfPresent(String.self, forKey: .name)
}
}
struct FirstUser : Codable {
let name : String?
enum CodingKeys: String, CodingKey {
case name = "name"
}
init(from decoder: Decoder) throws {
let values = try? decoder.container(keyedBy: CodingKeys.self)
name = try? values?.decodeIfPresent(String.self, forKey: .name)
}
}
Parse
do {
let res = try JSONDecoder().decode(RootClass.self, from: data)
print(res?.user?.first?.firstUser?.name ?? "Yours optional value")
} catch {
print(error)
}

Swift decodable propagate userInfo

I have the following models:
struct Shop: Decodable {
let id: Int
let name: String
let products: [Product]
private enum CodingKeys: String, CodingKey {
case id
case name
case products
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int.self, forKey: .id)
self.name = try container.decode(String.self, forKey: .name)
self.products = try container.decode([Product].self, forKey: .products)
}
}
struct Product: Decodable {
let id: Int
let title: String
let productImageURL: String
private(set) var productDetails: ProductDetails
private enum CodingKeys: String, CodingKey {
case id
case title
case productImageList
case productImageDetails
case productDetails
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int.self, forKey: .id)
self.title = try container.decode(String.self, forKey: .title)
self.productDetails = try container.decode(ProductDetails.self, forKey: .productDetails)
guard let serializationType = decoder.userInfo[.serializationType] as? SerializationGenericType else {
throw NetworkError.modelParsingError(message: "Product model missing serialization type")
}
switch serializationType {
case .list:
self.productImageURL = try container.decode(String.self, forKey: .productImageList)
case .details:
self.productImageURL = try container.decode(String.self, forKey: .productImageList)
self.productDetails = try container.decode(ProductDetails.self, forKey: .productDetails)
}
}
}
Of course, the models are much more complex, but this is enough to explain my problem :)
Also, please don't mind the coding keys and the image URL, it's just to show that for different serialization type it initialized differently.
When getting the product details I have:
let decoder = JSONDecoder()
decoder.userInfo[.serializationType] = SerializationGenericType.details
let product = try decoder.decode(Product.self, from: data)
But when I try to pass the serialization type when initializing the Shop in the user info of the JsonDecoder like this:
let decoder = JSONDecoder()
decoder.userInfo[.serializationType] = SerializationGenericType.list
let shop = try decoder.decode(Shop.self, from: data)
In the Product initializer the user info is empty.
My question is how to propagate the serialization type from the Shop to the Product so I can properly decode it using the correct serialization type?

Store Decodable struct to Userdefaults

I have a Decodable struct like so...
struct BestWishes: Decodable {
private enum CodingKeys : String, CodingKey {
case customerID = "customer_id"
case birthDate = "birth_date"
case type = "type"
case customerName = "customer_name"
case mobileNo = "mobile_number"
}
let customerID: Int
let date: String
let type: String
let customerName : String
let mobileNumber: Int
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
customerID = try container.decode(Int.self, forKey: .customerID)
type = try container.decode(String.self, forKey: .type)
if type == "anniversary_date" {
date = try container.decode(String.self, forKey: .anniversaryDate)
} else {
date = try container.decode(String.self, forKey: .birthDate)
}
customerName = try container.decode(String.self, forKey: .customerName)
mobileNumber = try container.decode(Int.self, forKey: .mobileNo)
}
}
And all the data within this is stored in an array like so..
var bestWishesArr = [BestWishes]()
self.bestWishesArr.append(contentsOf: result.bestWishes)
Now I would like to store bestWishesArr to Userdefaults. Not sure how to achieve that...
Add the second half of the Codable protocol. To be compatible with the init method the date property is saved differently depending on type
struct BestWish: Codable {
private enum CodingKeys : String, CodingKey {
case customerID = "customer_id"
case birthDate = "birth_date"
case anniversaryDate = "anniversary_date"
case type
case customerName = "customer_name"
case mobileNumber = "mobile_number"
}
let customerID: Int
let date: String
let type: String
let customerName : String
let mobileNumber: Int
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
customerID = try container.decode(Int.self, forKey: .customerID)
type = try container.decode(String.self, forKey: .type)
if type == "anniversary_date" {
date = try container.decode(String.self, forKey: .anniversaryDate)
} else {
date = try container.decode(String.self, forKey: .birthDate)
}
customerName = try container.decode(String.self, forKey: .customerName)
mobileNumber = try container.decode(Int.self, forKey: .mobileNumber)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(customerID, forKey: .customerID)
try container.encode(type, forKey: .type)
if type == "anniversary_date" {
try container.encode(date, forKey: .anniversaryDate)
} else {
try container.encode(date, forKey: .birthDate)
}
try container.encode(customerName, forKey: .customerName)
try container.encode(mobileNumber, forKey: .mobileNumber)
}
}
Then encode the array and write the Data object to UserDefaults
do {
let jsonData = try JSONEncoder().encode(bestWishesArr)
UserDefaults.standard.set(jsonData, forKey:"bestWishes")
} catch { print(error) }

Resources