convert struct array to json swift - swift

I have an array of struct's that I need to write to a server side code, I can't seem to find any examples of adding a json object with multiple parent keys.
struct Photo {
var imageName = "", thumbFileURL = "", viewCount = 0, likeCount = 0
}
and then I have a couple of photo object that are declared like...
var photo1 = Photo()
photo1.imageName = "ImPhoto1"
photo1.thumbFileURL = "www.SO.com"
photo1.viewCount = 5
photo1.likeCount = 1
var photo2 = Photo()
photo1.imageName = "ImPhoto2"
photo1.thumbFileURL = "www.SO.com"
photo1.viewCount = 10
photo1.likeCount = 2
////// and then same for x amount of Photo() object
And then I have an array
myArray = [photo1,photo2,photo3, ...]
and then I need to write a json that looks something ike this:
myJson object = {
photo1: {
imageName: "ImPhoto1"
thumbFileURL = "www.SO.com"
viewCount: 5
likeCount: 1
},
photo2: {
imageName: "Imphoto2"
....
},
....
}
so my question is, how do I convert myarray -> myJson

You need a custom implementation of Encodable of PhotoCollection, which is a wrapper type for an array of photos:
struct Photo : Codable {
var imageName = "", thumbFileURL = "", viewCount = 0, likeCount = 0
}
struct PhotoCollection: Encodable, ExpressibleByArrayLiteral {
var photos: [Photo]
typealias ArrayLiteralElement = Photo
init(arrayLiteral elements: Photo...) {
photos = elements
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
for (i, photo) in photos.enumerated() {
try container.encode(photo, forKey: CodingKeys(stringValue: "photo\(i + 1)")!)
}
}
struct CodingKeys: CodingKey, ExpressibleByStringLiteral {
var stringValue: String { return key }
init?(stringValue: String) {
key = stringValue
}
var intValue: Int? { return Int(key) }
init?(intValue: Int) {
key = "\(intValue)"
}
init(stringLiteral value: String) {
key = value
}
var key: String
}
}
var photo1 = Photo()
photo1.imageName = "ImPhoto1"
photo1.thumbFileURL = "www.SO.com"
photo1.viewCount = 5
photo1.likeCount = 1
var photo2 = Photo()
photo1.imageName = "ImPhoto2"
photo1.thumbFileURL = "www.SO.com"
photo1.viewCount = 10
photo1.likeCount = 2
let photoCollection: PhotoCollection = [photo1, photo2]
let json = try JSONEncoder().encode(photoCollection)
print(String(data: json, encoding: .utf8)!)
This prints:
{"photo2":{"imageName":"","likeCount":0,"viewCount":0,"thumbFileURL":""},"photo1":{"imageName":"ImPhoto2","likeCount":2,"viewCount":10,"thumbFileURL":"www.SO.com"}}
Formatted:
{
"photo2": {
"imageName": "",
"likeCount": 0,
"viewCount": 0,
"thumbFileURL": ""
},
"photo1": {
"imageName": "ImPhoto2",
"likeCount": 2,
"viewCount": 10,
"thumbFileURL": "www.SO.com"
}
}

struct Photo:Codable {
var imageName = "", thumbFileURL = "", viewCount = 0, likeCount = 0
}
var photo1 = Photo()
photo1.imageName = "ImPhoto1"
photo1.thumbFileURL = "www.SO.com"
photo1.viewCount = 5
photo1.likeCount = 1
var photo2 = Photo()
photo1.imageName = "ImPhoto2"
photo1.thumbFileURL = "www.SO.com"
photo1.viewCount = 10
photo1.likeCount = 2
var myArray = [photo1,photo2]
let tempData = try? JSONEncoder().encode(myArray)
//Create JSON
var Finaldata: Any?
if let data = tempData { Finaldata = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) }
This Will Work

Related

How to get Firebase data as a model in swift?

