How to ignore nil json values in codable - ios

I currently getting receipts back both Auto-Renewable and Non-Renewable. But the Non-Renewable doesn't come back with expires_date json key. How can I ignore this. I'm trying to avoid making expires_date an optional. When I make it optional Apple sends a response back. Is there way I can decode the json without making expires_date optional.
struct Receipt: Codable {
let expiresDate: String
private enum CodingKeys: String, CodingKey {
case expiresDate = "expires_date"
}
}
Right now I can currently get
"No value associated with key CodingKeys(stringValue: \"expires_date\", intValue: nil) (\"expires_date\")."

You will have to implement your own init(from: Decoder) and use decodeIfPresent(_:forKey:) before nil coalescing to a default value.
struct Receipt: Codable {
let expiresDate: String
enum CodingKeys: String, CodingKey {
case expiresDate = "expires_date"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.expiresDate = try values.decodeIfPresent(String.self, forKey: .expiresDate)
?? "1970" //Default value
}
}
NOTE:
If Receipt has more key-value pairs, then you would have to manually decode those as well.
Usage Example:
let data = """
[{
"expires_date": "2019"
},
{
}]
""".data(using: .utf8)!
do {
let obj = try JSONDecoder().decode([Receipt].self, from: data)
print(obj)
}
catch {
print(error)
}

How about manual decode it:
struct Receipt: Codable {
let expiresDate: String
private enum CodingKeys: String, CodingKey {
case expiresDate = "expires_date"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
if let expDate = try? values.decode(String.self, forKey: .expiresDate) {
self.expiresDate = expDate
} else {
self.expiresDate = "sth"
}
}
}
Example:
struct Receipt: Codable {
let expiresDate: String
let b: String
private enum CodingKeys: String, CodingKey {
case expiresDate = "expires_date"
case b = "b"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
if let expDate = try? values.decode(String.self, forKey: .expiresDate) {
self.expiresDate = expDate
} else {
self.expiresDate = "sth"
}
b = try values.decode(String.self, forKey: .b)
}
}
let a = """
{
"b": "asdf"
}
""".data(using: .utf8)!
let myStruct = try JSONDecoder().decode(Receipt.self, from: a)
print(myStruct) //Receipt(expiresDate: "sth", b: "asdf")

Related

iOS How can I parse JSON codable array with Alamofire

