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

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.

Related

Try to parse JSON Array of Arrays on Swift

Here is the json code:
{
"rows":
[[{
"text":"some text",
"type":{
"type":"inlineUrl",
"url":"https://nothing.com"
}
}],
[{
"text":"some text",
"type":{
"type":"inlineCallback",
"data":"some data"
}
},
{
"text":"some text",
"type":{
"type":"inlineCallback",
"data":"some data"
}
}
]]
}
A more abbreviated would look like:
Rows = [ [Row1], [Row2, Row3] ]
And I'm trying to get it to work like this:
struct ReplyHandler: Codable {
let rows: [RowsHandler]
enum CodingKeys: String, CodingKey {
case rows
}
}
struct RowsHandler: Codable {
let row1: [RowHandler]
let row2: [Row2Handler]
enum CodingKeys: String, CodingKey {
case row1
case row2
}
}
struct RowHandler: Codable {
let text: String
let type: TypeHandler
enum CodingKeys: String, CodingKey {
case type = "type"
case text = "text"
}
}
struct Row2Handler: Codable {
let row1: RowHandler
let row2: RowHandler
enum CodingKeys: String, CodingKey {
case row1
case row2
}
}
struct TypeHandler: Codable {
let type: String
let url: String
enum CodingKeys: String, CodingKey {
case type = "type"
case url = "url"
}
}
But Xcode compiler gives this error:
Expected to decode String but found an array instead.
Don't understand how to properly nest other arrays in the array
While posting my question, I noticed that the arrays in "type" are different: the first row have url, the second and third have data, but anyway, it's not a point.
You can simplify your model to something like this:
struct ReplyHandler: Codable {
let rows: [[RowHandler]]
}
struct RowHandler: Codable {
let text: String
let type: TypeHandler
}
struct TypeHandler: Codable {
let type: String
var url: String?
var data: String?
}
Notice that url and data properties in TypeHandler are optionals to cover both cases.
Although manual JSON parsing is useful I think that it must be much easier to use tool like this https://app.quicktype.io/ to convert JSON into Swift classes or structs
For you JSON it provides this result
// MARK: - Welcome
struct Welcome: Codable {
let rows: [[Row]]
}
// MARK: - Row
struct Row: Codable {
let text: String
let type: TypeClass
}
// MARK: - TypeClass
struct TypeClass: Codable {
let type: String
let url: String?
let data: String?
}

Firebase replacing an entire array returning "Unsupported type: __SwiftValue"

I have a document that contains an array with the following structure:
"songs": [
{ "id": "asdasdasd", "votes": 1, "downVotedBy": [], "upVotedBy": ["user1"] }
]
When I'm trying to upvote the song using other user, after adding the user to the "upVotedBy" array, adding one to "votes" and ordering the songs based on the votes, I want to replace the entire array, so I do something like this:
ref.updateData([ "songs": songs ])
Where songs is an array containing elements of this custom class:
public struct SongModel: Codable {
var id: String
var votes: Int = 1
var upVotedBy: [String]
var downVotedBy: [String]
enum CodingKeys: String, CodingKey {
case id
case votes
case upVotedBy
case downVotedBy
}
}
But I get an Exception "Unsupported type: __SwiftValue"
My temporary solution was to convert the array of SongModel to an array of dictionaries:
private func convertSongToDic(songs: [SongModel]) -> [Any]{
var newSongs = [Any]()
for song in songs {
newSongs.append([
"id": song.id,
"votes": song.votes,
"upVotedBy": song.upVotedBy,
"downVotedBy": song.downVotedBy
])
}
return newSongs
}

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
}
}

How to decode JSON that has multiple lists that are unnamed in Swift [duplicate]

This question already has answers here:
Decoding JSON array of different types in Swift
(2 answers)
Closed 4 years ago.
I am trying to decode a JSON string but it seems to have multiple lists that do not have any names/keys that I can call with my structs. From what I can tell (with the little knowledge I have) there are two lists inside of this JSON and I only want the second list. I know how to decode normal JSON but figuring out how to call this keyless list/array is perplexing.
I tried to make my struct use 0 or 1, depending on which list I wanted, as the case name but that did not work either. I am really just confused on how to call something that is not named explicitly. Below is my JSON data and code.
Here is a small portion of the JSON:
[{
"page": 1,
"pages": 1,
"per_page": "5000",
"total": 58
},
[{
"indicator": {
"id": "NY.GDP.MKTP.CD",
"value": "GDP (current US$)"
},
"country": {
"id": "US",
"value": "United States"
},
"value": "19390604000000",
"decimal": "0",
"date": "2017"
},
{
"indicator": {
"id": "NY.GDP.MKTP.CD",
"value": "GDP (current US$)"
},
"country": {
"id": "US",
"value": "United States"
},
"value": "18624475000000",
"decimal": "0",
"date": "2016"
}]
]
Here is my Swift code:
let url = URL(string:"https://api.worldbank.org/countries/USA/indicators/NY.GDP.MKTP.CD?per_page=5000&format=json")!
let task = URLSession.shared.dataTask(with: url) {(data,response, error) in
if let data = data {
let jsonDecoder = JSONDecoder()
let countryData = try? jsonDecoder.decode(CountryData.self, from:data)
print(countryData?.data)
}
}
task.resume()
struct CountryData: Codable {
let data: [Country]
enum CodingKeys: String, CodingKey {
case data = ""
}
init(from decoder: Decoder) throws {
let valueContainer = try decoder.container(keyedBy: CodingKeys.self)
self.data = try valueContainer.decode([Country].self, forKey: CodingKeys.data)
}
}
struct Country: Codable {
let value: String
let date: String
let total: String
enum CodingKeys: String, CodingKey {
case value = "value"
case date = "date"
case total = "total"
}
init(from decoder: Decoder) throws {
let valueContainer = try decoder.container(keyedBy: CodingKeys.self)
self.value = try valueContainer.decode(String.self, forKey: CodingKeys.value)
self.date = try valueContainer.decode(String.self, forKey: CodingKeys.date)
self.total = try valueContainer.decode(String.self, forKey: CodingKeys.total)
}
}
extension URL {
func withQueries(_ queries: [String: String]) -> URL? {
var components = URLComponents(url: self, resolvingAgainstBaseURL: true)
components?.queryItems = queries.compactMap
{ URLQueryItem(name: $0.0, value: $0.1) }
return components?.url
}
}
I really just want to eventually access the dates and put them into an array for a tableView and be able to access the rest of the JSON data for the following view.
Thank you so much,
Jack
You simply need to use decoder.unkeyedContainer() when decoding a JSON Array manually. Then you can specify the type of the elements you want to decode one-by-one, which you'll need, since the first and second element of your array are different. If they were the same, you could simply decode it using JSONDecoder.decode([ArrayElementType].self).
struct CountryData: Decodable {
struct Indicator: Decodable {
let id:String
let value:String
}
struct Country: Decodable {
let id:String
let value:String
}
let indicator:Indicator
let country:Country
let value:String
let decimal:String
let date:String
}
struct CountryDataResponse: Decodable {
let countries:[CountryData]
struct CountryDataRoot: Decodable {
let page:Int
let pages:Int
let per_page:String
let total:Int
}
init(from decoder:Decoder) throws {
var container = try decoder.unkeyedContainer()
try container.decode(CountryDataRoot.self)
countries = try container.decode([CountryData].self)
}
}
let countries = try JSONDecoder().decode(CountryDataResponse.self, from: yourJson)

Convert array of custom object to String in swift4

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)
}

Resources