How can I create a more detailed array with objects | Swift - swift

Essentially I have been creating the following simple String/Int arrays to record info
into an array set (which of course checks for duplicates before appending) how can I record an ID alongside the string to create a JSON array?
Currently doing the following:
var myArray = Set<String>()
myArray.insert("Example1")
["Example1","Example2","Example3"]
Example of what I would like to achieve.
[{"myexample":"Example2","id":1},{"myexample":"Example3","id":2}]

You can use a struct that conforms to Codable to represent your data:
struct Model : Codable, Hashable {
var myexample: String
var id: Int
}
var mySet = Set([Model(myexample: "Example2", id: 1),
Model(myexample: "Example3", id: 2)])
mySet.insert(Model(myexample: "Example4", id: 3))
do {
let json = try JSONEncoder().encode(mySet)
print(String(data: json, encoding: .utf8)!)
} catch {
print(error)
}

Related

Initialising 'top' struct in Swift

I'm currently attempting to initialise a struct with Swift, that goes several structs deep. Once an external API is called, it sends back data which I want to place into a struct so that I can use it to populate a TableView.
I'm wanting to decode data that looks like this:
{
"data": {
"1": {
"name": "Bitcoin",
"quote": {
"GBP": {
"price": 25794.72142905233,
"percent_change_1h": -1.4133929,
"percent_change_24h": -0.74636982,
"percent_change_7d": -5.8533249
}
}
},
"52": {
"name": "XRP",
"quote": {
"GBP": {
"price": 0.7157097479533718,
"percent_change_1h": -1.35513268,
"percent_change_24h": 1.84172355,
"percent_change_7d": 5.05130272
}
}
}
}
To do this, I have the following Struct structure:
Coin struct which references data
Datum struct which references the '1' and '52'
Quote struct which references the 'quote'
GBP struct which references the 'price', 'percent_change_1h'
These look like this:
struct Coin: Codable {
let data: [String: Datum]
}
struct Datum: Codable {
let name: String
let quote: Quote
}
struct Quote: Codable {
let gbp: Gbp
enum CodingKeys: String, CodingKey {
case gbp = "GBP"
}
}
struct Gbp: Codable {
let price, percentChange1H, percentChange24H, percentChange7D: Double
enum CodingKeys: String, CodingKey {
case price
case percentChange1H = "percent_change_1h"
case percentChange24H = "percent_change_24h"
case percentChange7D = "percent_change_7d"
}
}
I'm then attempting to set up a variable which is an empty 'Coin' struct under the variable name 'coins' like so:
var coins = Coin()
Unfortunately - trying to do this prompts the following error:
Missing argument for parameter 'data' in call
If I then follow the prompts, I can insert the following:
var coins = Coin(data: [String : Datum])
But this then produces the following error:
Cannot convert value of type '[String: Datum].Type' to expected
argument type '[String: Datum]'
Please can somebody point out what is going wrong here? Have I built my structs incorrectly? Is there an alternative I can do?
Rather than [String : Datum], which the compiler is telling you is a Type, use [:], which is the Swift syntax for an empty Dictionary:
var coins = Coin(data: [:])

Create variables from parsed JSON file to tableView

I have this printed output from parsed JSON file:
[App.Root(fields: ["12345": App.Field(timestampValue: "2020-02-04"), "7895": App.Field(timestampValue: "2020-02-04")], createTime: "2020-02-04", updateTime: "2020-02-04")]
There is my code from UserData.swift file:
import Foundation
struct Root: Codable {
let fields: [String: Field]
let createTime, updateTime: String
}
// MARK: - Field
struct Field: Codable {
let timestampValue: String
}
How can I create variable for etc.:Id 12345?
I need to put it in tableView in this format:
ID:12345
Date:2020-02-04
next row in table view:
ID:7895
Date:2020-02-04
Many thanks for some suggestions :)
You can try
let res = try jsonDecoder.decode(Root.self, from: data)
var all = [Item]()
for (id,item) in res.fields {
all.append(Item(id:id,timestampValue:item.timestampValue))
}
struct Item {
let id,timestampValue:String
}
Then use all as table dataSource array

