Parsing JSON Swift 3 - json

I am running iOS 9.0, Swift 3 on Xcode8 and implementing Facebook into my app, the result object returns successfully as:
Optional({
email = "usersEmail#yahoo.com";
"first_name" = name;
id = 1015396591;
"last_name" = lastName;
picture = {
data = {
"is_silhouette" = 0;
url = "https://scontent.xx.fbcdn.net/v/t1.0-1/s200x200/1915003_414206574090_5019436_n.jpg?oh=bf120460b36b26c648185d6777a&oe=5892EB78";
};
};
})
However, attempting to access the elements as: result["email"] as! String, notes the error Type Any? has no Subscript Members
How can I allow the element to be read?

Use this to get email
let result = result as! Dictionary<String,Any>
let email = result["email"] as! String

Related

ObjectMapper returrning NIL in Swift

I'm trying to use Object mapper https://github.com/Hearst-DD/ObjectMapper to convert a JSON string into a Swift object. Note I've simplified the object to a single field here - obviously my real response has more fields!
My response is:
data arrray response [{
chat = {
"_id" = 1;
}}]
So I want to convert to my chat class:
public class Chat: Mappable {
var _id: String? }
public required init?(map: Map){
}
public func mapping(map: Map) {
_id <- map["_id"]
}
}
So I convert my data array to a dictionary
let jsonResponse = dataArray [0]
let discResponse = jsonResponse as! Dictionary<String,AnyObject>
I can even access my field manually
let chat = discResponse["chat"]
let id = chat!["_id"]
print ("CHAT ID", id)
But mapping to the object
let jsonData = try! JSONSerialization.data(withJSONObject: chat, options: .prettyPrinted)
let user = Chat(JSONString: String( describing: jsonData))
returns nil
Why?
Just putting my comment as an answer, if someone will stuck on the same issue: use Mapper<Chat>().map(JSONObject: chat). It should help your cause.

Swift Siesta edit fetched entity

