How to create a JSON in SWFT using users input data? - swift

I need to create a JSON like this:
{
"var1": "1",
"var2": "2",
"newArray": [{
"Deleted": false
}]
}
My Codable looks like this:
struct RequestModel: Codable {
var var1: String?
var var1: String?
var Treatments: [Treat]
}
struct Treat: Codable {
var adminBy: String?
}
So, now I need to create the above JSON... I tried the following:
let adminBy = adminByTextField.text ?? ""
let VAR1 = var1TextField.text ?? ""
let VAR2 = var2TextField.text ?? ""
let dict = ["adminBy":adminBy] as! NSDictionary
///And I TRY to create my JSON here
private func createRequestObject() -> RequestModel {
let request = RequestModel(var1: VAR1,
var2: VAR2,
Treatments: dict)
return request
}
But I get this error:
I don't understand why this error occurs or how to fix it.
any advice would be appreciated.

You should not be using an NSDictionary here. You RequestModel object requires an array of Treat.
You could create the array in the following way and pass it to your RequestModel :
let adminBy = adminByTextField.text ?? ""
let VAR1 = var1TextField.text ?? ""
let VAR2 = var2TextField.text ?? ""
let treats = [Treat(adminBy: adminBy)]
let request = RequestModel(var1: VAR1, var2: VAR2, Treatments: treats)

Related

passing a struct type as a parameter throwing initialization error

I have the following code which has 3 objects which are then part of a 4th object. I'm getting errors trying to create the init method for the aggregate object (GTFS) because I'm passing (or trying to pass) the type of the 3 component objects (Stop, Route, Trip). I'm not sure why those have to be initialized before just their types being used.
protocol GTFSObject {
static var fileName: String { get }
init(csvRow: [String: String])
}
struct Stop: GTFSObject {
static let fileName = "stops.txt"
let stopID: String
let stopCode: String
let stopName: String
let stopDesc: String
let stopLat: Double
let stopLon: Double
let locationType: Int
let parentStation: String
init(csvRow: [String: String]) {
self.stopID = csvRow["stop_id"]!
self.stopCode = csvRow["stop_code"]!
self.stopName = csvRow["stop_name"]!
self.stopDesc = csvRow["stop_desc"]!
self.stopLat = Double(csvRow["stop_lat"]!)!
self.stopLon = Double(csvRow["stop_lon"]!)!
self.locationType = Int(csvRow["location_type"]!)!
self.parentStation = csvRow["parent_station"]!
}
}
struct Trip: GTFSObject {
static let fileName = "trips.txt"
let routeID: String
let serviceID: String
let tripID: String
init(csvRow: [String: String]) {
tripID = csvRow["trip_id"] ?? ""
routeID = csvRow["route_id"] ?? ""
serviceID = csvRow["service_id"] ?? ""
}
}
struct Route: GTFSObject {
static let fileName = "trips.txt"
let routeID: String
let agencyID: String
let routeShortName: String
let routeLongName: String
let routeDesc: String
let routeType: Int
let routeURL: String
let routeColor: String
let routeTextColor: String
init(csvRow: [String: String]) {
routeID = csvRow["route_id"] ?? ""
agencyID = csvRow["agency_id"] ?? ""
routeShortName = csvRow["route_short_name"] ?? ""
routeLongName = csvRow["route_long_name"] ?? ""
routeDesc = csvRow["route_desc"] ?? ""
routeType = Int(csvRow["route_type"] ?? "0") ?? 0
routeURL = csvRow["route_url"] ?? ""
routeColor = csvRow["route_color"] ?? ""
routeTextColor = csvRow["route_text_color"] ?? ""
}
}
class GTFS {
let routes: [Route]
let stops: [Stop]
let trips: [Trip]
init(gtfsFolderUrl: URL) {
self.stops = init_struct_from_url(gtfsFolderUrl: gtfsFolderUrl, type: Stop.self)
self.trips = init_struct_from_url(gtfsFolderUrl: gtfsFolderUrl, type: Trip.self)
self.routes = init_struct_from_url(gtfsFolderUrl: gtfsFolderUrl, type: Route.self)
}
private func init_struct_from_url<GTFSType>(gtfsFolderUrl: URL, type: GTFSType.Type) -> [GTFSType] where GTFSType : GTFSObject{
var returnList: [GTFSType] = []
let rows = try! NamedCSV(url: GTFS_FOLDER_URL.appendingPathComponent(type.fileName), delimiter: CSVDelimiter.comma, loadColumns: false).rows
for row in rows {
returnList.append(type.init(csvRow: row))
}
return returnList
}
}
The error I get is
'self' used in method call 'init_struct_from_url' before all stored properties are initialized
I don't know why I have to initialize the properties of the struct just to pass the type of the struct to this other function. What am I missing?
It's not about the three types that you are passing to init_struct_from_url. It's about the call init_struct_from_url itself.
You are actually calling:
self.init_struct_from_url(...)
^^^^^
The error is saying that that use of self is not allowed, because self is not initialised. If init_struct_from_url uses a property in self, it could see an uninitialised value.
Since init_struct_from_url doesn't actually use self at all and is just a helper function, you can make it an inner function of init:
init(gtfsFolderUrl: URL) {
func structFromUrl<GTFSType>(gtfsFolderUrl: URL, type: GTFSType.Type) -> [GTFSType] where GTFSType : GTFSObject{
var returnList: [GTFSType] = []
let rows = try! NamedCSV(url: GTFS_FOLDER_URL.appendingPathComponent(type.fileName), delimiter: CSVDelimiter.comma, loadColumns: false).rows
for row in rows {
returnList.append(type.init(csvRow: row))
}
return returnList
}
self.stops = structFromUrl(gtfsFolderUrl: gtfsFolderUrl, type: Stop.self)
self.trips = structFromUrl(gtfsFolderUrl: gtfsFolderUrl, type: Trip.self)
self.routes = structFromUrl(gtfsFolderUrl: gtfsFolderUrl, type: Route.self)
}