I'm trying data parsing from server with Alamofire.
(I've been trying for 2 days and it's failing.)
How can I get Json array Codable type with Alamofire?...
API :
[
{
"name": "John Doe",
"email": "johndoe#gmail.com",
"type": "Lattee",
"size": "medium"
},
{
"name": "Doe",
"email": "doe#gmail.com",
"type": "Lattee",
"size": "small"
}
]
now this is my code
in Model.swift
struct OrderList : Codable{
var list : [Order]
}
enum coffeeType: String, Codable{
case cappuccino
case lattee
case espressino
case cortado
}
enum coffeeSize: String, Codable{
case small
case medium
case large
enum CodingKeys: String, CodingKey {
case small = "s"
case medium = "m"
case large = "l"
}
}
struct Order: Codable {
let email: String!
let name : String!
let size : coffeeSize!
let type : coffeeType!
enum CodingKeys: String, CodingKey{
case name = "Name"
case email = "Email"
case type = "Type"
case size = "Size"
}
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) ?? "Guest"
size = try values.decodeIfPresent(coffeeSize.self, forKey: .size) ?? .small
type = try values.decodeIfPresent(coffeeType.self, forKey: .type) ?? .lattee
}
}
struct Resource<T: Codable> {
let url : URL
var httpMethod: HTTPMethod = .get
}
I have defined it in various formats such as responseData, responseJSON, and responseCodable, but I keep getting nil or something is missing.
I know how to parse with responseJSON. but I want trying to parse by applying Codable...
it's too difficult.
---- data parsing ---
func load<T>(resource: Resource<T>, completion: #escaping (Result<T, NetworkError>) -> Void) {
let call = AF.request(myurl,method: resource.httpMethod, parameters: nil).responseJSON{ response in
switch response.result {
case .success(let data):
if let JSON = response.value {
do{
let dataJson = try JSONSerialization.data(withJSONObject: JSON, options: [])
let getInstanceData = try JSONDecoder().decode(T.self, from: dataJson)
print(getInstanceData)
completion(.success(getInstanceData))
}catch{
print(error)
}
}
case .failure(_):
break
}
}
}
Since the API returns this payload:
[ { "name": "John Doe", "email": "johndoe#gmail.com", "type": "Lattee", "size": "medium" }, { "name": "Doe", "email": "doe#gmail.com", "type": "Lattee", "size": "small" } ]
these are keys that your code needs to handle:
name, email, type, size
Therefore the Order struct should be:
struct Order: Codable {
let email: String
let name : String
let size : CoffeeSize
let type : CoffeeType
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) ?? "Guest"
size = try values.decode(CoffeeSize.self, forKey: .size)
type = try values.decode(CoffeeType.self, forKey: .type)
}
}
enum CoffeeType: String, Codable {
case cappuccino
case latte
case espressino
case cortado
init(from decoder: Decoder) throws {
let label = try decoder.singleValueContainer().decode(String.self)
let lowercaseLabel = label.lowercased()
self = CoffeeType(rawValue: lowercaseLabel) ?? .latte
}
}
enum CoffeeSize: String, Codable {
case small
case medium
case large
}
since there is no list key in the json structure therefore you don't need
struct OrderList. In order to get a list of orders you can simply call
load(resource<[Order]>) { result in
// handle the response but now you will get the list of orders
}
Alternatively, I created a playground to load a json file (data.json) locally and parse it into objects you can have a look at the solution below
func readJsonFile(filename: String) -> String {
guard let fileUrl = Bundle.main.url(forResource: filename, withExtension: "json") else { fatalError() }
guard let jsonData = try? String(contentsOf: fileUrl) else {
return ""
}
return jsonData
}
enum CoffeeType: String, Codable {
case cappuccino
case latte
case espressino
case cortado
init(from decoder: Decoder) throws {
let label = try decoder.singleValueContainer().decode(String.self)
let lowercaseLabel = label.lowercased()
self = CoffeeType(rawValue: lowercaseLabel) ?? .latte
}
}
enum CoffeeSize: String, Codable {
case small
case medium
case large
}
struct Order: Codable {
let email: String
let name : String
let size : CoffeeSize
let type : CoffeeType
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) ?? "Guest"
size = try values.decode(CoffeeSize.self, forKey: .size)
type = try values.decode(CoffeeType.self, forKey: .type)
}
}
func parseJsonFile() {
let jsonStr = readJsonFile(filename: "data")
let jsonData: Data = Data(jsonStr.utf8)
let decoder = JSONDecoder()
do {
let orders = try decoder.decode([Order].self, from: jsonData)
orders.forEach {
print("\($0.name)" + " - " + "\($0.type.rawValue)" + " - " + "\($0.size.rawValue)")
}
} catch {
print(error.localizedDescription)
}
}
parseJsonFile()
Result:
John Doe - latte - medium
Doe - latte - small
The problem with your code is case-sensitive properties.
If the API returns these keys Name, Type, Email, Size, Type then the Codable object should have the coding Keys to handle keys from API
struct Order: Codable {
let email: String
let name : String
let size : CoffeeSize
let type : CoffeeType
enum CodingKeys: String, CodingKey{
case name = "Name"
case email = "Email"
case type = "Type"
case size = "Size"
}
}
In terms of unwrapping properties, it will depend on the contract between your code and API. If you're pretty sure API will always return size,type, name but email is nullable. Then you should use the method decodeIfPresent allows the value of the parsing key is nullable
If you're certain about name, the value is always not null, then you can use decode normally
and your Order struct will become
struct Order: Codable {
let email: String?
let name : String
let size : CoffeeSize
let type : CoffeeType
enum CodingKeys: String, CodingKey{
case name = "Name"
case email = "Email"
case type = "Type"
case size = "Size"
}
}

Swift Decode Array of Dictionaries inside String using Decodable Protocol

