Swift: JSONDecoder won't decode when struct has default array initialization - ios

I'm decoding a simple structure and ran into unexpected behavior.
struct Contacts: Codable {
struct Recipient: Codable {
let name: String
}
let recipients: [Recipient]
}
do {
let jsonData = SOME STING WITH VALID JSON
let contacts = try JSONDecoder().decode(Contacts.self, from: jsonData)
}
catch {
}
This decodes just fine. If I do this simple change to the structure, it no longer decodes.
struct Contacts: Codable {
struct Recipient: Codable {
let name: String
}
let recipients: [Recipient] = []
}
Now JSONDecoder won't decode the exact same string. Why does the default initialization of the array cause the decoder to stop working?

Change your code to read:
struct Contacts: Codable {
struct Recipient: Codable {
let name: String
}
var recipients: [Recipient] = []
}
The issue (as described in the comments above) is that your initial declaration of the variable is immutable (meaning it cannot be changed after it is initialized). So, if you declare the initial value for a let as [] then you cannot subsequently change it. By changing let to var you are declaring the variable as mutable (meaning it can be changed) which allows you to both supply an initial value as well as change the value later.

Your let here isn't a "default initialization." It's defining recipients as a constant with a specific, compile-time, value. If you want a default value, then you can create a initializer for that if you like:
struct Contacts: Codable {
struct Recipient: Codable {
let name: String
}
let recipients: [Recipient]
init(recipients: [Recipient] = []) { self.recipients = recipients }
}
Generally speaking on this kind of very simple data type, I'd make recipients a var instead (see John Ayers's answer for example). It's much more flexible, while maintaining all the advantages of a value type. But in cases where you want a let constant with a default (rather than a single value), you need an init.

Related

Convert null values to default strings in parsing JSON using JSONDecoder in Swift

I am trying to parse some JSON in Swift using JSONDecoder where the JSON occasionally has null values. I would like to put in a default instead.
The following allows me to handle it but the nulls cause problems later.
struct Book: Codable {
let title : String
let author: String?
}
Is there a way to do something like (following does not compile), perhaps using an initializer?:
struct Book: Codable {
let title : String
let author: String ?? "unknown"
}
Thanks for any suggestions
This could be address by manually decoding as described here.
The other way to go would be to have the stored properties reflect the data exactly, and then have a computed var for the case of providing a non-optional value.
struct Book: Codable {
let title : String
let author: String?
var displayAuthor: String {
return author ?? "unknown"
}
}
The other reason this might be appealing is it preserves the optional value should you need to check if the value exists at all in the future.
You can achieve this using the custom init(decoder:) method definition. Use the decodeIfPresent API and give the property your desired default value if the try fails. Or you can use the computed property method mentioned by #dktaylor. Here the code you need:
struct Book {
let title : String
let author: String
enum CodingKeys: String, CodingKey {
case title, author
}
}
extension Book: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
title = try container.decode(String.self, forKey: .title)
author = try container.decodeIfPresent(String.self, forKey: .author) ?? "unknown"
}
}
You can also achieve this with a property wrapper:
#propertyWrapper
struct OnNil<T> {
let value: T
init(_ value: T) {
self.value = value
}
private var _wrappedValue: T?
var wrappedValue: T! {
get { _wrappedValue ?? value }
set { _wrappedValue = newValue }
}
}
struct SomeStruct {
let title : String
#OnNil("unknown")
let author: String!
}
The benefits of using a property wrapper like this is that you don't have to soil your object with utility methods, and you don't have to fiddle with the decode function. The downside obviously is that the syntax looks kind of odd, you have to make some variables implicitly unwrapped optional, and it makes your code slightly harder to debug due to the nature of property wrappers.

How I can decode and access Double from JSON using Alamofire and Swift Decode