I am trying to get data from firebase and use it as a model that I created.
Here is my model;
class UserData{
var nickname : String = ""
var onesignal_player_id : String = ""
var step_count : Int = 0
var total_point : Int = 0
var competitions : [String:Competition] = [String:Competition]()
}
class Competition{
var end_date : String = ""
var gift : String = ""
var id: String = ""
var name: String = ""
var users : [String:Int] = [:]
}
and this is my function;
func getFirebaseData() {
ref = Database.database().reference()
ref.child("users").child("HXXNCXf6RRS4WVO12shZ3j15BnG3").observe(.value) { (snapshot) in
if let snapshotValue = snapshot.value as? Dictionary<String,Any> {
//change userData with the snapshotValue
self.userData.nickname = snapshotValue["nickname"] as! String
self.userData.step_count = snapshotValue["step_count"] as! Int
self.userData.total_point = snapshotValue["total_point"] as! Int
self.userData.onesignal_player_id = snapshotValue["onesignal_player_id"] as! String
self.userData.competitions = snapshotValue["competitions"] as! [String:Competition]
//reload UI with userData
print(self.userData.competitions)
} else {
print("An error occured while assigning snapshotValue to userData")
}
}
}
This code gave me error like this;
Could not cast value of type '__NSDictionaryM' (0x1f47ada20) to 'StepCounterApp.Competition' (0x1004c06f0).
2021-01-02 23:05:49.985711+0300 StepCounterApp[32511:3685645] Could not cast value of type '__NSDictionaryM' (0x1f47ada20) to 'StepCounterApp.Competition' (0x1004c06f0).
but when i comment out the line included self.userData.competitions from getFirebaseData function, everything works fine.
What should I do? How can I use firebase data as a model?
Finally here is my firebase data;
The problem is in your data model. Declare your model data like this
class UserData {
var nickname : String = ""
var onesignal_player_id : String = ""
var step_count : Int = 0
var total_point : Int = 0
var competitions : Competition = Competition()
}
class Competition{
var end_date : String = ""
var gift : String = ""
var id: String = ""
var name: String = ""
var users : [String:Int] = [:]
init() {
}
init(with dictionary: [String: Any]) {
self.end_date = dictionary["end_date"] as! String
self.gift = dictionary["gift"] as! String
self.id = dictionary["id"] as! String
self.name = dictionary["name"] as! String
self.users = dictionary["users"] as! [String:Int]
}
}
And inside the getFirebaseData funcation
self.userData.competitions = Competition(with: snapshotValue["competitions"] as! [String: Any])
The problem was in my data model and with the help of Raja Kishan's data model sugestion I fixed the problem.
First I changed the model little bit;
class UserData{
var nickname : String = ""
var onesignal_player_id : String = ""
var step_count : Int = 0
var total_point : Int = 0
var competitions : [String:Competition] = [String:Competition]()
}
class Competition{
var end_date : String = ""
var gift : String = ""
var id: Int = 0
var name: String = ""
var users : [String:Int] = [:]
init() {
}
init(with dictionary: [String: Any]) {
self.end_date = dictionary["end_date"] as! String
self.gift = dictionary["gift"] as! String
self.id = dictionary["id"] as! Int
self.name = dictionary["name"] as! String
self.users = dictionary["users"] as! [String:Int]
}
}
Than I add a childSnapshot to my method so I can work directly the "competitions";
func getFirebaseData() {
ref = Database.database().reference()
ref.child("users").child("HXXNCXf6RRS4WVO12shZ3j15BnG3").observe(.value) { [self] (snapshot) in
if let snapshotValue = snapshot.value as? [String:Any] {
//change userData with the snapshotValue
self.userData.nickname = snapshotValue["nickname"] as! String
self.userData.step_count = snapshotValue["step_count"] as! Int
self.userData.total_point = snapshotValue["total_point"] as! Int
self.userData.onesignal_player_id = snapshotValue["onesignal_player_id"] as! String
//******
//This part of the coded added for to solve the problem starting from here
let childSnap = snapshot.childSnapshot(forPath: "competitions")
if let childSnapValue = childSnap.value as? [String:Any] {
childSnapValue.forEach { (element) in
self.userData.competitions.updateValue(Competition(with: element.value as! [String:Any]), forKey: element.key)
}
} else {
print("something wrong with the childSnap")
}
//to here
//******
} else {
print("An error occured while assigning snapshotValue to userData")
}
}
}