Decoding numerical snake_case keys with JSONDecoder

I have the following JSON object which is to be converted to an object using JSONDecoder:
{
"first_key": 3,
"image_1000x1000": "location"
}
This maps to the following Swift model:
struct Response: Decodable {
var firstKey: Int
var image1000x1000: String
}
By using JSONDecoder with the .convertFromSnakeCase option, the snake_case keys inside the JSON are transformed to camelCase by using the algorithm defined in the documentation:
This strategy follows these steps to convert JSON keys to camel-case:
Capitalize each word that follows an underscore.
Remove all underscores that aren't at the very start or end of the string.
Combine the words into a single string.
Therefore, in this case:
first_key becomes firstKey (as expected)
image_1000x1000 should become image1000x1000
However when attempting to decode this response, a keyNotFound error for the image1000x1000 key is thrown (see this live example):
let json = "{\"first_key\": 3, \"image_1000x1000\": \"location\"}".data(using: .utf8)!
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let response = try decoder.decode(Response.self, from: json)
print(response)
} catch let e {
print(e)
}
What is incorrect about my camel case conversion of image_1000x1000, and why can’t JSONDecoder find the corresponding key?
You can see what the algorithm is expecting by running the process in reverse; use a JSONEncoder to encoder your data and inspect the output:
struct Response: Codable {
var firstKey: Int
var image1000x1000: String
}
let test = Response(firstKey: 10, image1000x1000: "secondKey" )
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
let data = try encoder.encode(test)
print(String(data: data, encoding: .utf8)!)
This will produce:
{"first_key":10,"image1000x1000":"secondKey"}
So if you have control over the JSON and can live with image1000x1000 as a key, then you're done. If not you'll have to do something like this:
struct Response: Codable {
var firstKey: Int
var image1000x1000: String
private enum CodingKeys: String, CodingKey {
case image1000x1000 = "image_1000x1000"
case firstKey = "first_key"
}
}
Another option is to implement a custom key encoding strategy. It may end up being less code. See KeyEncodingStrategy for more about this.
I am facing the same issue my json keys are iso639_1 and iso639_2. Without adding Coding key it will works.
make your variable optional
Here it's my Decodable model
struct CountryModel: Decodable{
var name: String
var borders: [String]
var region: String
var alpha2Code: String
var alpha3Code: String
var flag: String
var languages: [languages]
}
struct languages: Decodable {
var iso639_1: String?
var iso639_2: String?
var name: String
var nativeName: String
}
When I add optional to both underscore variables iso639_1 and iso639_2. Then it works fine may be due to Null Value!
Here, in your case add optional to your image1000x1000 variable. Like below
struct Response: Decodable {
var firstKey: Int
var image_1000x1000: String?
}
Hope it will works!

How to create nested dictionary elements in Swift?