I'm building an API client using Siesta and Swift 3 on Xcode 8. I want to be able to fetch an entity using a Siesta resource, then update some of the data and do a patch to the API.
The issue is that having an entity, if I save the JSON arrays in my entity fields I can't send them back to the server, I get the following error:
▿ Siesta.RequestError
- userMessage: "Cannot send request"
- httpStatusCode: nil
- entity: nil
▿ cause: Optional(Siesta.RequestError.Cause.InvalidJSONObject())
- some: Siesta.RequestError.Cause.InvalidJSONObject
- timestamp: 502652734.40489101
My entity is:
import SwiftyJSON
import Foundation
struct Order {
let id: String?
let sessionId: String?
let userId: Int?
let status: String?
let comment: String?
let price: Float?
let products: Array<JSON>?
init(json: JSON) throws {
id = json["id"].string
sessionId = json["sessionId"].string
userId = json["userId"].int
status = json["status"].string
comment = json["comment"].string
price = json["price"].float
products = json["products"].arrayValue
}
/**
* Helper method to return data as a Dictionary to be able to modify it and do a patch
**/
public func toDictionary() -> Dictionary<String, Any> {
var dictionary: [String:Any] = [
"id": id ?? "",
"sessionId": sessionId ?? "",
"userId": userId ?? 0,
"status": status ?? "",
"comment": comment ?? ""
]
dictionary["products"] = products ?? []
return dictionary
}
}
What I'm doing is:
MyAPI.sessionOrders(sessionId: sessionId).request(.post, json: ["products": [["product": productId, "amount": 2]], "comment": "get Swifty"]).onSuccess() { response in
let createdObject : Order? = response.typedContent()
expect(createdObject?.sessionId).to(equal(sessionId))
expect(createdObject?.comment).to(equal("get Swifty"))
expect(createdObject?.products).to(haveCount(1))
expect(createdObject?.price).to(equal(product.price! * 2))
if let createdId = createdObject?.id {
var data = createdObject?.toDictionary()
data?["comment"] = "edited Swifty" // can set paid because the user is the business owner
MyAPI.order(id: createdId).request(.patch, json: data!).onSuccess() { response in
result = true
}.onFailure() { response in
dump(response) //error is here
}
}
}
Resources:
func sessionOrders( sessionId: String ) -> Resource {
return self
.resource("/sessions")
.child(sessionId)
.child("orders")
}
func order( id: String ) -> Resource {
return self
.resource("/orders")
.child(id)
}
Transformers:
self.configureTransformer("/sessions/*/orders", requestMethods: [.post, .put]) {
try Order(json: ($0.content as JSON)["data"])
}
self.configureTransformer("/orders/*") {
try Order(json: ($0.content as JSON)["data"])
}
I've managed to circle this by creating dictionary structures like:
let products: Array<Dictionary<String, Any>>?
products = json["products"].arrayValue.map({
["product": $0.dictionaryValue["product"]!.stringValue, "amount": $0.dictionaryValue["amount"]!.intValue]
})
But I live in a hell of downcasts if I need to modify anything:
var data = createdObject?.toDictionary()
data?["comment"] = "edited Swifty"
//if I want to modify the products...
var products = data?["products"] as! Array<Dictionary<String, Any>>
products[0]["amount"] = 4
data?["products"] = products
How can I send those original JSON arrays with Siesta? They're really easy to modify and read! I've browsed the siesta docs and github issues with no success...
Your problem is a mismatch between SwiftyJSON and Foundation’s JSONSerialization; Siesta just happens to be in the middle of it.
InvalidJSONObject is Siesta telling you that Foundation doesn’t understand the thing you gave it — which would be the value returned by your toDictionary() method. Most of the things in that dictionary look fine: strings, ints, a float. (Careful about using float for money, BTW.)
The culprit is that products array: it’s [JSON], where JSON is a SwiftyJSON type that Foundation doesn’t know what to do with. You should be in the clear if you turn the JSON values back into simple dictionaries:
dictionary["products"] = (products ?? []).map { $0.dictionaryObject }
If that doesn’t do it, or if you need to diagnose a similar error in the future, remove all the values from the offending dictionary and then add them back in one at a time to see which one is tripping up JSONSerialization.

Create new Realm List and add items