How to save nested data to the Firestore?

I'm new here. An error occurs while writing the nested data to the Firestore. This is my data structure:
struct CartArray: Codable {
var num:Int
var name:String
var price:Double
init (num: Int,name: String,price: Double)
{ self.num = num
self.name = name
self.price = price
}
this is the data recording function:
let db = Firestore.firestore()
var arrayCart: [CartArray] = []
#IBAction func buttonCheckout(_ sender: Any) {
saveData()
}
...
func saveData () {
let menuItems = [arrayCart]
var list_menuItem = [Any]()
for item in menuItems {
do {
let jsonData = try JSONEncoder().encode(item)
let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: [])
list_menuItem.append(jsonObject)
}
catch {
// handle error
}
}
print(list_menuItem)
let parameters = [
"address": userAddress as Any,
"datetime": Timestamp(date: Date()),
"status": "Заказано",
"user_phone": userPhone as Any,
"username": userName as Any,
"total":cart.total,
"order":list_menuItem
]
db.collection("orders").document().setData(parameters)
{ err in
if let e = err {
print("$-- error save data \(e)")
} else {
print("success!")
}
}
}
this is the converting json of array:
[<__NSArrayI 0x600001f97880>( { name = "\U041f\U0438\U0440\U043e\U0436\U043a\U0438 \U0441 \U043c\U044f\U0441\U043e\U043c"; num = 3; price = 40; },
{ name = "\U041f\U0438\U0446\U0446\U0430 \U0421\U0442\U0430\U043d\U0434\U0430\U0440\U0442"; num = 1; price = 500; } ) ]
When saving occurs in the 'order' field, an 'Nested arrays are not supported' error occurs. Why?
The problem is that in the saveData() you are creating an array of arrays of CartArray.
Replace: let menuItems = [arrayCart]
with: let menuItems = arrayCart

How to merge values with similar keys for NSdictionary

Hello i have situation where i have getting a raw response for the server like this
(
{
"Nov 2018" = {
AudioFileURL = "https://www.GeorgeA.m4a";
AudioFileText = "GeorgeA";
};
},
{
"Nov 2018" = {
AudioFileURL = "https://www.GeorgeB.m4a";
AudioFileText = "Georgeb";
};
},
{
"Nov 2018" = {
AudioFileURL = "https://www.Georgec.m4a";
AudioFileText = "Georgec";
};
},
{
"Sep 2018" = {
AudioFileURL = "https://www.GeorgeB.m4a";
AudioFileText = "GeorgeD";
};
}
)
now i would like to combine all the Values with the same key so that i can use them in section with UITableView controller . can someone please provide me some guidance for that .
The output i am looking for is something like this
(
{
"Nov 2018" = {
[AudioFileURL = "https://www.GeorgeA.m4a";
AudioFileText = "GeorgeA"],
[AudioFileURL = "https://www.GeorgeB.m4a";
AudioFileText = "GeorgeB"],
[AudioFileURL = "https://www.GeorgeC.m4a";
AudioFileText = "GeorgeC"];
};
},
{
"Sep 2018" = {
[AudioFileURL = "https://www.GeorgeB.m4a";
AudioFileText = "GeorgeD";]
};
}
)
First make a struct(s) that represents the info you want in one cell:
struct AudioFileInfo {
let urlString: String
let text: String
init(dict: [String: String]) {
urlString = dict["AudioFileURL"] ?? ""
text = dict["AudioFileText"] ?? ""
}
}
struct CellInfo {
let date: String
let audioFileInfos: [AudioFileInfo]
}
Then you can:
let cellInfos = response
.flatMap {
$0.map { ($0.key, AudioFileInfo(dict: $0.value)) }
}
.reduce([CellInfo]()) { partial, item in
var new = partial
if let index = partial.index(where: { $0.date == item.0 }) {
new[index] = CellInfo(date: partial[index].date, audioFileInfos: partial[index].audioFileInfos + [item.1])
}
else {
new.append(CellInfo(date: item.0, audioFileInfos: [item.1]))
}
return new
}
This will give you an array of CellInfo objects.
As a general rule, this how you solve this sort of problem. First define a type that represents the output you want, then manipulate the input until you create objects of that type. Playground is your friend for this sort of thing.
Something like this will work for you
typealias AudioFile = (url: String, text: String)
class DictArray {
var items: [MainModal]
class MainModal {
let date: String
var files: [AudioFile]
init(key: String, file: AudioFile) {
self.key = key
self.files = [file]
}
}
init(dictArray: [[String: [String: String]]]) {
for dict in dictArray {
if let date = dict.keys.first {
if let item = items.first(where: { $0.date == date }) {
guard let value = dict.values.first as? [String: String] else { continue }
let file = AudioFile(url: value["AudioFileURL"]!, text: value["AudioFileText"]!)
item.files.append(file)
} else {
guard let value = dict.values.first as? [String: String] else { continue }
let file = AudioFile(url: value["AudioFileURL"]!, text: value["AudioFileText"]!)
items.append(MainModal(key: date, files: [file]))
}
}
}
}
}
DictArray(dict: json)