How to create Dictionary of value be type String and Array of any type?

I need to create dictionary that holds key-value pairs where Key of type String and Value of type Array of Struct, and need to subscript to get the count of Value of given Key. Please help me. Here is how i am creating the desired type, is it correct? and i don't know how to access specific value of a given key.
var BypassDictionary = [String: [ByPassList]]()
struct ByPassData
{
var name: String
var address: String
}
var bypassDictionary = [String: [ByPassData]]()
bypassDictionary["test"] = [ByPassData.init(name: "John", address: "FL"),ByPassData.init(name: "Joe", address: "CL")]
bypassDictionary["test1"] = [ByPassData.init(name: "John", address: "FL")]
if let getDataForTest = bypassDictionary["test"] {
let countForTest = getDataForTest.count
print(countForTest)
}
Here is a simple example
struct ByPassList {
var someValue: Int
}
var bypassDictionary = [String: [ByPassList]]()
bypassDictionary["hello"] = [ByPassList(someValue: 1), ByPassList(someValue: 2)]
if let test = bypassDictionary["hello"] {
print(test[1])
}
Note that var name should begin with a lowercase
Here is the Loop-based
struct ByPassList {
var someValue: Int
}
var dic = [String: [ByPassList]]()
dic["values"] = Array(0...100).map({ByPassList(someValue: $0)})
print(dic["values"])

how to remove double quote from empty array in codable struct