I make a API request with Alamofire , I then get a response in JSON format, I then parse the JSON into a NSDictionary to get to the data I want.
The data I get is four Arrays of different items.
I want to the create a new List in Realm to save these items in.
Here are my Realm Object Classes :
class ListOfDefinitions: Object {
let listOfItems = List<Item>()
}
and
class Item: Object {
dynamic var AverageCost = Int()
dynamic var Barcode = ""
dynamic var Description = ""
dynamic var InternalUnique = Int()
dynamic var LastCost = Int()
dynamic var LimitToMainRegionUnique = Int()
dynamic var Notes = ""
dynamic var StockCategoryUnique = Int()
dynamic var StockCode = ""
dynamic var StockGroupUnique = Int()
dynamic var UnitDescriptor = ""
}
Here is my code on how I handle the JSON response and where I want to save the data in my code.
var newItemInStockList : ListOfDefinitions! // declared in the class
let newItemInStock = Item()
.responseJSON { response in
switch response.result {
case .Success(let JSON):
// print("Success with JSON: \(JSON)")
let response = JSON as! NSDictionary
let responseParams = response.objectForKey("ResponseParameters") as! NSDictionary
//print(responseParams)
//let stockItemGroupList = responseParams.objectForKey("StockItemGroupList")
let stockItemList = responseParams.objectForKey("StockItemList") as! NSDictionary
//print(stockItemList)
let listofDefinitions = stockItemList.objectForKey("ListofDefinitions") as! NSArray
print(listofDefinitions.count)
for defJson in listofDefinitions {
print(defJson["Description"])
someString = defJson["Description"] as! String
print(someString)
// Because there are 4 arrays of items this for loop will be red 4 times, each time it is red I want o create a new list and add the items to the list
// This comment area is where I tried to create a new list and then .append the items in it, but it doesn't work.
// let newOne = ListOfDefinitions()
//
//
// try! realm.write{
//
// realm.add(newOne)
// }
// self.newItemInStock.AverageCost = defJson["AverageCost"] as! Int
// self.newItemInStock.Barcode = defJson["Barcode"] as! String
// self.newItemInStock.Description = defJson["Description"] as! String
// self.newItemInStock.InternalUnique = defJson["InternalUnique"] as! Int
// self.newItemInStock.LastCost = defJson["LastCost"] as! Int
// self.newItemInStock.LimitToMainRegionUnique = defJson["LimitToMainRegionUnique"] as! Int
// self.newItemInStock.Notes = defJson["Notes"] as! String
// self.newItemInStock.StockCategoryUnique = defJson["StockCategoryUnique"] as! Int
// self.newItemInStock.StockCode = defJson["StockCode"] as! String
// self.newItemInStock.StockGroupUnique = defJson["StockGroupUnique"] as! Int
// self.newItemInStock.UnitDescriptor = defJson["UnitDescriptor"] as! String
//
// try! realm.write{
//
// self.newItemInStockList.listOfItems.append(self.newItemInStock)
// }
}
case .Failure(let error):
print("Request failed with error: \(error)")
}
And here is what I get when I print the 4 Arrays
Looking at your sample code, I think the main issue happening here is that you're re-using the same self.newItemInStock instance for each object you're adding to the list.
It would be best to create a new Item object in the loop as you're going along and append that to the List object.
I recommend using a combination of AlamofireObjectMapper to handle all your JSON mapping (both ways) https://github.com/tristanhimmelman/AlamofireObjectMapper
and the ListTransform found in ObjectMapper+Realm https://github.com/Jakenberg/ObjectMapper-Realm
They're both available to be installed through cocoapods. Your code will look much cleaner and easier to maintain

Initialize Swift class with AnyObject? from NSJSONSerialization

I am using NSJSONSerialization in Swift 1.2 to parse some json that is returned from an API response.
var err: NSError?
let opts = NSJSONReadingOptions.AllowFragments
let json: AnyObject? = NSJSONSerialization.JSONObjectWithData(jsonData!, options: opts, error: &err)
The parsed json is provided as AnyObject?. I would like to use this optional to initialize a class object which can be used as the model data in an application.
class Alerts {
let type: String
let date: String
let description: String
let expires: String
let message: String
init(json: AnyObject) {
if let
jsonDict = json as? [String: AnyObject],
alertsArray = jsonDict["alerts"] as? [AnyObject],
alertsDict = alertsArray[0] as? [String: AnyObject],
type = alertsDict["type"] as? String,
date = alertsDict["date"] as? String,
description = alertsDict["description"] as? String,
expires = alertsDict["expires"] as? String,
message = alertsDict["message"] as? String
{
self.type = type
self.date = date
self.description = description
self.expires = expires
self.message = message
}
else
{
self.type = "err"
self.date = "err"
self.description = "err"
self.expires = "err"
self.message = "err"
}
}
}
// example of creating a data model from the json
let alerts = Alerts(json: json!)
alerts.type
alerts.date
alerts.description
alerts.expires
alerts.message
Since NSJSONSerialization returns an optional, I have to check for the existence of each value type as I extract the json data. As you can see in the above code, I used the improved optional bindings from Swift 1.2 to clean up the init method. Without using third-party libraries, is there anything else I can do to the class model (enums, structs, type aliases) to make it more readable? Should I use a struct for the model data instead of a class? Would it be possible to create a custom type using an enum or struct to represent a json object?
So without using third party libraries, the if let trees are usually the best practice, which you have shown. To help you later down the road, maybe recreate your object hierarchy in JSON as a Struct model in Swift. Something like:
var json = JSON(JSONData.sharedjson.jsonRaw!)
var mongoIdTest = json["resultset"]["account"]["mongoId"].string
struct Root {
var timestamp: Int?
var resultset = ResultSet()
init() {
self.timestamp = json["timestamp"].int
println(json)
}
}
struct ResultSet {
var alert: String?
var account = Account()
var customer = Customer()
init() {
}
}
struct Account {
var mongoId: String?
init() {
mongoId = json["resultset"]["account"]["mongoId"].string
}
}
struct Locations {
}
struct Customer {
var account: String?
var address: String?
var id: String?
var loginId: String?
var mongoId: String?
var name: String?
var opco = Opco()
init() {
account = json["resultset"]["customer"]["account"].string
address = json["resultset"]["customer"]["address"].string
id = json["resultset"]["customer"]["id"].string
loginId = json["resultset"]["customer"]["loginId"].string
mongoId = json["resultset"]["customer"]["mongoId"].string
name = json["resultset"]["customer"]["name"].string
}
}
struct Opco {
var id: String?
var phone: String?
var cutOffTime: String?
var name: String?
var payerId: String?
init() {
id = json["resultset"]["customer"]["opco"]["id"].string
phone = json["resultset"]["customer"]["opco"]["phone"].string
cutOffTime = json["resultset"]["customer"]["opco"]["cutOffTime"].string
name = json["resultset"]["customer"]["opco"]["name"].string
payerId = json["resultset"]["customer"]["opco"]["payerId"].string
}
}
This way you can still use autocomplete and dot notation to navigate through your hierarchy.
Edit: I have a data structure from an actual project I've worked on added to the answer, hopefully this gives a better idea. Keep in mind that I'm using SwiftyJSON for the JSON() call.
Edit 2:
This is a method I found for getting JSON info into a Swift dictionary without the use of some other library. I'm not sure there is another way to do it that's easier without the use of third party libraries.
var urlToRequest = "https://EXAMPLE.com/api/account.login?username=MY_USERNAME&password=Hunter2"
if let json = NSData(contentsOfURL: NSURL(string: urlToRequest)!) {
// Parse JSON to Dictionary
var error: NSError?
let boardsDictionary = NSJSONSerialization.JSONObjectWithData(json, options: NSJSONReadingOptions.MutableContainers, error: &error) as? Dictionary<String, AnyObject>
fulljson = boardsDictionary
// Display all keys and values
println("Keys in User Data:")
for (key, value) in boardsDictionary! {
println("\(key)-------\(value)")
}
println(fulljson?["resultset"])
}
else {
println("Test JSON nil: No Connection?")
}
That dictionary will be the input for your Structs.

Cannot subscript a value of a type AnyObject, in Swift

I have this line of code, and I want to extract the "title" key:
var title = jParams["title"] as! String
However it wont let me compile, and if I get this error message in red:
Cannot subscript a value of a type AnyObject with an index of type String
When show the contents of jParams in the log with println(jParams) I get the following content:
INCOMING LIST PARAMETERS (jParameters)
Optional({
title = "Example List";
values = (
{
id = 1;
name = "Line 1";
},
{
id = 2;
name = "Line 2";
},
{
id = 3;
name = "Line 3";
}
);
})
I am new to Swift so I am not familiar with the details of handling JSON to deal with these type of problems. What could be wrong?
//jParams comes from a JSON server response
var data = NSURLConnection.sendSynchronousRequest(request, returningResponse: nil, error: nil)
if data != nil {
var jdata = JSON(data: data!)
var jParams=jdata["responseData"]["extraData"]["params"]
In your edit it looks like you're using SwiftyJSON.
If that is indeed the case, you can help the compiler to know what's in the dictionary by using SwiftyJSON's dictionaryValue property:
let jParams = jdata["responseData"]["extraData"]["params"].dictionaryValue
Then you should be able to access your values without downcasting:
let title = jParams["title"]
because SwiftyJSON will have inferred the right type for the values.