Store Decodable struct to Userdefaults - ios

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) }

Related

Swift: getting error while decoding using CodingKey

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)
}
}

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)")
}

Why are my json response objects returning nil?

I have some json:
{
"wallets":[
{
"fundingProviderName":"tns_cof",
"authLimit":75,
"pinRequired":true,
"wallets":[
{
"authLimit":75,
"isCardSupported":true,
"paymentProcessorId":6,
"mainDisplayText":"...0013",
"imageUrl":"https://az755244.vo.msecnd.net/paymentimages/visa2.png",
"userPaymentSourceId":9756,
"secondaryDisplayText":"12/30",
"expDate":"2030-12-31T23:59:59Z",
"cardIssuer":"Visa"
},
{
"authLimit":75,
"isCardSupported":true,
"paymentProcessorId":7,
"mainDisplayText":"...1020",
"imageUrl":"https://az755244.vo.msecnd.net/paymentimages/mastercard2.png",
"userPaymentSourceId":9757,
"secondaryDisplayText":"12/25",
"expDate":"2025-12-31T23:59:59Z",
"cardIssuer":"Mastercard"
},
{
"authLimit":75,
"isCardSupported":true,
"paymentProcessorId":8,
"mainDisplayText":"...3025",
"imageUrl":"https://az755244.vo.msecnd.net/paymentimages/amex.png",
"userPaymentSourceId":9758,
"secondaryDisplayText":"12/27",
"expDate":"2027-12-31T23:59:59Z",
"cardIssuer":"Amex"
}
],
"isSupported":true
}
]
}
my struct looks like this:
struct CreditCard: Codable {
var authLimit: Int?
var isCardSupported: Bool?
var paymentProcessorId: Int?
var mainDisplayText: String?
var imageUrl: String?
var userPaymentSourceId: Int?
var secondaryDisplayText: String?
var expDate: String?
var cardIssuer: String?
enum CodingKeys: String, CodingKey {
case cardIssuer = "cardIssuer"
case authLimit = "authLimit"
case isCardSupported = "isCardSupported"
case paymentProcessorId = "paymentProcessorId"
case mainDisplayText = "mainDisplayText"
case imageUrl = "imageUrl"
case userPaymentSourceId = "userPaymentSourceId"
case secondaryDisplayText = "secondaryDisplayText"
case expDate = "expDate"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
authLimit = try values.decodeIfPresent(Int.self, forKey: .authLimit)
isCardSupported = try values.decodeIfPresent(Bool.self, forKey: .isCardSupported)
paymentProcessorId = try values.decodeIfPresent(Int.self, forKey: .paymentProcessorId)
mainDisplayText = try values.decodeIfPresent(String.self, forKey: .mainDisplayText)
imageUrl = try values.decodeIfPresent(String.self, forKey: .imageUrl)
userPaymentSourceId = try values.decodeIfPresent(Int.self, forKey: .userPaymentSourceId)
secondaryDisplayText = try values.decodeIfPresent(String.self, forKey: .secondaryDisplayText)
expDate = try values.decodeIfPresent(String.self, forKey: .expDate)
cardIssuer = try values.decodeIfPresent(String.self, forKey: .cardIssuer)
}
}
my json decoder code looks like this:
do {
if let jsonData = response?.responseData {
let jsonDecoder = JSONDecoder()
let creditCards = try jsonDecoder.decode(CreditCard.self, from: jsonData)
print("credit cards \(creditCards)")
completion(nil, creditCards)
}
} catch {
print(error)
}
I'm sure there's probably an obvious oversight to why the model is still all nil. Any help would be really appreciated. Thanks!
Either decode from the top of the JSON (as #Rob answered) or traverse through the JSON and get the wallets key and then decode it.
if let jsonData = response?.responseData as? Dictionary<String,Any> {
if let walletData = jsonData["wallet"] as? [Dictionary<String,Any>] {
if let mainWalletData = walletData[0]["wallets"] as? [Dictionary<String,Any>] {
do {
let jsonDecoder = JSONDecoder()
let creditCards = try jsonDecoder.decode([CreditCard].self, from: mainWalletData)
print("credit cards \(creditCards)")
completion(nil, creditCards)
}
} catch {
print(error)
}
}
}
}
struct Wallet: Decodable {
let wallets: [WalletDetails]
}
struct WalletDetails: Decodable {
let fundingProviderName: String
let authLimit: Int
let pinRequired: Bool
let wallets: [CreditCard]
}
struct CreditCard: Codable {
var authLimit: Int?
var isCardSupported: Bool?
var paymentProcessorId: Int?
var mainDisplayText: String?
var imageUrl: String?
var userPaymentSourceId: Int?
var secondaryDisplayText: String?
var expDate: String?
var cardIssuer: String?
enum CodingKeys: String, CodingKey {
case cardIssuer = "cardIssuer"
case authLimit = "authLimit"
case isCardSupported = "isCardSupported"
case paymentProcessorId = "paymentProcessorId"
case mainDisplayText = "mainDisplayText"
case imageUrl = "imageUrl"
case userPaymentSourceId = "userPaymentSourceId"
case secondaryDisplayText = "secondaryDisplayText"
case expDate = "expDate"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
authLimit = try values.decodeIfPresent(Int.self, forKey: .authLimit)
isCardSupported = try values.decodeIfPresent(Bool.self, forKey: .isCardSupported)
paymentProcessorId = try values.decodeIfPresent(Int.self, forKey: .paymentProcessorId)
mainDisplayText = try values.decodeIfPresent(String.self, forKey: .mainDisplayText)
imageUrl = try values.decodeIfPresent(String.self, forKey: .imageUrl)
userPaymentSourceId = try values.decodeIfPresent(Int.self, forKey: .userPaymentSourceId)
secondaryDisplayText = try values.decodeIfPresent(String.self, forKey: .secondaryDisplayText)
expDate = try values.decodeIfPresent(String.self, forKey: .expDate)
cardIssuer = try values.decodeIfPresent(String.self, forKey: .cardIssuer)
}
}
You have not added the top level keys and that is why it is not working. Try this:
do {
if let jsonData = response?.responseData {
let jsonDecoder = JSONDecoder()
let creditCards = try jsonDecoder.decode(Wallet.self, from: jsonData)
print("credit cards \(creditCards)")
completion(nil, creditCards)
}
} catch {
print(error)
}
top level key wallets is missing in your Codable, and you don't need to implement
init(from decoder: Decoder) throws