The JSON response is:-
{
"id" = "1"
"message" = "SUCCESS"
"data" = "[{\"name\":"FirstName",\"office_id\":1111,\"days_name\":\"Mon\"},
{\"name\":"SecondName:,\"office_id\":1112,\"days_name\":\"Tue\"}]"
}
I don't seems to understand how to approach decoding "data", In data model shall the data be declared as String? and I have been trying to figure out but don't seem to get any clue and stuck here for a while, if anyone can please shed some light to it, it would help a lot. The only problem I am facing is how to deal with "" double quotes wrapped around data array as shown in above response.
Data model and URLSession code is below:
struct Root : Codable {
let id : String?
let message : String?
let data : String?
enum CodingKeys: String, CodingKey {
case id = "id"
case message = "message"
case data = "data"
}
}
struct insideData: Codable {
let name: String?
let officeId : Int?
let daysName: String?
enum CodingKeys: String, CodingKey {
case name = "name"
case officeId = "office_id"
case daysName = "days_name"
}
}
URLSession.shared.dataTask(with: url!) { (responseData, httpUrlResponse , error) in
if(error == nil && responseData != nil && responseData?.count != 0){
let decoder = JSONDecoder()
do{
let result = try decoder.decode(Root.self, from: responseData!)
print(result.data!)
}
catch let error {
debugPrint("Error occured while decoding = \(error.localizedDescription)")
}
}
}.resume()
I save result.data! in a new variable and convert it to data and again use JSONDecoder but now with insideData.self struct but don't get desired output, not getting mapped with keys inside insideData struct.
I am just getting started with learning networking in swift so please pardon me for silly mistakes.
data value is JSON with JSON, ie it's a JSONString.
A way to parse it is to parse again the JSON String. To do so, you need to override init(from decoder:) of Root.
Let's first fix your JSON which isn't valid, to be able to use it in Playgrounds.
let jsonString = #"""
{
"id": "1",
"message": "SUCCESS",
"data": "[{\"name\":\"FirstName\",\"office_id\":1111,\"days_name\":\"Mon\"}, {\"name\":\"SecondName:\", \"office_id\":1112,\"days_name\":\"Tue\"}]"
}
"""#
Then, change data: let data : [InsideData]
You could then:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: .id)
self.message = try container.decodeIfPresent(String.self, forKey: .message)
guard let dataString = try container.decodeIfPresent(String.self, forKey: .data) else {
self.data = []
return
}
self.data = try JSONDecoder().decode([InsideData].self, from: Data(dataString.utf8))
}
If you don't like creating a new decoder, you can pass one in userInfo of the decoder:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: .id)
self.message = try container.decodeIfPresent(String.self, forKey: .message)
guard let dataString = try container.decodeIfPresent(String.self, forKey: .data) else {
self.data = []
return
}
guard let insideDecoder = decoder.userInfo[CodingUserInfoKey(rawValue: "InsideDecoder")!] as? JSONDecoder else {
self.data = []
return
}
self.data = try insideDecoder.decode([InsideData].self, from: Data(dataString.utf8))
}
Then the root decoding:
let decoder = JSONDecoder()
let insideDecoder = JSONDecoder()
decoder.userInfo = [CodingUserInfoKey(rawValue: "InsideDecoder")!: insideDecoder]
do {
let root = try decoder.decode(Root.self, from: Data(jsonString.utf8))
print(root)
} catch {
print("Error: \(error)")
}
The provided JSON looks to be invalid.
In json you don't use the = sign but the : sign between a key and value.
You are missing , behind each value.
There is a typo behind SecondName :, should be ",
It's weird to have quotes around your data array. It can be easier decoded when you have them removed.
Suggested JSON changes:
{
"id": "1",
"message": "SUCCESS",
"data": [
{"name":"FirstName","office_id":1111,"days_name":"Mon"},
{"name":"SecondName","office_id":1112,"days_name":"Tue"}
]
}
I've tested decoding this json in Playground and it seems to work:
struct DataEntity: Decodable {
let name: String
let officeId: Int
let daysName: String
enum CodingKeys: String, CodingKey {
case name = "name"
case officeId = "office_id"
case daysName = "days_name"
}
}
struct RootEntity: Decodable {
let id: String
let message: String
let data: [DataEntity]
}
struct Mapper {
func map(json: String) -> RootEntity {
guard let rootJsonData = json.data(using: .utf8) else {
fatalError("Couldn't convert json string to data")
}
do {
let rootEntity = try JSONDecoder().decode(RootEntity.self, from: rootJsonData)
return rootEntity
} catch let error {
fatalError(error.localizedDescription)
}
}
}
let jsonToDecode = """
{
"id": "1",
"message": "SUCCESS",
"data": [
{"name":"FirstName","office_id":1111,"days_name":"Mon"},
{"name":"SecondName","office_id":1112,"days_name":"Tue"}
]
}
"""
let rootEntity = Mapper().map(json: jsonToDecode)
print(rootEntity)
Print output:
RootEntity(id: "1", message: "SUCCESS", data: [DataEntity(name: "FirstName", officeId: 1111, daysName: "Mon"), DataEntity(name: "SecondName", officeId: 1112, daysName: "Tue")])

How to decode a property with type of JSON dictionary in Swift?

I am currently getting the following error in my tests:
If I correctly understood the problem, then I need to convert my dictionary to decoder.
At the moment i have the next code:
static var validConfirmAuthorizationData: [String: Any] { return json("valid_confirm_authorization_data") }
Which has the following structure:
{
"data": {
"id": "1",
"success": true
}
}
And the response class itself which I use together with the decodable:
public struct SEConfirmAuthorizationResponse: Decodable {
public let id: String
public let success: Bool
enum CodingKeys: String, CodingKey {
case data
}
enum DataCodingKeys: String, CodingKey {
case id
case success
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let dataContainer = try container.nestedContainer(keyedBy: DataCodingKeys.self, forKey: .data)
id = try dataContainer.decode(String.self, forKey: .id)
success = try dataContainer.decode(Bool.self, forKey: .success)
}
}
You have a couple options, you could create a codable struct with a variable named data. Data would have the type SEConfirmAuthorizationResponse. Then you would decode into the new struct.
You could decode into a dictionary like:
let decoded = JsonDecoder().decode([String: SEConfirmAuthorizationResponse].self, from: someData)
let response = decoded[“data”]
Or you could write a custom decoder which I usually try to avoid.
public struct AuthorizationData: Codable {
public let id: String
public let success: Bool
init(id: String, success: Bool) {
self.id = id
self.success = success
}
enum CodingKeys: String, CodingKey {
case id
case success
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
success = try container.decode(Bool.self, forKey: .success)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(success, forKey: .success)
}
}
public struct SEConfirmAuthorizationResponse: Codable {
public let data: AuthorizationData
enum CodingKeys: String, CodingKey {
case data
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
data = try container.decode(AuthorizationData.self, forKey: .data)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(data, forKey: .data)
}
init(_ data: AuthorizationData) {
self.data = data
}
}
func authData() -> Data? {
let authorizationData = AuthorizationData(id: "1", success: true)
let response = SEConfirmAuthorizationResponse(authorizationData)
guard let prettyJsonData = try? JSONEncoder().encode(response) else {
return nil
}
if let jsonString = String(data: prettyJsonData, encoding: .utf8) {
print("jsonString", jsonString)
}
return prettyJsonData
}
func authResponse() {
if let data = authData() {
guard let response = try? JSONDecoder().decode(SEConfirmAuthorizationResponse.self, from: data) else {
return
}
print("response", response)
}
}
It turned out to solve the problem as follows.
First of all, I created SpecDecodableModel:
public struct SpecDecodableModel<T: Decodable> {
static func create(from fixture: [String: Any]) -> T {
let decoder = JSONDecoder()
decoder.dateDecodingStrategyFormatters = [DateUtils.dateFormatter, DateUtils.ymdDateFormatter]
let fixtureData = Data(fixture.jsonString!.utf8)
return try! decoder.decode(T.self, from: fixtureData)
}
}
After that, I can convert my JSON structures to the desired type.
And now returning to the original problem, I can fix it as follows:
let fixture = DataFixtures.validConfirmAuthorizationData
let response = SpecDecodableModel<SEConfirmAuthorizationResponse>.create(from: fixture)
Update:
public extension Dictionary {
var jsonString: String? {
if let data = try? JSONSerialization.data(withJSONObject: self, options: []),
let string = String(data: data, encoding: String.Encoding.utf8) {
return string
}
return nil
}
}

How can I use decodable in unspecified return data from API

when trying to parse return data from API im getting "The data couldn’t be read because it isn’t in the correct format." because the return is inconsistent.
When logo_url has value it was a object see example below:
"logo_url": {
"mime_type": "image/jpeg",
"url": "http://google.com"
},
But when it doenst have value its return empty array
"logo_url": [],
This is the reason why im getting "The data couldn’t be read because it isn’t in the correct format."
My model
struct Model: Decodable {
let logo: Logo?
enum CodingKeys: String, CodingKey {
case logo = "logo_url"
}
}
struct Logo: Decodable {
let mimeType: String?
let url: String?
enum CodingKeys: String, CodingKey {
case mimeType = "mime_type"
case url
}
}
If you can't change this badly written API, you'd need a custom decoder, where you basically attempt to decode as the type you want, and failing that - make it nil:
struct Model: Decodable {
let logo: Logo?
enum CodingKeys: String, CodingKey {
case logo = "logo_url"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let logo = try? container.decode(Logo.self, forKey: .logo) {
self.logo = logo
} else {
self.logo = nil
}
}
}
I personally prefer checking if the logo_url is an array first, then let Swift report error if there is any happens by using try instead of try? when trying to decode a key. Since in most cases, you may want to know why your decoding failed instead of just getting nil as a result.
Additionally, you may want to use .convertFromSnakeCase as a keyDecodingStrategy so you don't have to write extra code.
let json2 = """
{
"logo_url": {
"mime_type": "image/jpeg",
"url": "http://google.com"
}
}
"""
let json3 = "[]"
struct Logo: Decodable {
let mimeType: String
let url: String
}
struct Model: Decodable {
let logo: Logo?
private enum CodingKeys: String, CodingKey {
case logoUrl
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if (try? container.decode([String].self, forKey: .logoUrl)) != nil {
self.logo = nil
} else {
self.logo = try container.decode(Logo.self, forKey: .logoUrl)
}
}
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result2 = try decoder.decode(Model.self, from: json2.data(using: .utf8)!)
print(result2)
let result3 = try? decoder.decode(Model.self, from: json3.data(using: .utf8)!)
print(result3)

How to decode a property with type of Array of dictionary in Swift 5 decodable protocol without key?

The below JSON response does not have the key and contents the array of dictionaries.
[
{
"content": "You can't program the monitor without overriding the mobile SCSI monitor!",
"media": [
{
"title": "Bedfordshire backing up copying",
}
],
"user": [
{
"name": "Ferne",
}
]
}
]
I am trying to decode this JSON using Decodable protocol using below struct
struct Articles: Decodable {
var details: ArticleDetails
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
details = try container.decode(ArticleDetails.self)
}
}
struct ArticleDetails: Decodable {
var content: String
var media: [Media]
var user: [User]
enum Keys: String, CodingKey {
case content
case media
case user
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Keys.self)
content = try container.decode(String.self, forKey: .content)
media = try container.decode([Media].self, forKey: .media)
user = try container.decode([User].self, forKey: .user)
}
}
struct Media: Decodable {
var title: String
enum Keys: String, CodingKey {
case title
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Keys.self)
title = try container.decode(String.self, forKey: .title)
}
}
struct User: Decodable {
var name: String
enum Keys: String, CodingKey {
case name
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Keys.self)
name = try container.decode(String.self, forKey: .name)
}
}
and decoding the response using below
let response = try JSONDecoder().decode(Articles.self, from: data)
OR
let response = try JSONDecoder().decode([ArticleDetails].self, from: data)
but getting the error
"Expected to decode Dictionary<String, Any> but found a string/data
instead."
How to decode such JSON response which contents array of dictionaries without a key?
Model:
struct Articles: Decodable {
let content: String
let media: [Media]
let user: [User]
}
struct Media: Decodable {
let title: String
}
struct User: Decodable {
let name: String
}
Decoding:
do {
let response = try JSONDecoder().decode([Articles].self, from: data)
print(response)
} catch { print(error) }
(This was already posted in your previous post. Which has been deleted.)

Resources