I looking to convert a row of SQlite to JSON array.
Example:
{
"0": {
"room_id": "5034"
},
"1": {
"room_id": "5199"
},
"2": {
"room_id": "5156"
}
}
Swift4 code:
typealias Rooms = [String: Room]
struct Room: Codable {
let roomID: String
enum CodingKeys: String, CodingKey {
case roomID = "room_id"
}
}
var rooms = [Rooms]()
for room in try (db?.prepare(isco_room_time))! {
let export: Room = Room(roomID: room[room_id])
rooms.append(export)
}
My error (on line rooms.append) :
Cannot convert value of type 'ViewController.Room' to expected argument type 'ViewController.Rooms' (aka 'Dictionary')
Why do you need this?
typealias Rooms = [String: Room]
If you want an array of Codable objects you don't need the alias
Changing
var rooms = [Rooms]()
To
var rooms = [Room]()
Will work.
Since rooms is a dictionary you shouldn't use append
for room in try (db?.prepare(isco_room_time))! {
let export: Room = Room(roomID: room[room_id])
rooms[room_id] = export
}
You can try
var counter = 0
var rooms = [String:Room]()
do {
guard let dbs = db else { return }
let res = try dbs.prepare(isco_room_time)
for room in res {
let export: Room = Room(roomID: room.id)
rooms["\(counter)"] = export
counter += 1
}
}
catch {
print(error)
}
Related
Currently I get data from json file located on http server
if let url = NSURL(string: "https://test.com/test") {
do {
let data = try Data(contentsOf: url as URL)
let decoder = JSONDecoder()
jsonData = try decoder.decode(TestList.self, from: data)
} catch {
print("error:\(error)")
}
struct TestList: Decodable {
enum CodingKeys: String, CodingKey {
case items
}
let items: [Item]
}
struct Item: Decodable {
var item_type: String?
//...
enum CodingKeys: String, CodingKey {
case item_type
//...
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.item_type = try? container.decode(String.self, forKey: .mtype)
//...
}
}
Everything works fine but when
i switched to firebase realtime database and get the same json data
Database.database().reference().child("items").observeSingleEvent(of: .value, with: { snapshot in
guard let value = snapshot.value else { return }
do {
self.jsonData = try FirebaseDecoder().decode(TestList.self, from: value)
} catch let error {
print(error)
}
})
My JSON:
{
"items": [
{
"item_type": "Rap",
"1": "Kastro",
"2": "EDI",
"3": "Noble",
"4": ""
},
{
"item_type": "Rock",
"1": "Nickelback",
"2": "",
"3": "",
"4": ""
},
{
"item_type": "Pop",
"1": "Ringo",
"2": "",
"3": "",
"4": ""
}
]
}
got this message:
typeMismatch(Swift.Dictionary,
Swift.DecodingError.Context(codingPath: [], debugDescription: "Not a
dictionary", underlyingError: nil))
How to fix this error ?
I had this problem before, this is what I did to fix it. So let's say this is how my database looks like. Pay attention to the naming convention, in this example its snake case.
Let's say I want to create an array of users from this snapshot and populate my tableView. This is tricky cause the snapshot.value isn't really JSON and has a value of Any. This is probably why your app is crashing or in your case type is mismatch.
Lets create a model for our users. It will look something like this.
class User: Codable {
var firstName: String
var lastName: String
var ageNumber: Int
enum CodingKeys: String, CodingKey {
case firstName,lastName
case ageNumber = "age"
}
}
So let me point something our really fast. In my firebase database the user properties look like this 'first_name', 'age' 'last_name'. In my user model it look like this 'firstName', 'ageNumber' 'lastName'. So I had to add the coding keys because of the age not because of the first or last name. The first & last name changes by themselves when I set the keyCodingStrategy on my decoder.
This is how that code looks like. Left some comments to better explain what the code does.
var items = [Item]()
override func viewDidLoad() {
super.viewDidLoad()
// Use your database reference here.
let ref = Database.database().reference(withPath: "users")
ref.observe(.value) { (snapshot) in
//First create a dict out of the snapshot value
guard let dict = snapshot.value as? [String: Any] else { return }
//Create a decoder this is why I don't need to chage the first_name to firstName
//inside my coding keys
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
//The key is the UID and the value is what we need to create a new users
dict.forEach { (key, value) in
do {
//Create new user and add it to our users array
//We convert the value to data for the decoder with this line of code
let testListData = try JSONSerialization.data(withJSONObject: value, options: .prettyPrinted)
let testList = try decoder.decode(TestList.self, from: testListData)
testList.items.forEach({ (item) in
self.items.append(item)
})
}
catch let err {
print(err.localizedDescription)
}
}
//Reload Table View
}
}
Hope this helps.
Edited
So based on the JSON you provided. You will have to create your structs something like this.
struct TestList: Codable {
var items: [Item]
}
struct Item: Codable {
var itemType: String
var one: String
var two: String
var three: String
var four: String
enum CodingKeys: String, CodingKey {
case itemType
case one = "1"
case two = "2"
case three = "3"
case four = "4"
}
}
Hopefully this will solve your problem.
After receiving a JSON payload from a web request, I need to parse out the data into variables in an NSObject subclass.
My variables are declared in the object:
var name:String?
var email:String?
var aboutMe:String?
Then I start parsing through all the possible data the JSON may return:
if let name = json["Name"] as? String
{
self.name = name
}
if let email = json["Email"] as? String
{
self.email = email
}
if let aboutMe = json["AboutMe"] as? String
{
self.aboutMe = aboutMe
}
This is becoming rather long as we have a lot of data.
I was wanting to shorted it by using a Dictionary containing the JSON key and the variable like this:
let varMap:[String:Any?] = [
"Name": self.name,
"Email": self.email,
"AboutMe": self.aboutMe
]
Then iterating over them like this:
for (key, var variable) in varMap
{
if let string = json[key]
{
variable = string
}
}
The problem here is that the Dictionary copies the value from the variable and not a pointer to it, so setting it to a new value has no effect on the variables of the overall object.
Is there a way to achieve what I am trying? If not, what other pattern would be a better way to do this instead of having tons of if statements?
For JSON parsing, you can simply use Codable types.
Let's assume your JSON looks like,
{
"name": "Alex",
"email": "alex#gmail.com",
"about_Me": "My name is Alex"
}
Models:
class Root: Decodable {
let name: String?
let email: String?
let aboutMe: String?
enum CodingKeys: String, CodingKey {
case name, email
case aboutMe = "about_Me"
}
}
Parse the JSON data like so,
do {
let response = try JSONDecoder().decode(Root.self, from: data)
print(response) //use response here...
} catch {
print(error)
}
You can use updateValue function for that, it finds the property and changes it.
if let oldValue = varMap.updateValue(self.name, forKey: "Name") {
print("The old value of \(oldValue) was replaced with a new one.")
}
So you for iteration is;
for (key, var variable) in varMap
{
varMap.updateValue(string, forKey: key )
//if let string = json[key]
//{
// variable = string
//}
}
After you update the dictionary you can call that part;
if let name = json["Name"] as? String
{
self.name = name
}
if let email = json["Email"] as? String
{
self.email = email
}
if let aboutMe = json["AboutMe"] as? String
{
self.aboutMe = aboutMe
}
I believe that part in a function, if its not, you can refactor it.
https://developer.apple.com/documentation/swift/dictionary/3127179-updatevalue
What is my json looks like :
{
"2019-08-27 19:00:00": {
"temperature": {
"sol":292
}
}
,
"2019-08-28 19:00:00": {
"temperature": {
"sol":500
}
}
}
Here is a method to get the current next five days in the format needed :
func getFormatedDates() -> [String] {
let date = Date()
let format = DateFormatter()
format.dateFormat = "yyyy-MM-dd"
var dateComponents = DateComponents()
var dates = [String]()
for i in 0...4 {
dateComponents.setValue(i, for: .day)
guard let nextDay = Calendar.current.date(byAdding: dateComponents, to: date) else { return [""] }
let formattedDate = format.string(from: nextDay)
dates.append(formattedDate + " " + "19:00:00")
}
return dates
}
Because date key in the API constantly changes, I need dynamic keys. I would like to use this method inside an enum like in my Model :
var dates = getFormatedDates()
let firstForcast: FirstForcast
let secondForcast: SecondForcast
enum CodingKeys: String, CodingKey {
case firstForcast = dates[0]
case secondForcast = dates[1]
}
Any ideas ?
Create related types as below,
// MARK: - PostBodyValue
struct PostBodyValue: Codable {
let temperature: Temperature
}
// MARK: - Temperature
struct Temperature: Codable {
let sol: Int
}
typealias PostBody = [String: PostBodyValue]
and decode like this,
do {
let data = // Data from the API
let objects = try JSONDecoder().decode(PostBody.self, from: data)
for(key, value) in objects {
print(key)
print(value.temperature.sol)
}
} catch {
print(error)
}
I can print only 1 data, not any more. This is my error:
Thread 1: Fatal error: Index out of range
This is my JSON:
[
{
"Guides": [
{
"_id": "5cbc780edfdb6307006aec37",
"Text": "He is one of Soroush Friend",
"Tavernier": 2
},
{
"_id": "5cbc781bdfdb6307006aec38",
"Text": "He is one of Soroush Friend",
"Tavernier": 2
}
]
}
]
And this is my struct that works well:
struct GuideStruct: Codable {
let guides: [Guide]
enum CodingKeys: String, CodingKey {
case guides = "Guides"
}
}
struct Guide: Codable {
let id, text: String
let tavernier: Int
enum CodingKeys: String, CodingKey {
case id = "_id"
case text = "Text"
case tavernier = "Tavernier"
}
}
And this is my array and my class:
internal static var guides = [guidesarr]()
class guidesarr {
var _id : String
var Text : String
var Tavernier : Int
init(_id : String,Text : String,Tavernier : Int) {
self._id = _id
self.Text = Text
self.Tavernier = Tavernier
}
}
And my codes in viewcontroller:
class GameViewController: UIViewController,UITextFieldDelegate {
typealias guide1 = [GuideStruct]
var i1 = 0
override func viewDidLoad() {
super.viewDidLoad()
let headers : HTTPHeaders = ["Content-Type":"application/json","OAtcp":"0!QSJ5SDG8Q39PPM$DXP5HD1E10"]
Alamofire.request("http://192.168.1.100:3535/DarkDiamonds/Api/GetActiveGames",method :.post,headers: headers).responseJSON { (newresponse) in
do {
let decoder = JSONDecoder()
let responseguide = try decoder.decode(guide1.self, from: newresponse.data!)
for each1 in responseguide {
let newstruct = guidesarr(_id:each1.guides[self.i1].id , Text: each1.guides[self.i1].text, Tavernier: each1.guides[self.i1].tavernier)
self.i1 = self.i1 + 1
AppDelegate.guides.append(newstruct)
}
print(AppDelegate.guides[0])
print(AppDelegate.guides[1])
print(AppDelegate.Games.count)
print(AppDelegate.guides[0].Text)
print(AppDelegate.guides[1].Text)
}catch {
}
}
}
}
I can print:
print(AppDelegate.guides[0])
And print this:
print(AppDelegate.guides[0].Text)
But when I want to print this:
print(AppDelegate.guides[1])
print(AppDelegate.guides[1].Text)
There is error:
Thread 1: Fatal error: Index out of range
There are several issues in your code.
The guidesarr class is unnecessary. Just use your Guide struct.
Use proper naming conventions. Class, struct, and enum names should start with uppercase letters. Property, function, and case names should start with lowercase letters.
Don't force-unwrap data. Safely check it and do proper error checking.
Your main issue is that the two chunks of data you seem to actually want are the two Guide instances inside the one (not two) GuideStruct instances.
I would redo your code something like this:
class GameViewController: UIViewController,UITextFieldDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let headers : HTTPHeaders = ["Content-Type":"application/json","OAtcp":"0!QSJ5SDG8Q39PPM$DXP5HD1E10"]
Alamofire.request("http://192.168.1.100:3535/DarkDiamonds/Api/GetActiveGames", method: .post, headers: headers).responseJSON { (newresponse) in
if let data = newresponse.data {
do {
let decoder = JSONDecoder()
let guides = try decoder.decode([GuideStruct].self, from: data)
for guideStruct in guides {
AppDelegate.guides.append(contentsOf: guideStruct.guides)
}
print(AppDelegate.guides[0])
print(AppDelegate.guides[1])
print(AppDelegate.Games.count)
print(AppDelegate.guides[0].Text)
print(AppDelegate.guides[1].Text)
}catch {
// Bad JSON
}
} else {
// No data
}
}
}
}
And change:
internal static var guides = [guidesarr]()
to:
internal static var guides = [Guide]()
And delete the guidearr class.
I want to parse JSON with struct and name it.
here is the JSON data:
{
"sgList": [
{
"ID": 11113,
"Name": "soss",
"Price": "10.0000",
"BigImagesUrl": "http://192.165.1.19:886/img/1/2015/7/11/20157111429315728.png",
"SmallImagesUrl": "http://192.165.1.19:886/img/1/2015/7/11/20157111429315728.png"
},
{
"ID": 11958,
"Name": "1017p-02",
"Price": "0.0000",
"BigImagesUrl": "http://192.165.1.13:886/img/rar-upload/f82f22ce-4a33-4ba2-a31d-4bae473f5d48/pics/797_1.jpg",
"SmallImagesUrl": "http://192.165.1.13:886/img/rar-upload/f82f22ce-4a33-4ba2-a31d-4bae473f5d48/pics/797_1-[135-135].jpg"
}
]
}
I spend hours on it and get nothing!
Please help me, Thank you very much!
If you don't want an third party library and do it yourself, it's pretty easy.
Assuming that your JSON String is in a variable called jsonString
let data = jsonString.dataUsingEncoding(NSUTF8StringEncoding)!
let json = try! NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments)
Then you can access your data via subsript. For example if you want the Name of the second object in sgList
json["sgList"][1]["Name"]
You can do something like this using SwiftyJSON:
import SwiftyJSON
struct SqList {
let sqList: Array<SqElement>
init(json: JSON) {
let sqArray = json["sqList"].arrayValue.flatMap { SqElement(json: $0) }
self.sqList = sqArray
}
}
struct SqElement {
let id: String
let name: String
let price: String
let bigImagesUrl: String
let smallImagesUrl: String
init?(json: JSON) {
guard
let id = json["ID"].string,
let name = json["Name"].string,
let price = json["Price"].string,
let bigImagesUrl = json["BigImagesUrl"].string,
let smallImagesUrl = json["SmallImagesUrl"].string
else { return nil }
self.id = id
self.name = name
self.price = price
self.bigImagesUrl = bigImagesUrl
self.smallImagesUrl = smallImagesUrl
}
}
In code you just call:
let sqList = SqList(json: JSON(data: dataWithJSON))