I try do decode and access specific exchange rate for specific currency using Alamofire and Swift decode:
this is my model:
struct Listek: Codable {
let base: String
let date: String
let rates: [String: Double]
enum CodingKeys: String, CodingKey {
case base = "base"
case date = "date"
case rates = "rates"
}
}
this is Alamofire API call + decode
let apiToContact = "https://api.exchangeratesapi.io/latest"
AF.request(apiToContact).responseJSON { (response) in
print(response)
guard let data = response.data else { return }
do {
let st = try JSONDecoder().decode(Listek.self, from: data)
print (st.rates)
print (st.base)
print (st.date)
}
catch {
print("error")
}
So far so good, but I fail in accessing the single currency and its rate value. I would like declare a variable "JPYrate" with value of JPY rate from JSON. Can you please navigate me?
You can simply get the value corresponding to key JPY from rates Dictionary like so,
let JPYrate = st.rates["JPY"]
Also, there is no need to create enum CodingKeys, if the key names are same as the property names. So, your struct Listek looks like,
struct Listek: Codable {
let base: String
let date: String
let rates: [String:Double]
}
exchangeratesapi seems to send consistent data. if so my suggestion is to decode the rates into a dedicated struct
struct Listek: Decodable {
let base: String
let date: String
let rates: Rates
}
struct Rates: Decodable {
let CAD, HKD, ISK, PHP, DKK, HUF, CZK, AUD, RON, SEK, IDR, INR, BRL, RUB, HRK, JPY, THB, CHF, SGD, PLN, BGN, TRY, CNY, NOK, NZD, ZAR, USD, MXN, ILS, GBP, KRW, MYR : Double
}
Then you can get the rate directly
print(st.rates.JPY)
First, you can make a computed property to access the JPY rate:
var jpyRate: Double? { rates["JPY"] }
Or parse the rates into a specific type as recommended by #vadian.
Second, you can use Alamofire's responseDecodable method to do the decoding automatically.
AF.request(apiToContact).responseDecodable(of: Listek.self) { response in
debugPrint(response)
}

Casting to object with Codable

All my JSON responses follow the same structure:
"success": <http code>,
"data": [
]
Where the data sent back can vary. Sometimes it can contain Users, sometimes Comments, etc. So I want to create a Codable struct that is flexible to handle the various types of objects being sent back in the data array.
Here is my current struct:
struct BasicResponse: Codable {
let success: Int
let data: [User]
}
As you can see, it currently only handles User data being sent back.
Then, I read the JSON data like this (through Alamofire/Moya):
var users = [User]()
let results = try JSONDecoder().decode(BasicResponse.self, from: response.data)
self.users.append(contentsOf: results.data)
How can I change my struct file to be more flexible, and how would I then cast the JSON response to the desired object?
So, without going through a lot of design cycles and straight off the top my head, I'd consider trying Swift's generic support, for example...
struct BasicResponse<DataType>: Codable where DataType: Codable {
let success: Int
let data: [DataType]
}
Then you just need to define the implementation of DataTypes you want to use
struct User: Codable {
var name: String
}
And decode it...
let decoder = JSONDecoder()
let response = try decoder.decode(BasicResponse<User>.self, from: data)
print(response.data[0].name)
Now, I just threw this into a Playground and tested it with some basic data...
struct User: Codable {
var name: String
}
struct BasicResponse<T>: Codable where T: Codable {
let success: Int
let data: [T]
}
let data = "{\"success\": 200, \"data\": [ { \"name\":\"hello\" }]}".data(using: .utf8)!
let decoder = JSONDecoder()
do {
let response = try decoder.decode(BasicResponse<User>.self, from: data)
response.data[0].name
} catch let error {
print(error)
}
You might need to "massage" the design to better meet your needs, but it might give you a place to start

Swift 4 JSON Decoder

I know this has been covered in other questions, but I've followed them and I'm still stumped. Here is my JSON structure:
{
"FindBoatResult": {
"num_boats": 10,
"boat": [
{
"num_segments": 1,
"segments": [
{
"ident": "String",
"origin" : {
"code" : "String"
},
},
]
}
etc...but thats as deep as the structure goes. there are multiple returns of "segments" in each JSON response. In Swift I have this code.
struct Result : Decodable {
let FindBoatResult : FindBoatResult
}
struct FindBoatResult : Decodable {
let boats : Boats
let num_boats : Int
}
struct Boats : Decodable {
let segments : [Segments]
}
struct Segments : Decodable {
let ident : String?
let origin : Origin
}
struct Origin : Decodable {
let code : String
}
func getBoats() {
let urlString = "http://myApi"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else {return}
let dataAsString = String(data: data, encoding: .utf8)
//print(dataAsString)
do {
let boats = try
JSONDecoder().decode(FindBoatResult.self, from: data)
print(boats)
} catch {
print(err)
}
}.resume()
}
This fails and throws err but err prints as nil..so I can't tell what I'm missing. dataAsString prints out the JSON as expected, so I know "data" is good.
I detected a couple of minor issues. Try replacing this:
struct FindBoatResult: Decodable {
let boats: Boats
let num_boats: Int
}
struct Boats: Decodable {
let segments: [Segments]
}
with:
struct FindBoatResult: Decodable {
let boat: [Boat]
let num_boats: Int
}
struct Boat: Decodable {
let segments: [Segments]
}
Finally, decode using the Result type (not FindBoatResult):
JSONDecoder().decode(Result.self, from: data)
Expanding on Paulo's answer, I might further suggest that if you're stuck with JSON that has keys that don't conform to Swift conventions for property names, that you use the CodingKeys pattern to translate JSON keys to better Swift property names, e.g.:
struct BoatResult: Decodable { // I'd simplify this name
let boatCollection: BoatCollection
enum CodingKeys: String, CodingKey {
case boatCollection = "FindBoatResult"
}
}
struct BoatCollection: Decodable { // I'd simplify this, too, removing "Find" from the name; verbs are for methods, not properties
let boats: [Boat]
let numberOfBoats: Int
enum CodingKeys: String, CodingKey {
case boats = "boat" // "boat" isn't great property name for an array of boats, so let's map the poor JSON key to better Swift name here
case numberOfBoats = "num_boats" // likewise, let's map the "_" name with better camelCase property name
}
}
struct Boat: Decodable { // This entity represents a single boat, so let's use "Boat", not "Boats"
let segments: [Segment]
}
struct Segment: Decodable { // This entity represents a single segment, so let's use "Segment", not "Segments"
let identifier: String
let origin: Origin
enum CodingKeys: String, CodingKey {
case identifier = "ident" // `ident` isn't a common name for identifier, so let's use something more logical
case origin
}
}
struct Origin: Decodable {
let code: String
}
So, for example, use a plurals (e.g. boats) when you're representing an array of objects, and use CodingKeys to map the misleading boat JSON key to this better named boats array reference. Or when you have a key like num_boats, don't feel like you have to use that bad name in your Swift property and use something better like numberOfBoats (or count or whatever), and lose the _ syntax which is very unSwifty.
Clearly, if you're in control of the design of the JSON, you can just fix some of these poorly chosen key names there, but even where you decide you want your web service to use the _ syntax, go ahead and use CodingKeys to make sure your Swift objects honor the camelCase convention.

Default initialiser in protocol requires unwanted mutable properties

In Swift 3: Imagine you want your models to be value types (struct) through out your app. But you would also like persistence of said models using Core Data/Realm, which requires you to create classes. Then you can convert your structs to classes and vice verse using JSON (which would require structs and classes to both support JSON deserialization and serialization).
Wouldn't it be neat if you don't have to write JSON deserialization (and analogously for serialization, but I'm focusing on deserialization here) in two places, but use put deserialization in a protocol, that both your struct and class uses.
Using structs we want our JSON model to have immutable fields, thus all properites being let constants. But using a protocol implementation of the deserialization does not allow for this AFAIK.
The code example below works, but it is ugly, because of all unwanted requirements (UR) marked in comments in the code.
struct JSON {
let json: [String: Any]
func value<Value>(_ key: String) throws -> Value {
guard let value = json[key] as? Value else { throw NSError() }
return value
}
}
protocol JSONDeserializable {
init(json: JSON) throws
}
protocol UserModel: JSONDeserializable {
var username: String { get set } // Unwanted requirement (UR) #1: property needs "set" so that it can be initialized within protocol
init() // UR2: needs empty init, because of default implementation of `init(json: JSON)` in `extension UserModel`
}
extension UserModel {
init(json: JSON) throws {
self.init() // UR3: Needs to call this otherwise compilation error: `'self' used before chaining to another self.init requirement`
username = try json.value("username")
}
}
struct UserStruct: UserModel {
// UR4: property cannot be `let`, beause of `set` in protocol.
var username: String = "" // UR5: Property have to have default value because of it being a empty init
init() {}
}
final class UserClass: NSObject, UserModel {
// UR6: analogue with UR4
var username: String = "" // UR7: analogue with UR5
}
let json: JSON = JSON(json: ["username": "Sajjon"])
let u1 = try UserStruct(json: json)
let u2 = try UserClass(json: json)
print(u1.username) // prints "Sajjon"
print(u2.username) // prints "Sajjon"
Is there another way of achieving this, with a lower amount of unwanted requirements? Or an optimal solution with zero UR? 🙄
Thanks to what #hamish pointed out, the best solution would be (where struct JSON and protocol JSONDeserializable remains the same as in the question). This is not a perfect solution since you have to implement the initializer of the class. The neat part is that you don't have to implement any initializer for the struct, since it has one implicitly.
protocol UserModel: JSONDeserializable {
var username: String { get }
var firstname: String { get }
var country: String { get }
init(
username: String,
firstname: String,
country: String
)
}
extension UserModel {
init(json: JSON) throws {
self.init(
username: try json.value("username"),
firstname: try json.value("firstname"),
country: try json.value("country")
)
}
}
struct UserStruct: UserModel {
let username: String
let firstname: String
let country: String
// struct has default initializer
}
final class UserClass: UserModel {
let username: String
let firstname: String
let country: String
init(
username: String,
firstname: String,
country: String
) {
self.username = username
self.firstname = firstname
self.country = country
}
}
let json: JSON = JSON(json: [
"username": "Sajjon",
"firstname": "Alexander",
"country": "Sweden"
])
let u1 = try UserStruct(json: json)
let u2 = try UserClass(json: json)
print(u1.username) // prints "Sajjon"
print(u2.username) // prints "Sajjon"

Resources