Convert array of custom object to String in swift4 - arrays

I am new to Swift and have an issue converting an array of custom object, to String.
This is my response class Tickets
public struct Tickets: Codable {
public let name: String!
public let status: String!
public let department: String!
}
After the webservice call i get following response and it would be mapped to Tickets class. Now, I have an array of "Tickets" as [Tickets] described below.
"tickets": [
{
"name": "d5b5d618-8a74-4e5f",
"status": "VALID",
"department": "IT"
},
{
"name": "a58f54b5-9420-49b6",
"status": "INVALID",
"department": "Travel"
}
]
Now, can I convert an array of [Tickets] to String? If so, how? Also, how to get it back as [Tickets] from a class of String.
I want to store it into UserDefaults after converting it to String, and retrieve it later

First of all:
Never declare properties or members in a struct or class as implicit unwrapped optional if they are supposed to be initialized in an init method. If they could be nil declare them as regular optional (?) otherwise as non-optional (Yes, the compiler won't complain if there is no question or exclamation mark).
Just decode and encode the JSON with JSONDecoder() and JSONEncoder()
let jsonTickets = """
{"tickets":[{"name":"d5b5d618-8a74-4e5f","status":"VALID","department":"IT"},{"name":"a58f54b5-9420-49b6","status":"INVALID","department":"Travel"}]}
"""
public struct Ticket: Codable {
public let name: String
public let status: String
public let department: String
}
do {
let data = Data(jsonTickets.utf8)
let tickets = try JSONDecoder().decode([String:[Ticket]].self, from: data)
print(tickets)
let jsonTicketsEncodeBack = try JSONEncoder().encode(tickets)
jsonTickets == String(data: jsonTicketsEncodeBack, encoding: .utf8) // true
} catch {
print(error)
}

Related

just one object nested in an array, how to decode the object?

I have this json
{
"status": [
{
"state": "checked",
"errorCode": "123",
"userId": "123456"
}
]
}
this is an array of statuses but is implemented badly because can be just one so I would like to decode just the status object
struct StatusResponse: Codable {
let state: String
let error: String
let id: String
enum CodingKeys: String, CodingKey {
case state = "state"
case error = "errorCode"
case id = "userId"
}
}
I try to custom decode it
let container = try decoder.container(keyedBy: ContainerKeys.self)
var statuses = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .status)
but as expected I get "Expected to decode Dictionary<String, Any> but found an array instead." how I can have access to the first object from statuses variable and decode it into StatusResponse? or some other idea on how to procede?
I would make a struct with field status to represent the top level object. That field is an array of StatusResponse:
struct TopLevelResponse: Codable {
var status: [StatusResponse]
}
when decoding the json:
let decoded = JSONDecoder().decode(TopLevelResponse.self, from: data)
let first = decoded.status.first! // Already decoded!
Unless it's guaranteed that there will be at least one item in the array then you should handle a nil case.
I will go with this solution inspired by this answer:
fileprivate struct RawStatusResponse: Decodable {
let status: [RawStatus]
struct RawStatus: Decodable {
let state: String
let errorCode: String
let userId: String
}
}
struct StatusResponse: Codable {
let state: String
let error: String
let id: String
enum CodingKeys: String, CodingKey {
case state = "state"
case error = "errorCode"
case id = "userId"
}
public init(from decoder: Decoder) throws {
let raw = try RawStatusResponse(from: decoder)
state = raw.status.first!.state
error = raw.status.first!.errorCode
id = raw.status.first!.userId
}
}
then when decode it just decode the actual object:
let state = try JSONDecoder().decode(StatusResponse, from: json)
You could decode it as a dictionary and use flatMap to get the array
let status = try JSONDecoder().decode([String: [StatusResponse]].self, from: data).flatMap(\.value)

Decoding JSON for single object vs array of objects in swift

