Swift 4 JSONdecoder Optional Variable

Swift 4 JSONDecoder optional variable

If you have local variables you have to specify the CodingKeys

public struct VIO: Codable {

private enum CodingKeys : String, CodingKey { case id }

let id:Int?
...
var par1:Bool = false
var par2:Bool = false

}

Edit:

If par1 and par2 should be also decoded optionally you have to write a custom initializer

  private enum CodingKeys : String, CodingKey { case id, par1, par2 }

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
par1 = try container.decodeIfPresent(Bool.self, forKey: .par1)
par2 = try container.decodeIfPresent(Bool.self, forKey: .par2)
}

This is Swift: No trailing semicolons

Decoding optional values in JSON - Swift 4

The API decodeIfPresent is pointless in this case because the key does exist.

An existing key with null value and a missing key are two different situations.

And never force unwrap in the init(from decoder method.

There are two options:

  1. Delete the entire init method then the optional struct members handle the null values.
  2. Use regular decode and ignore the error

    name = try? values.decode(String.self, forKey: .name)

With JSONDecoder in Swift 4, can missing keys use a default value instead of having to be optional properties?

Approach that I prefer is using so called DTOs - data transfer object.
It is a struct, that conforms to Codable and represents the desired object.

struct MyClassDTO: Codable {
let items: [String]?
let otherVar: Int?
}

Then you simply init the object that you want to use in the app with that DTO.

 class MyClass {
let items: [String]
var otherVar = 3
init(_ dto: MyClassDTO) {
items = dto.items ?? [String]()
otherVar = dto.otherVar ?? 3
}

var dto: MyClassDTO {
return MyClassDTO(items: items, otherVar: otherVar)
}
}

This approach is also good since you can rename and change final object however you wish to.
It is clear and requires less code than manual decoding.
Moreover, with this approach you can separate networking layer from other app.

Swift Codable Decode Manually Optional Variable

Age is optional:

let age: String? 

So try to decode in this way:

let age: String? = try values.decodeIfPresent(String.self, forKey: .age)

Best approach to create Non-optional Codable with `default values` in swift

You can implement a custom decoder with default values:

class CompanyInfo : Codable {
var NameEn: String
var CityEn: String
var Website: String
var Email: String
var Phone: String
var Fax: String

required init(from decoder: Decoder) throws {
do {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.NameEn = try container.decodeIfPresent(String.self, forKey: .NameEn) ?? "Default"
self.CityEn = try container.decodeIfPresent(String.self, forKey: .CityEn) ?? "Default"
self.Website = try container.decodeIfPresent(String.self, forKey: .Website) ?? "Default"
self.Email = try container.decodeIfPresent(String.self, forKey: .Email) ?? "Default"
self.Phone = try container.decodeIfPresent(String.self, forKey: .Phone) ?? "Default"
self.Fax = try container.decodeIfPresent(String.self, forKey: .Fax) ?? "Default"
}
}
}

Unrelated to question, but important Note:

In Swift, only Types names should start with a capital letter. If you continue naming variables like this, you will have a serious refactoring issue one day if you decide to use CoreData or working with other Swift developers.

Decoding json with optional fields

You should split this into several steps in order to avoid to handle all these optionals in your model.

First create a struct that has only those properties that are guaranteed to be there. ok in your case:

struct OKResult: Codable{
let ok: Bool
}

then create one for your error state and one for your success state:

struct ErrorResult: Codable{
let ok: Bool
let errorCode: Int
let error: String

private enum CodingKeys: String, CodingKey{
case ok, errorCode = "error_code", error
}
}

struct ShortLinkData: Codable {
let ok: Bool
let result: Result
}

struct Result: Codable {
let code, shortLink: String
let fullShortLink: String
let shortLink2: String
let fullShortLink2: String
let shortLink3: String
let fullShortLink3: String
let shareLink: String
let fullShareLink: String
let originalLink: String

enum CodingKeys: String, CodingKey {
case code
case shortLink = "short_link"
case fullShortLink = "full_short_link"
case shortLink2 = "short_link2"
case fullShortLink2 = "full_short_link2"
case shortLink3 = "short_link3"
case fullShortLink3 = "full_short_link3"
case shareLink = "share_link"
case fullShareLink = "full_share_link"
case originalLink = "original_link"
}
}

Then you can decode the data:

guard try JSONDecoder().decode(OKResult.self, from: data).ok else{
let errorResponse = try JSONDecoder().decode(ErrorResult.self, from: data)
//handle error scenario
fatalError(errorResponse.error) // or throw custom error or return nil etc...
}

let shortlinkData = try JSONDecoder().decode(ShortLinkData.self, from: data)

Remarks:

  • Your inits are not necessary.
  • Never use try? this will hide all errors from you
  • you would need to wrap this either in a do catch block or make your function throwing and handle errors further up the tree.

Codable: give a default value to a new non-optional property

You can implement required init and give it a default value:

required init(from decoder: Decoder) throws {

let container = try decoder.container(keyedBy: CodingKeys.self)

let title = try container.decodeIfPresent(String.self, forKey: .title) ?? "Default title"
self.title = title

let baseDecoder = try container.superDecoder(forKey: .id)

try super.init(from: baseDecoder)
}


Related Topics



Leave a reply



Submit