I'm making a POST request with URLSession. I need to send the following string in the request body:
{"rebajados": false, "text": "pantalon", "municipios": [], "departamentos": []}
so i define a struct to use codable to send data as request body. the struct is this.
struct filter: Codable {
var text: String?
var departamentos: [String]?
var municipios: [String]?
var rebajados = false
}
but what I send is this:
{
"departamentos": [
""
],
"municipios": [
""
],
"rebajados": false,
"text": "pantalon"
}
The backend returns no result because [""] makes it lost.
So what's a posible way to make the array a empty array without the double ""?
Note: I can't modifiy the backend to accept the array with empty string.
First of all please name structs with a starting capital letter.
An empty string array is encoded as empty JSON array
struct Filter : Codable {
let text : String
let departamentos : [String]
let municipios : [String]
let rebajados : Bool
}
let filter = Filter(text: "pantalon", departamentos: [], municipios: [], rebajados: false)
do {
let data = try JSONEncoder().encode(filter)
let string = String(data: data, encoding: .utf8)!
print(string) // {"rebajados":false,"municipios":[],"departamentos":[],"text":"pantalon"}
} catch {
print(error)
}
Try
struct filter:Codable {
var text: String?
var departamentos: [String] = []
var municipios: [String] = []
var rebajados = false
}

UTF-8 encoding issue of JSONSerialization

I was trying convert struct to Dictionary in Swift. This was my code:
extension Encodable {
var dictionary: [String: Any]? {
if let data = try? JSONEncoder().encode(self) {
if let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
return dict
}
return nil
}
return nil
}
}
This works in most situation. But when I try to convert a nested structure which contains unicode characters such as Chinese, this happened:
struct PersonModel: Codable {
var job: String?
var contacts: [ContactSimpleModel]
var manager: ManagerSimpleModel?
}
struct ContactSimpleModel: Codable {
var relation: String
var name: String
}
struct ManagerSimpleModel: Codable {
var name: String
var age: Int
}
let contact1 = ContactSimpleModel(relation: "朋友", name: "宙斯")
let contact2 = ContactSimpleModel(relation: "同学", name: "奥丁")
let manager = ManagerSimpleModel(name: "拉斐尔", age: 31)
let job = "火枪手"
let person = PersonModel(job: job, contacts: [contact1, contact2], manager: manager)
if let dict = person.dictionary {
print(dict)
}
The result of this code is this:
["contacts": <__NSArrayI 0x600002471980>(
{
name = "\U5b99\U65af";
relation = "\U670b\U53cb";
},
{
name = "\U5965\U4e01";
relation = "\U540c\U5b66";
}
)
, "manager": {
age = 31;
name = "\U62c9\U6590\U5c14";
}, "job": 火枪手]
You can see the result. The Chinese characters in those nested structures were become a utf-8 encoding string. The top-level property "job": 火枪手 is right. But the values in those nested structures were not the original string.
Is this a bug of JSONSerialization? Or how to make it right?
More information. I used the result like this:
var sortedQuery = ""
if let dict = person.dictionary {
sortedQuery = dict.sorted(by: {$0.0 < $1.0})
.map({ "\($0)\($1)" })
.joined(separator: "")
}
It was used to check whether the query was legal. The result is not the same as Java or other platform.
The result is perfectly fine. That's the internal string representation – a pre-Unicode legacy – of an array or dictionary when you print it.
Assign the values to a label or text view and you will see the expected characters.

Array of structs: UserDefaults, how to use?