I want to create a variable which stores this:
["messageCode": API_200, "data": {
activities = (
{
action = 1;
state = 1;
}
);
messages = (
{
body = hi;
// ...
}
);
}, "message": ]
What I have done is this:
var fullDict: Dictionary<String, AnyObject> = [:]
fullDict["messageCode"] = "API_200" as AnyObject
var data: Dictionary<String, AnyObject> = [:]
fullDict ["data"] = data as AnyObject
Is this way is correct and how I can add activities?
I would suggest to go with creating a custom Model:
struct Model {
var messageCode: String
var data: MyData
var message: String
}
struct MyData {
let activities: [Activity]
let messages: [Message]
}
struct Activity {
var action: Int
var state: Int
}
struct Message {
var body: String
// ...
}
Thus you could use it as:
let data = MyData(activities: [Activity(action: 1, state: 1)], messages: [Message(body: "hi")])
let myModel = Model(messageCode: "API_200", data: data, message: "")
However, if you -for some reason- have to declare it as a dictionary, it could be something like this:
let myDict: [String: Any] = [
"messageCode": "API_200",
"data": ["activities": [["action": 1, "state": 1]],
"messages": [["body": "hi"]]
],
"message": ""
]
which means that myDict is a dictionary contains:
messageCode string.
data as nested dictionary, which contains:
activities array of dictionaries (array of [String: Int]).
messages array of dictionaries (array of [String: String]).
message string.
One of the simplest reasons why you should go with the modeling approach is because when it comes to read from myModel, all you have to do is to use the dot . notation. Unlike working with it as a dictionary, you would have to case its values which could be a headache for some point. For instance, let's say that we want to access the first message body in data messages array:
Model:
myModel.data.messages.first?.body
Dictionary:
if let data = myDict["data"] as? [String: [[String: Any]]],
let messages = data["messages"] as? [[String: String]],
let body = messages.first?["body"] {
print(body)
}
Since you explicitly want it as [String:AnyObject]:
var dict: [String:AnyObject] = ["messageCode":"API_200" as AnyObject,
"data": ["activities": [["action":1,
"state":1]],
"messages": [["body":"hi"]]] as AnyObject,
"message": "" as AnyObject]
Basically all the root values should be typecasted as AnyObject
Or the long way:
//Activities is as Array of dictionary with Int values
var activities = [[String:Int]]()
activities.append(["action": 1,
"state": 1])
//Messages is an Array of string
var messages = [[String:String]]()
messages.append(["body" : "hi"])
//Data is dictionary containing activities and messages
var data = [String:Any]()
data["activities"] = activities
data["messages"] = messages
//Finally your base dictionary
var dict = [String:AnyObject]()
dict["messageCode"] = "API_200" as AnyObject
dict["data"] = data as AnyObject
dict["message"] = "" as AnyObject
print(dict)
Parsing this to get your data back will be hell; with all the type casts and all.
Example (lets capture action):
let action = ((dict["data"] as? [String:Any])?["activities"] as? [String:Int])?.first?.value
As you can see you need to typecast at every level. This is the problem with using dictionaries in Swift. Too much cruft.
Sure, you could use a third-party library like SwiftyJSON to reduce the above to:
let action = dict["data"]["activities"][0]["action"]
But do you want a dependency just for something as simple as this?
Instead...
If your structure is defined then create models instead; as Ahmad F's answer suggests. It will be more readable, maintainable and flexible.
...but since you asked, this is how one would do it with pure Dictionary elements.

How do I correctly print a struct?

I'm trying to store an array of store structs within my users struct, but I can't get this to print correctly.
struct users {
var name: String = ""
var stores: [store]
}
struct store {
var name: String = ""
var clothingSizes = [String : String]()
}
var myFirstStore = store(name: "H&M", clothingSizes: ["Shorts" : "Small"])
var mySecondStore = store(name: "D&G", clothingSizes: ["Blouse" : "Medium"])
var me = users(name: "Me", stores: [myFirstStore, mySecondStore])
println(me.stores)
You’re initializing them just fine. The problem is your store struct is using the default printing, which is an ugly mangled version of the struct name.
If you make it conform to CustomStringConvertible, it should print out nicely:
// For Swift 1.2, use Printable rather than CustomStringConvertible
extension Store: CustomStringConvertible {
var description: String {
// create and return a String that is how
// you’d like a Store to look when printed
return name
}
}
let me = Users(name: "Me", stores: [myFirstStore, mySecondStore])
println(me.stores) // prints "[H&M, D&G]"
If the printing code is quite complex, sometimes it’s nicer to implement Streamable instead:
extension Store: Streamable {
func writeTo<Target : OutputStreamType>(inout target: Target) {
print(name, &target)
}
}
p.s. convention is to have types like structs start with a capital letter