Casting to object with Codable - ios

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

Related

Trying to decode data from Youtube Api popular videos in swift getting "Expected to decode Array<Any> but found a dictionary instead"

Im new to swift, and im learning to parse data from Api to my Swift Apps.
I tried to get data from Youtube APi of Popular video :(https://developers.google.com/youtube/v3/docs/videos/list) but I am not able to get the data, don't know where I am getting wrong. But its giving "Expected to decode Array but found a dictionary instead."
here is my model:
struct Items: Codable {
let kid : String
}
struct PopularVideos: Codable, Identifiable {
let id: String?
let kind : String
let items : [Items]
}
My Api request method:
//Getting Api calls for youtube video
func getYoutubeVideo(){
let url = URL(string: "https://www.googleapis.com/youtube/v3/videos?part=snippet&chart=mostPopular&regionCode=US&key=\(self.apiKey)")!
URLSession.shared.dataTask(with: url){(data, response, error) in
do {
let tempYTVideos = try JSONDecoder().decode([PopularVideos].self, from: data!)
print(tempYTVideos)
DispatchQueue.main.async {
self.YTVideosDetails = tempYTVideos
}
}
catch {
print("There was an error finding data \( error)")
}
} .resume()
}
The root object returned by that API call is not an array. It is a simple object that contains an array of Item.
So, you want
let tempYTVideos = try JSONDecoder().decode(PopularVideos.self, from: data!)
Also, your data structures don't look right; There is no id property in the root object and there is no kid property in the item. An item has a kind and an id.
I would also suggest that you name your struct an Item rather than Items, since it represents a single item;
struct Item: Codable, Identifiable {
let kind: String
let id: String
}
struct PopularVideos: Codable {
let kind : String
let items : [Item]
}

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

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.

How to parse through JSON with [] brackets?

I'm parsing through JSON on a website like so (under a request.httpMethod = "GET" in Swift
):
let example = json.data.first?.TShirtPrice
The JSON I'm getting is structured like so
{"returned":1,"data":[{"TShirtPrice":"5"}]}
But I have a new JSON set that is structured without [] brackets like so:
{"returned":1,"base":"USD","data":{"TShirtPrice":"3.448500"}}
The same exact code doesn't let me get the price of the shirt anymore -- what is the fix? Thank you!
This is my code
if let data = data {
do {
let json = try JSONDecoder().decode(Root.self,from: data)
let price = json.data.first?.TShirtPrice
struct Root: Codable {
let data: [Datum]
}
struct Datum: Codable {
let TShirtPrice: String
}
Assuming your data model is something as follows You can be using Struct or Class it's not an issue.
struct Root: Decodable {
let returned: Int?
let base: String?
let data: Price?
}
struct Price: Codable {
let TShirtPrice: String?
}
Sample JSON Sting is as follows
let jsonString = """
{
"returned": 1,
"base": "USD",
"data": {
"TShirtPrice": "3.448500"
}
}
"""
You just have to change the way data is parsed by making change in data model as given above and way to access the data as given below
if let data = jsonString.data(using: .utf8) {
let myObject = try JSONDecoder().decode(Root.self, from: data)
print(myObject.data?.TShirtPrice)
}
In your case it will look like this
if let data = data {
do {
let json = try JSONDecoder().decode(Root.self,from: data)
let Price = json.data?.TShirtPrice
}
}
What is changed here?
As your price data was in format of Array the code was written accordingly and as per new data it's not an Array anymore so you have to adapt those changes app side also.

How to handle partially dynamic JSON with Swift Codable?

I've got some JSON messages coming in over a websocket connection.
// sample message
{
type: "person",
data: {
name: "john"
}
}
// some other message
{
type: "location",
data: {
x: 101,
y: 56
}
}
How can I convert those messages into proper structs using Swift 4 and the Codable protocol?
In Go I can do something like: "Hey at the moment I only care about the type field and I'm not interested in the rest (the data part)." It would look like this
type Message struct {
Type string `json:"type"`
Data json.RawMessage `json:"data"`
}
As you can see Data is of type json.RawMessage which can be parsed later on. Here is a full example https://golang.org/pkg/encoding/json/#example_RawMessage_unmarshal.
Can I do something similar in Swift? Like (haven't tried it yet)
struct Message: Codable {
var type: String
var data: [String: Any]
}
Then switch on the type to convert the dictionary into proper structs. Would that work?
I wouldn't rely upon a Dictionary. I'd use custom types.
For example, let's assume that:
you know which object you're going to get back (because of the nature of the request); and
the two types of response truly return identical structures except the contents of the data.
In that case, you might use a very simple generic pattern:
struct Person: Decodable {
let name: String
}
struct Location: Decodable {
let x: Int
let y: Int
}
struct ServerResponse<T: Decodable>: Decodable {
let type: String
let data: T
}
And then, when you want to parse a response with a Person, it would be:
let data = json.data(using: .utf8)!
do {
let responseObject = try JSONDecoder().decode(ServerResponse<Person>.self, from: data)
let person = responseObject.data
print(person)
} catch let parseError {
print(parseError)
}
Or to parse a Location:
do {
let responseObject = try JSONDecoder().decode(ServerResponse<Location>.self, from: data)
let location = responseObject.data
print(location)
} catch let parseError {
print(parseError)
}
There are more complicated patterns one could entertain (e.g. dynamic parsing of the data type based upon the type value it encountered), but I wouldn't be inclined to pursue such patterns unless necessary. This is a nice, simple approach that accomplishes typical pattern where you know the associated response type for a particular request.
If you wanted you could validate the type value with what was parsed from the data value. Consider:
enum PayloadType: String, Decodable {
case person = "person"
case location = "location"
}
protocol Payload: Decodable {
static var payloadType: PayloadType { get }
}
struct Person: Payload {
let name: String
static let payloadType = PayloadType.person
}
struct Location: Payload {
let x: Int
let y: Int
static let payloadType = PayloadType.location
}
struct ServerResponse<T: Payload>: Decodable {
let type: PayloadType
let data: T
}
Then, your parse function could not only parse the right data structure, but confirm the type value, e.g.:
enum ParseError: Error {
case wrongPayloadType
}
func parse<T: Payload>(_ data: Data) throws -> T {
let responseObject = try JSONDecoder().decode(ServerResponse<T>.self, from: data)
guard responseObject.type == T.payloadType else {
throw ParseError.wrongPayloadType
}
return responseObject.data
}
And then you could call it like so:
do {
let location: Location = try parse(data)
print(location)
} catch let parseError {
print(parseError)
}
That not only returns the Location object, but also validates the value for type in the server response. I'm not sure it's worth the effort, but in case you wanted to do so, that's an approach.
If you really don't know the type when processing the JSON, then you just need to write an init(coder:) that first parses the type, and then parses the data depending upon the value that type contained:
enum PayloadType: String, Decodable {
case person = "person"
case location = "location"
}
protocol Payload: Decodable {
static var payloadType: PayloadType { get }
}
struct Person: Payload {
let name: String
static let payloadType = PayloadType.person
}
struct Location: Payload {
let x: Int
let y: Int
static let payloadType = PayloadType.location
}
struct ServerResponse: Decodable {
let type: PayloadType
let data: Payload
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
type = try values.decode(PayloadType.self, forKey: .type)
switch type {
case .person:
data = try values.decode(Person.self, forKey: .data)
case .location:
data = try values.decode(Location.self, forKey: .data)
}
}
enum CodingKeys: String, CodingKey {
case type, data
}
}
And then you can do things like:
do {
let responseObject = try JSONDecoder().decode(ServerResponse.self, from: data)
let payload = responseObject.data
if payload is Location {
print("location:", payload)
} else if payload is Person {
print("person:", payload)
}
} catch let parseError {
print(parseError)
}

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.

Resources