I've already check all of those topics:
How to save an array of custom struct to NSUserDefault with swift?
How to save struct to NSUserDefaults in Swift 2.0
STRUCT Array To UserDefaults
I have a struct containing some Strings and an other struct: MySection.
struct MySection {
var name: String = ""
var values: [MyRow] = []
}
And there is MyRow which is store in MySection.values
struct MyRow {
var value: String = ""
var quantity: String = ""
var quantityType: String = ""
var done: String = ""
}
Two arrays for use it
var arraySection: [MySection] = []
var arrayRow: [MyRow] = []
And in my application, I add dynamically some values in those arrays.
There is the delegate method for get datas from my second ViewController
func returnInfos(newItem: [MyRow], sectionPick: String) {
arrayRow.append(MyRow())
arrayRow[arrayRow.count - 1] = newItem[0]
manageSection(item: sectionPick)
listTableView.reloadData()
}
And there is the manageSection function.
func manageSection(item: String) {
var i = 0
for _ in arraySection {
if arraySection[i].name == item {
arraySection.insert(MySection(), at: i + 1)
arraySection[i + 1].values = [arrayRow[arrayRow.count - 1]]
return
}
i += 1
}
arraySection.append(MySection())
arraySection[arraySection.count - 1].name = item
arraySection[arraySection.count - 1].values = [arrayRow[arrayRow.count - 1]]
}
My need is to store datas of the two arrays in UserDefaults (or CoreData maybe??) and use these datas when the user going back to the application.
I don't know how to do it, I've already try methods from the 3 topics but I'm not even doing a good job.
How can I do it?
Thanks guys!
Since both types contain only property list compliant types a suitable solution is to add code to convert each type to a property list compliant object and vice versa.
struct MySection {
var name: String
var values = [MyRow]()
init(name : String, values : [MyRow] = []) {
self.name = name
self.values = values
}
init(propertyList: [String: Any]) {
self.name = propertyList["name"] as! String
self.values = (propertyList["values"] as! [[String:String]]).map{ MyRow(propertyList: $0) }
}
var propertyListRepresentation : [String: Any] {
return ["name" : name, "values" : values.map { $0.propertyListRepresentation }]
}
}
struct MyRow {
var value: String
var quantity: String
var quantityType: String
var done: String
init(value : String, quantity: String, quantityType: String, done: String) {
self.value = value
self.quantity = quantity
self.quantityType = quantityType
self.done = done
}
init(propertyList: [String:String]) {
self.value = propertyList["value"]!
self.quantity = propertyList["quantity"]!
self.quantityType = propertyList["quantityType"]!
self.done = propertyList["done"]!
}
var propertyListRepresentation : [String: Any] {
return ["value" : value, "quantity" : quantity, "quantityType" : quantityType, "done" : done ]
}
}
After creating a few objects
let row1 = MyRow(value: "Foo", quantity: "10", quantityType: "Foo", done: "Yes")
let row2 = MyRow(value: "Bar", quantity: "10", quantityType: "Bar", done: "No")
let section = MySection(name: "Baz", values: [row1, row2])
call propertyListRepresentation to get a dictionary ([String:Any]) which can be saved to User Defaults.
let propertyList = section.propertyListRepresentation
Recreation of the section is quite easy, too
let newSection = MySection(propertyList: propertyList)
Edit
Use the propertyList initializer only if you get data from UserDefaults in all other cases use the other initializer.
For example replace
#IBAction func addButtonPressed(_ sender: Any) {
newProducts.append(MyRow(propertyList: ["":""]))
newProducts[newProducts.count - 1].value = nameTextField.text!
newProducts[newProducts.count - 1].quantity = quantityTextField.text!
newProducts[newProducts.count - 1].quantityType = type
newProducts[newProducts.count - 1].done = "No"
delegate?.returnInfos(newItem: newProducts, sectionPick: typePick)
navigationController?.popViewController(animated: true)
}
with
#IBAction func addButtonPressed(_ sender: Any) {
let row = MyRow(value: nameTextField.text!,
quantity: quantityTextField.text!,
quantityType: type,
done: "No")
newProducts.append(row)
delegate?.returnInfos(newItem: newProducts, sectionPick: typePick)
navigationController?.popViewController(animated: true)
}
and replace
func returnInfos(newItem: [MyRow], sectionPick: String) {
arrayRow.append(MyRow(propertyList: ["":""]))
arrayRow[arrayRow.count - 1] = newItem[0]
manageSection(item: sectionPick)
listTableView.reloadData()
}
with
func returnInfos(newItem: [MyRow], sectionPick: String) {
arrayRow.append(newItem[0])
manageSection(item: sectionPick)
listTableView.reloadData()
}
Basically first create the object, then append it to the array. The other way round is very cumbersome.