I'm fairly new to the swift programming language and have been trying to get this to work for the last week or so. I'm working with an existing API that returns JSON data whose structure changes a little depending on how many venues get returned.
The real structure is somewhat more complicated, but this example illustrates the problem. In one flavor of result, I get a single venue returned like:
{
"totalItems": 21,
"pageSize": 2,
"venues": {
"venue":
{
"name": "Venue Name 1"
"location": "Location A",
"showCount": "4"
}
}
}
In another flavor of result, I get an array of venues returned:
{
"totalItems": 21,
"pageSize": 2,
"venues": {
"venue":
[{
"name": "Venue Name 1"
"location": "Location A",
"showCount": "4"
},
{
"name": "Venue Name 2"
"location": "Location B",
"showCount": "2"
}]
}
}
Yes - the owner of this API should have returned an array regardless, but they didn't and it cannot be changed.
I was able to get this to decode properly for an array of venues (or even if no venues were passed), but it aborts when a single venue is passed (due to the structure variation of course). My code also worked when I changed it to accommodate a single venue, but then returns of multiple venues aborted.
What I'd like to do is decode either variation into an internal structure containing an array regardless of which variation I receive, thus making things far simpler for me to deal with programmatically afterward. Something like this:
struct Response: Decodable {
let totalItems: Int
let pageSize: Int
let venues: VenueWrapper
struct VenueWrapper: Decodable {
let venue: [Venue] // This might contain 0, 1, or more than one venues
}
struct Venue: Decodable {
let name: String
let location: String
let showCount: Int
}
}
Note: In the actual JSON response, there are actually several substructures like this in the response (e.g., a single structure vs an array of structures) which is why I felt simply creating an alternate structure was not a good solution.
I'm hoping someone has come across this before. Thanks in advance!
You can create your own decoder,
struct Response: Decodable {
let totalItems: Int
let pageSize: Int
let venues: VenueWrapper
struct VenueWrapper: Decodable {
var venue: [Venue]
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
venue = []
if let singleVenue = try? values.decode(Venue.self, forKey: CodingKeys.venue) {
//if a single venue decoded append it to array
venue.append(singleVenue)
} else if let multiVenue = try? values.decode([Venue].self, forKey: CodingKeys.venue) {
//if a multi venue decoded, set it as venue
venue = multiVenue
}
enum CodingKeys: String, CodingKey { case venue }
}
}
struct Venue: Decodable {
let name: String
let location: String
let showCount: String
}
}
There's no need for VenueWrapper. 🙅🎁
struct Response {
let totalItems: Int
let pageSize: Int
let venues: [Venue]
struct Venue {
let name: String
let location: String
let showCount: Int
}
}
You'll need to write your own initializer. Unfortunately, I don't think there's a way to eliminate the boilerplate for the not-goofily-encoded properties.
Even if you never need to encode it, making Response Codable, not just Decodable, will give you access to an auto-generated CodingKeys.
extension Response: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
totalItems = try container.decode(Int.self, forKey: .totalItems)
pageSize = try container.decode(Int.self, forKey: .pageSize)
venues = try container.decode(key: .venues)
}
}
That last line relies on a protocol and extension which you can use for any other types that are similarly "encoded". 🙃
protocol GoofilyEncoded: Codable {
/// Must have exactly one case.
associatedtype GoofyCodingKey: CodingKey
}
extension KeyedDecodingContainer {
func decode<Decodable: GoofilyEncoded>(key: Key) throws -> [Decodable] {
let nestedContainer = try self.nestedContainer(
keyedBy: Decodable.GoofyCodingKey.self,
forKey: key
)
let key = nestedContainer.allKeys.first!
do {
return try nestedContainer.decode([Decodable].self, forKey: key)
}
catch {
return try [nestedContainer.decode(Decodable.self, forKey: key)]
}
}
}
All your types that might be encoded in arrays, or not 🤦‍♂️, will need a one-case enum, like so:
extension Response.Venue: GoofilyEncoded {
enum GoofyCodingKey: CodingKey {
case venue
}
}

Parsing array with {"Name": String, "Value": String} structure in Swift 4