Typecasting causing struct values to change (Swift)

After downcasting an array of structs, my Variables View window shows that all of the values in my struct have shifted "down" (will explain in a second). But when I print(structName), the values are fine. However, when I run an equality check on the struct, it once again behaves as though my values have shifted.
For example, I am trying to downcast Model A to ModelProtocol. var m = Model A and has the values {id: "1234", name: "Cal"}. When I downcast, m now has the values { id:"\0\0", name:"1234" }.
Actual Example Below:
Models that I want to downcast:
struct PrivateSchoolModel: Decodable, SchoolProtocol {
var id: String
var name: String
var city: String
var state: String
}
struct PublicSchoolModel: Decodable, SchoolProtocol {
var id: String
var name: String
var city: String
var state: String
var latitude: String
var longitude: String
}
Protocol I want to downcast to:
protocol SchoolProtocol {
var id: String { get set }
var name: String { get set }
var city: String { get set }
var state: String { get set }
var longitude: Float { get set }
var latitude: Float { get set }
}
extension SchoolProtocol {
var longitude: Float {
get { return -1.0 }
set {}
}
var latitude: Float {
get { return -1.0 }
set {}
}
}
Downcasting:
guard let downcastedArr = privateSchoolArray as? [SchoolProtocol] else { return [] }
Result (item at index 0) or originalArr:
id = "1234"
name = "Leo High School"
city = "Bellview"
state = "WA"
Result (item at index 0) of downcastedArr:
id = "\0\0"
name = "1234"
city = "Leo High School"
state = "Bellview"
But if I print(downcastArr[0]), it will show:
id = "1234"
name = "Leo High School"
city = "Bellview"
state = "WA"
But if I try originalArray[0].id == downcastArr[0].id, it returns false
My Code with the problem:
class SchoolJSONHandler {
private enum JSONFile: String {
case publicSchool = "publicSchool"
case privateSchool = "privateSchool"
}
private lazy var privateSchoolArray = getPrivateSchools()
private lazy var publicSchoolArray = getPublicSchools()
func getSchoolArray(sorted: Bool, filtered: Bool, by stateAbbreviation: String?) -> [SchoolProtocol] {
var schools = combineArrays()
if sorted {
schools.sort(by: { $0.name < $1.name })
}
if filtered {
guard let abbr = stateAbbreviation else { return [] }
schools = schools.filter {
return $0.state == abbr
}
}
return schools
}
private func combineArrays() -> [SchoolProtocol] {
// EVERYTHING IS FINE IN NEXT LINE
let a = privateSchoolArray
// PROBLEM OCCURS IN NEXT 2 LINES WHEN DOWNCASTING
let b = privateSchoolArray as [SchoolProtocol]
let c = publicSchoolArray as [SchoolProtocol]
return b + c
}
private func getPublicSchools() -> [PublicSchoolModel] {
guard let jsonData = getJSONData(from: .publicSchool) else { return [] }
guard let schools = decode(jsonData: jsonData, using: [PublicSchoolModel].self) else { return [] }
return schools
}
private func getPrivateSchools() -> [PrivateSchoolModel] {
guard let jsonData = getJSONData(from: .privateSchool) else { return [] }
guard let schools = decode(jsonData: jsonData, using: [PrivateSchoolModel].self) else { return [] }
return schools
}
private func getJSONData(from resource: JSONFile) -> Data? {
let url = Bundle.main.url(forResource: resource.rawValue, withExtension: "json")!
do {
let jsonData = try Data(contentsOf: url)
return jsonData
}
catch {
print(error)
}
return nil
}
private func decode<M: Decodable>(jsonData: Data, using modelType: M.Type) -> M? {
do {
//here dataResponse received from a network request
let decoder = JSONDecoder()
let model = try decoder.decode(modelType, from:
jsonData) //Decode JSON Response Data
return model
} catch let parsingError {
print("Error", parsingError)
}
return nil
}
}
And then it is just called in another class by schoolJSONHandler.getSchoolArray(sorted: true, filtered: true, by: "WA")