Decodable nested data without creating additional class in Swift

I'm new in iOS development, so sorry for stupid question in advance.
I have json like this:
{
"type":"post",
"comments":{
"count":0,
"can_post":1
},
"likes":{
"count":0,
"user_likes":0,
"can_like":1,
"can_publish":1
},
"reposts":{
"count":0,
"user_reposted":0
}
}
I want to convert this to class which will contain just likesCount, commentsCount, repostsCount but without creating separate classes for comments, likes, reposts. I'm using Decodable for this and here is my code which doesn't work :)
Code:
final class FeedItem: Decodable {
enum Keys: String, CodingKey {
case type,
likes = "likes.count",
comments = "comments.count",
reposts = "reposts.count"
}
let type: String
var likes = 0
var comments = 0
var reposts = 0
required convenience init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Keys.self)
let type = try container.decode(String.self, forKey: .type)
let likes = try container.decode(Int.self, forKey: .likes)
let comments = try container.decode(Int.self, forKey: .comments)
let reposts = try container.decode(Int.self, forKey: .reposts)
self.init(type: type, likes: likes, comments: comments, reposts: reposts)
}
init(type: String,
likes: Int,
comments: Int,
reposts: Int) {
self.type = type
self.likes = likes
self.comments = comments
self.reposts = reposts
}
}
Error:
"No value associated with key Keys(stringValue: \"likes.count\", intValue: nil) (\"likes.count\")."
The error is very clear, there are no values for associate keys likes.count as the key not exists.
Use
let container = try decoder.container(keyedBy: CodingKeys.self)
and try container.decodeIfPresent(:_) to check for key exists or if not assign empty value.
Code:
struct FeedItem: Codable {
let type: String
let commentsCount: Int
let canPostComment: Int
let likesCount: Int
let userLikes: Int
let canLike: Int
let canPublish: Int
let repostsCount: Int
let userReposted: Int
enum CodingKeys: String, CodingKey {
case type = "type"
case comments = "comments"
case likes = "likes"
case reposts = "reposts"
case count = "count"
case canPost = "can_post"
case userLikes = "user_likes"
case canLike = "can_like"
case canPublish = "can_publish"
case userReposted = "user_reposted"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.type = try container.decodeIfPresent(String.self, forKey: .type) ?? ""
let comments = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .comments)
self.commentsCount = try comments.decodeIfPresent(Int.self, forKey: .count) ?? 0
self.canPostComment = try comments.decodeIfPresent(Int.self, forKey: .canPost) ?? 0
let likes = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .likes)
self.likesCount = try likes.decodeIfPresent(Int.self, forKey: .count) ?? 0
self.userLikes = try likes.decodeIfPresent(Int.self, forKey: .userLikes) ?? 0
self.canLike = try likes.decodeIfPresent(Int.self, forKey: .canLike) ?? 0
self.canPublish = try likes.decodeIfPresent(Int.self, forKey: .canPublish) ?? 0
let reposts = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .reposts)
self.repostsCount = try reposts.decodeIfPresent(Int.self, forKey: .count) ?? 0
self.userReposted = try reposts.decodeIfPresent(Int.self, forKey: .userReposted) ?? 0
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(type, forKey: .type)
var comments = container.nestedContainer(keyedBy: CodingKeys.self, forKey: .comments)
try comments.encode(commentsCount, forKey: .count)
try comments.encode(canPostComment, forKey: .canPost)
var likes = container.nestedContainer(keyedBy: CodingKeys.self, forKey: .likes)
try likes.encode(likesCount, forKey: .count)
try likes.encode(userLikes, forKey: .userLikes)
try likes.encode(canLike, forKey: .canLike)
try likes.encode(canPublish, forKey: .canPublish)
var reposts = container.nestedContainer(keyedBy: CodingKeys.self, forKey: .reposts)
try reposts.encode(repostsCount, forKey: .count)
try reposts.encode(userReposted, forKey: .userReposted)
}
}
Data Reading:
let data = //Your JSON data from API
let jsonData = try JSONDecoder().decode(FeedItem.self, from: data)
print("\(jsonData.type) \(jsonData.canLike)")

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?

Resources