I need to parse in Swift a data structure similar to this (based on a JSON):
[
{
"Name": "uniquename",
"Value": "John"
},
{
"Name": "locale",
"Value": "UK"
},
]
I stored this node in a struct like this
struct Rowset : Decodable {
var row: LoggedUserSession
init(loggedUser: [LoggedUserSession]){
self.row = loggedUser[0]
}
enum CodingKeys: String, CodingKey{
case row = "Row"
}
}
I prepared a similar struct to all the data I need to extract from the array but I don't know how to iterate on that and return the value when the name string match my case.
struct LoggedUserSession : Decodable {
var username: String;
var locale: String;
init(username: String, locale: String) {
// In JS I would embedd an iterator here and return the values
self.username = username
self.locale = locale
}
enum CodingKeys: String, CodingKey {
case username = "uniquename"
case locale = "locale"
}
}
If I understand what you are saying correctly, you want to parse an array of LoggedUserSession JSONs into a swift array of LoggedUserSessions. If that's the case, then you're almost there.
For completeness, I'm going to interpret the JSON you posted as follows so that it is valid:
{"loggedUserSessions":
[
{
"uniquename": "John",
"locale": "UK"
}
]
}
Your LoggedUserSession object is implemented correctly, so now all you need is the array parsing part. You can do that with the following struct:
struct SessionList: Decodable {
let sessions: [LoggedUserSession]
}
Calling this with JSONDecoder and your JSON data should serialize your list into an array that you can access via the SessionList's sessions property.

Accessing embedded JSON using decodable in Swift 4