Type [SubscriptType] does not conform to protocol StringLiteralConvertible error

Type [SubscriptType] does not conform to protocol StringLiteralConvertible
// JsonRequest.swift
class JsonRequest {
var title: String?
var postBody: String?
var coverImage: String?
init(json: NSDictionary){
self.title = json["title"] as? String
self.postBody = json["body"] as? String
self.coverImage = json["img_url"] as? String
}
}
// ViewController.swift file
var posts = [JsonRequest]()
let feedUrl = NSURL(string: "http://example.com/json")
// 2
if let JSONData = NSData(contentsOfURL: feedUrl!) {
// 3
var jsonResult = NSJSONSerialization.JSONObjectWithData(JSONData, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
var myJSON = JSON(jsonResult)
let arrayLength = myJSON["dump"].array?.count
if arrayLength != 0 {
for postIndex in 0...arrayLength! - 1 {
var postArray = myJSON["dump"][postIndex]["title"] as? [NSDictionary]
for item in postArray {
posts.append(JsonRequest(json: item))
}
}
}
}
I want to append from my JSON["dump"][0, 1, 2]["title"] to postArray array, save all this titles in the this array, but here is the this error. How can I fix it and save my titles in this Array?
You can not cast postArray as [NSDictionary] because it is not NSDictionary.
But It is string and here is example code for you.
var posts = [String]()
let feedUrl = NSURL(string: "http://example.com/en/feed")
// 2
if let JSONData = NSData(contentsOfURL: feedUrl!) {
// 3
var jsonResult = NSJSONSerialization.JSONObjectWithData(JSONData, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
var myJSON = JSON(data:JSONData)
let arrayLength = myJSON["dump"].array?.count
if arrayLength != 0 {
for postIndex in 0...arrayLength! - 1 {
var post = myJSON["dump"][postIndex]["title"].stringValue
posts.append(post)
println(posts)
}
}
}
EDIT
Update your code this way:
JsonRequest.swift
class JsonRequest {
var title: String?
var postBody: String?
var coverImage: String?
init(json: JSON){
self.title = json["title"].stringValue
self.postBody = json["body"].stringValue
self.coverImage = json["img_url"].stringValue
}
}
ViewController.swift
var posts = [JsonRequest]()
let feedUrl = NSURL(string: "http://example.com/en/feed")
// 2
if let JSONData = NSData(contentsOfURL: feedUrl!) {
// 3
var jsonResult = NSJSONSerialization.JSONObjectWithData(JSONData, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
var myJSON = JSON(data:JSONData)
let arrayLength = myJSON["dump"].array?.count
var dict = myJSON["dump"]
if arrayLength != 0 {
for postIndex in 0...arrayLength! - 1 {
var tempDict = dict[postIndex]
posts.append(JsonRequest(json: tempDict))
}
}
}
Hope it helps.