I'm trying to take data return from an API in to an Object. However I am getting errors because the array is complex and not of the same type - it will also differ from the API according to the request made.
let json = try JSONSerialization.jsonObject(with: data!) as! Dictionary<String, Dictionary<String, String>>
The array/data it is being given is as such:
[
'heartbeatStatus' => 'ok',
'events' => [
['id' => '1', 'name' => 'Event'],
['id' => '2', 'name' => 'Event 2'],
]
]
Is there any simple way of doing this? The questions I have seen on SO so far are either for older versions of Swift (this is Swift 5) or aren't are simply different.
The errors I get are like this (different with the various attempts I have made)
Could not cast value of type '__NSSingleObjectArrayI' (0x1ed250b30) to
'NSDictionary' (0x1ed251378). 2020-04-13 18:52:23.173759+0100
removed app id [1209:472869] Could not cast value of type
'__NSSingleObjectArrayI' (0x1ed250b30) to 'NSDictionary'
(0x1ed251378).
Try to use Codable. First define a struct to be your model:
struct HeartbeatResponse: Codable { // or whatever
let heartbeatStatus: String
let events: [Event]
struct Event: Codable {
let id: String
let name: String
}
}
Then to decode:
do {
if let data = data {
let heartbeat = JSONDecoder().decode(HeartbeatResponse.self, data)
// do stuff with heartbeat
}
} catch {
print(error)
}
Related
I m mapping data that come from service with using dictionary [String: String]. I collect them dictionary array. For example, if their parent ids are the same, I want to add their values by array value.
["ParentId": "1","Value": "["Giyim","Aksesuar","Ayakkabı"]"]
It is also the reason I don't know parent id sometimes on the left sometimes on the right in photo
Here is my code and its output.
struct Categories {
let parentId: String
let values: [String]
}
for result in results {
if result?.parentCategoryId != "" {
for docId in self.docIds {
if result?.parentCategoryId == docId {
//print(result?.name)
var values = [String]()
values.append(result?.name ?? "")
self.newCat.append(Categories(parentId: docId, values: values))
}
}
}
}
Problem
As far as I understand from the description you want to map some service data structure to a dictionary where key is parentId and value is an array of some items referred to parentId.
I believe your problem comes from a misunderstanding of the concept of dictionary as a data structure.
[String: String] is dictionary where keys and their associated values are of String type. For example:
["firstKey": "firsthValue", "secondKey": "secondValue", ...]
That means you cannot store associated values of String and Array types in the same dictionary, as you already told the compiler you would like to store only strings.
It is also the reason I don't know parent id sometimes on the left sometimes on the right in photo
This is because key-value pairs are stored in the dictionary without order. This is how dictionaries work :) I'd strongly recommend reading some short official materials to get used to them.
New Swift 5.4 version has a new OrderedDictionary data structure, where keys are ordered, but there is absolutely 100500% no reason to use it for your problem*
Possible solutions
In your case i would suggest either use some struct:
struct SomeData {
let parentID: Int
var values: [String]
}
var storage: [SomeData] // alternative to emptyDic
// Filling storage with data from service
for result in inputData {
// search for SomeData with required id, add value
// OR create SomeData if there is no such id in array yet
}
OR [personally this appeals to me more]
Store data in [String: [String]] dictionary, where the key is parentID and the associated value is an array of values.
The algorithm of filling this dictionary is pretty the same:
You add new key-value pair for every new parentID
You append new values for parentIDs that are already in the dictionary.
Using the struct approach, you could do something like this (you'll need to adapt it to your code, but that should be straightforward):
struct Categories {
let parentId: String
var values: [String] //notice this needs to be a var, not a let
}
func addItem(categories : inout [Categories], docId: String, name: String) {
if let index = categories.firstIndex(where: { $0.parentId == docId }) {
categories[index].values.append(name)
} else {
categories.append(Categories(parentId: docId, values: [name]))
}
}
func addValues() {
var categories = [Categories]()
addItem(categories: &categories, docId: "4", name: "Test1")
addItem(categories: &categories, docId: "1", name: "Test")
addItem(categories: &categories, docId: "4", name: "Test2")
addItem(categories: &categories, docId: "4", name: "Test3")
print(categories)
//in your code, it'll look more like:
// addItem(categories: &self.newCat, docId: docId, name: result?.name ?? "")
}
Which yields this:
[
StackOverflowPlayground.Categories(parentId: "4", values: ["Test1", "Test2", "Test3"]),
StackOverflowPlayground.Categories(parentId: "1", values: ["Test"])
]
I still wonder whether you maybe just want a Dictionary that is keyed by the parentId, but not knowing your use case, it's hard to say.
Hi i am new to iOS and I am using Alamofire for network calls. The things were going good and I am facing no trouble in making network calls. But since I have to post my custom object I am having no luck. so here are the things I was doing before
let parameters: Parameters = [
"Phone": phone,
"ApiKey":"x-y-z"
]
this was working fine.
but now I have to post my objects like
let parameters: Parameters = [
"ApiKey": Common.API_KEY,
"cardModel": cardModel,
"clientModel" : clientModel
]
My cardModel and client model are already converted in Json string i am just putting them into dictionary. the converted model looks like these
"cardModel": {
"ExpiryYear": 2018,
"ExpiryMonth": 1,
"CardNumber": "55555",
"CardHolderName": "xyz"
}
so I am putting these serialized models in the dictionary and post this data into request body using Alamofire.
But on server side these Models are null. Any idea how to put custom model in the way I want ? please help
PS I just print out my parameters dictionary and I have examined this output
["ApiKey": "x-y-z",
"\cardModel\": "{
"\ExpiryYear\": 2018,
"\ExpiryMonth\": 1,
"\CardNumber\": "\55555\",
"\CardHolderName\": "\xyz\"
}
]
I put that parameters json printed output in jsonLint and it was wrong format. I just removed the "\" and replaced [] with {} and then it appears to be valid Json
So what I should do now???? please help
Update1:
this is valid json for my endpoint (sending from android)
{
"ApiKey": "XXX-XXX-XXX",
"cardModel": {
"CardHolderName": "Fjj",
"CardNumber": "555",
"CardExpiryMonth": 1,
"CardExpiryYear": 2018
......
}
}
First you need to have a method/computed property that converts your model into a dictionary of type [String: AnyObject] or [String: Any].
Then instead of adding your model into your parameter dictionary add the model dictionary like in below example.
class CardModel: NSObject {
var expiryYear: Int?
var expiryMonth: Int?
var cardNumber: String?
var cardHolderName: String?
var jsonDict: [String: AnyObject] {
var json: [String: AnyObject] = [:]//Initializing an Empty Dictionary
json["expiryYear"] = expiryYear as AnyObject // Inserting expiryYear into Dictionary
json["expiryMonth"] = expiryMonth as AnyObject // Inserting expiryMonth into Dictionary
json["cardNumber"] = cardNumber as AnyObject // Inserting cardNumber into Dictionary
json["cardHolderName"] = cardHolderName as AnyObject // Inserting cardHolderName into Dictionary
return json // returning newly populated dictionary
}
}
func requestToServer(cardModel: CardModel) {
var parameters = [String: AnyObject]()
parameters["ApiKey"] = "dfv12345df234t" as AnyObject
parameters["cardModel"] = cardModel.jsonDict as AnyObject// adding card model dictionary into you paramters dictionary.
//Same logic will be use for your next clientModel
}
I suggest you read up on the Codable protocol, it is a very elegant way to map basic Swift-types to JSON-data. A Playground will help you with the following
import Cocoa
let jsonData = """
{
"ApiKey": "XXX-XXX-XXX",
"cardModel": {
"CardHolderName": "Fjj",
"CardNumber": "555",
"CardExpiryMonth": 1,
"CardExpiryYear": 2018
}
}
""".data(using: .utf8)!
// ... missing here (but added a closing brace for cardModel
struct CardModel: Codable {
let holderName: String
let number: String
let expiryMonth: Int
let expiryYear: Int
enum CodingKeys: String, CodingKey {
case holderName = "CardHolderName"
case number = "CardNumber"
case expiryMonth = "CardExpiryMonth"
case expiryYear = "CardExpiryYear"
}
}
struct ApiCardPayment: Codable {
let apiKey: String
let cardModel: CardModel
enum CodingKeys: String, CodingKey {
case apiKey = "ApiKey"
case cardModel
}
}
do {
let apiPayment = try JSONDecoder().decode(ApiCardPayment.self, from:jsonData)
print(apiPayment)
} catch {
print(error)
}
This is much easier to handle than the [String:AnyObject] casting nightmare you will probably have to interpret otherwise. Besides the error messages of the JSONDecoder have been improving rapidly and it is now rather good at pointing out what is going wrong.
P.S.: Of course there is also JSONEncoder().encode(), but that is just the easy part anyways.
Hello I have a json the returns me some parameters as variables.
It has Parameter1, Parameter2, Parameter3 etc..
I don't know how many parameters will it give me. It's not a list it's just different variables in the json.
Which is the best way to map a json like that? I use Object Mapper
For Example:
First Time the json is
{
"MyObject": {
"Parameter1": "p1",
"Parameter2": "p2",
"Parameter3": "p3",
"Parameter4": "p4"
}
}
And a second time the json is
{
"MyObject": {
"Parameter1": "p1",
"Parameter2": "p2",
"Parameter3": "p3"
}
}
You can try this.
let keyvalue = parentDict.value(forKey: "MyObject") as! NSDictionary
var lastValue = Keyvalue.allValues
var lastKey = Keyvalue.allKeys
for Keyname in Keyvalue.allKeys
{
print("Keyname %#",Keyname)
print("Value %#",Keyvalue.value(forKey:Keyname))
}
the first step to parse any JSON to make it reusable is to create your Model class or struct accordingly.
Create a class called MyObject as same as your json dictionary
Create a let/var property parameters: [String]?. It's optional as API isn't reliable and maybe wont send anything at all.
See the example below how I parse the json object below.
class MyObject {
let parameters: [String]?
// it's failable because maybe json different at runtime & couldn't parse
init?(json: [String:AnyObject]) {
var key = "Parameter"
var parms = [String]()
for i in 0..<json.count {
guard let item = json["\(key)\(i+1)"] as? String else { continue }
params.append(item)
}
self.parameters = params
}
}
Now you can access the parameters array with index.
Well this could be refactored and you can get the idea how you will handle this with that library.
I have recieved a response object (res) in swift from REST API. It is of type. __NSArrayM. It contains a JSON format string which I want to parse.
{ JsonResult = "[ { \"IsAuth\":\"true\" } ]"; }
It is a long JSON String and I have shortened it for simplicity.
To parse a json, the object needs to be of type Dictionary but I can't cast the object of type __NSArrayM into it.
I searched a lot but can't figure out anyway to read this JSON string.
Additional: Whichever object I try to cast the response object. I get the error -
Could not cast value of type '__NSArrayM' (0x107e86c30) to 'NSData' (0x107e86168) or whichever data type I cast into.
Let's do this step by step.
You say you have an object named "res" which is of type __NSArrayM and which contains this thing:
{ JsonResult = "[ { \"IsAuth\":\"true\" } ]"; }
It means that you already have converted the JSON to an object, namely an NSArray.
In this array that we don't see, this thing you're showing us is a dictionary (that we will name "dict") with its value being a String which itself represents another JSON object.
Let's get the value using the key:
if let value = dict["JsonResult"] as? String {
print(value)
}
Now "value" is supposed to be "[ { \"IsAuth\":\"true\" } ]".
This is a String which represents JSON. To decode the JSON, we first have to make the string into data then we can decode:
if let data = value.data(using: .utf8) {
if let content = try? JSONSerialization.jsonObject(with: data, options: []),
let array = content as? [[String: Any]]
{
print(array)
}
}
I'm currently attempting to create a user database, with Firestore in Swift 5.
I have created a signup view controller, and I am following the Firestore documentation (this link), in order to store the user data.
In a constants file I have stored the following code.
struct userMap: Codable {
let firstName: String?
let lastName: String?
let email: String?
let profilePictureURL: String?
let UserIdentification: String?
enum CodingKeys: String, CodingKey {
case firstName
case lastName
case email
case profilePictureURL
case UserIdentification
}
}
And in my signup view controller, I have this code.
let user = Constants.userMap(firstName: firstName,
lastName: lastName,
email: email,
profilePictureURL: metaImageURL,
UserIdentification: docRefID)
do {
try dbRef.document(docRefID).setData(user)
} catch let error {
print("Error writing to Firestore: \(error)")
}
On the line try dbRef.document(docRefID).setData(user) I am getting the error, "Cannot convert value of type 'Constants.userMap' to expected argument type '[String : Any]' "
Something I feel is worth adding. In the firebase documentation, it recommends the below.
try dbRef.document(docRefID).setData(from: user)
But, I tried this, and this too and I was just asked to remove from:.
Thanks all for your help.
You are using an object of the struct userMap instead of using a dictionary. You can fix this by creating a dictionary inside the userMap struct and providing that dictionary as the parameter. Since your struct already conforms to Codable you can create Data from object of userMap using JSONEncoder. Then you can convert the data to a JSON dictionary to use as a parameter. Here the code:
struct userMap: Codable {
//...
var dictionary: [String: Any] {
let data = (try? JSONEncoder().encode(self)) ?? Data()
return (try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any]) ?? [:]
}
}
And for the setData:
try dbRef.document(docRefID).setData(from: user.dictionary)
Note: Main struct names capitalized for consistency So, convert userMap to UserMap.
For reference, another reason why someone may see the following error:
Cannot convert value of type 'x' to expected argument type '[String : Any]'
when working with Firestore's setData command is because they haven't added the line:
import FirebaseFirestoreSwift