I am trying to access a particular an embedded array of dictionaries to create my swift objects. I am unsure about how to access that array in the JSON dictionary.
Here is the definition of my Swift object = StarWarsPeople
class StarWarsPeople: Decodable {
var name: String?
var height: String?
var weight: String?
var hair_color: String?
var skin_color: String?
var eye_color: String?
var birth_year: String?
var gender: String?
}
Here is my APIClient class:
class StarWarsPeopleAPIClient
{
class func getStarWarsPeopleInformation (page: Int, completion:#escaping ([StarWarsPeople])-> ()) throws {
let starWarsPeopleURL = "https://swapi.co/api/people/?page=\(page)"
let convertedStarWarsPeopleURL = URL(string: starWarsPeopleURL)
guard let unwrappedConvertedStarWarsPeopleURL = convertedStarWarsPeopleURL else { print("unwrappedConvertedStarWarsPeopleURL did not unwrap"); return}
let request = URLRequest(url: unwrappedConvertedStarWarsPeopleURL)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let unwrappedData = data else { print("unwrappedData did not unwrap"); return}
do {
let starWarsPeopleDataArray = try JSONDecoder().decode([StarWarsPeople].self, from: unwrappedData)
completion(starWarsPeopleDataArray)
}
catch let error {
print("Error occured here: \(error.localizedDescription)")
}
}
task.resume()
}
}
Here is my Json, it is the results array that I would like to access, it is an array of dictionaries, which I need to iterate over to create my StarWarsPeople Object.
{
"count": 87,
"next": "url",
"previous": null,
"results": [
{
"name": "Luke Skywalker",
"height": "172",
"mass": "77",
"hair_color": "blond",
"skin_color": "fair",
"eye_color": "blue",
"birth_year": "19BBY",
"gender": "male",
"homeworld": "url",
"films": [
"url",
"url",
"url",
"url",
"url"
],
"species": [
"url"
],
"vehicles": [
"url",
"url"
Please read the JSON. You are ignoring the enclosing object
struct Root: Decodable {
let count: Int
let next: URL?
let previous: URL?
let results : [StarWarsPeople]
}
struct StarWarsPeople: Decodable {
private enum CodingKeys: String, CodingKey {
case name, height, mass
case hairColor = "hair_color", skinColor = "skin_color"
case eyeColor = "eye_color", birthYear = "birth_year", gender
}
let name: String
let height: String
let mass: String
let hairColor: String
let skinColor: String
let eyeColor: String
let birthYear: String
let gender: String
}
...
let root = try JSONDecoder().decode(Root.self, from: unwrappedData)
let starWarsPeopleDataArray = root.results
...
Notes:
A struct is sufficient.
Map the snake_cased keys to camelCased properties.
In almost all cases the properties can be declared as constants (let).
Don't declare all properties schematically as optional. Declare only those as optional which corresponding key can be missing or the value can be null.
Simply define a wrapper struct that holds the results property from the JSON response.
struct ApiResponse: Decodable {
results: [StarWarsPeople]
}
and later use
let apiResponse = try JSONDecoder().decode(ApiResponse.self, from: unwrappedData)
let starWarsPeopleDataArray = apiResponse.results
to parse it.
Results you are trying to fetch is actually present in results keys. Also we need to use properties same as parameter name( we can use CodingKeys enum too for overriding this).
So, first parse outer JSON , In new struct say StarWarsPeopleParent
class StarWarsPeopleParent: Decodable {
var count: Int?
var results: [StarWarsPeople]?
}
Update your StarWarsPeople struct's properties as:
class StarWarsPeople: Decodable {
var name: String?
var height: String?
var mass: String?
var hair_color: String?
var skin_color: String?
var eye_color: String?
var birth_year: String?
var gender: String?
}
Then parse it like:
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let unwrappedData = data else { print("unwrappedData did not unwrap"); return}
do {
let starWarsPeopleDataArray = try JSONDecoder().decode(StarWarsPeopleParent.self, from: unwrappedData)
completion(starWarsPeopleDataArray)
}
catch let error {
print("Error occured here: \(error.localizedDescription)")
}
}
Hope this is fine for you to understand.

Swift 4 JSON Codable - value returned is sometimes an object, others an array

the data I'm getting from an API returns a single object but when there's multiple objects, it returns an array in the same key. With the current model (struct) I'm working with, the decoding fails when an array shows up.
These results are randomly ordered, meaning I can't know when I will be served an object or array.
Is there a way to create a model that takes this fact into account and can assign the correct type to cast for the value ('String' or '[String]') so that the decoding continues without problem?
This is an example of when an object is returned:
{
"firstFloor": {
"room": "Single Bed"
}
}
This is an example of when an array is returned (for the same key):
{
"firstFloor": {
"room": ["Double Bed", "Coffee Machine", "TV", "Tub"]
}
}
Example of the struct that should be able to be used as model to decode both samples above:
struct Hotel: Codable {
let firstFloor: Room
struct Room: Codable {
var room: String // the type has to change to either array '[String]' or object 'String' depending on the returned results
}
}
These results are randomly ordered, meaning I can't know when I will be served an object or array.
Here is the complete playground file:
import Foundation
// JSON with a single object
let jsonObject = """
{
"firstFloor": {
"room": "Single Bed"
}
}
""".data(using: .utf8)!
// JSON with an array instead of a single object
let jsonArray = """
{
"firstFloor": {
"room": ["Double Bed", "Coffee Machine", "TV", "Tub"]
}
}
""".data(using: .utf8)!
// Models
struct Hotel: Codable {
let firstFloor: Room
struct Room: Codable {
var room: String // the type has to change to either array '[String]' or object 'String' depending on the results of the API
}
}
// Decoding
let decoder = JSONDecoder()
let hotel = try decoder.decode(Hotel.self, from: jsonObject) //
print(hotel)
You might encapsulate the ambiguity of the result using an Enum with Associated Values (String and Array in this case), for example:
enum MetadataType: Codable {
case array([String])
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self = try .array(container.decode(Array.self))
} catch DecodingError.typeMismatch {
do {
self = try .string(container.decode(String.self))
} catch DecodingError.typeMismatch {
throw DecodingError.typeMismatch(MetadataType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload not of an expected type"))
}
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .array(let array):
try container.encode(array)
case .string(let string):
try container.encode(string)
}
}
}
struct Hotel: Codable {
let firstFloor: Room
struct Room: Codable {
var room: MetadataType
}
}

Resources