How to get values from JSON using Alamofire and Swift 4.0? - json

{
"uri" : "http://www.edamam.com/ontologies/edamam.owl#recipe_f9e656dd9d2b4db9816340687d01722c",
"calories" : 38,
"totalWeight" : 113.25,
"dietLabels" : [ "LOW_FAT" ],
"healthLabels" : [ "SUGAR_CONSCIOUS", "VEGAN", "VEGETARIAN", "PEANUT_FREE", "TREE_NUT_FREE", "ALCOHOL_FREE" ],
"cautions" : [ ],
"totalNutrients" : {
"ENERC_KCAL" : {
"label" : "Energy",
"quantity" : 38.505,
"unit" : "kcal"
},
"FAT" : {
"label" : "Fat",
"quantity" : 0.41902500000000004,
"unit" : "g"
},
"FASAT" : {
"label" : "Saturated",
"quantity" : 0.044167500000000005,
"unit" : "g"
},
"FAMS" : {
"label" : "Monounsaturated",
"quantity" : 0.0124575,
"unit" : "g"
},
"FAPU" : {
"label" : "Polyunsaturated",
"quantity" : 0.043035000000000004,
"unit" : "g"
}
}
}
/*
* networking method
*/
func getNutritionData(url: String) {
Alamofire.request(url, method: .get)
.responseString { response in
if response.result.isSuccess {
print("Sucess! Got the Nutrition data")
let nutritionJSON : JSON = JSON(response.result.value!)
//print(nutritionJSON)
self.updateNutritionData(json: nutritionJSON)
} else {
print("Error: \(String(describing: response.result.error))")
self.caloriesLable.text = "Connection Issues"
}
}
}
func updateNutritionData(json: JSON) {
let calories = json["calories"].intValue
print(calories)
}
^ When I try to get the calories for example, I get nil
In the nutritionData method I tried using .responseJSON but it was throwing an error so I switched to .responseString. I want to get the "totalNutrients" information from that JSON. Help would be appreciated

It's better to use method with responseJSON instead of responseString
Alamofire.request(url, method: .get, parameters:nil, encoding: JSONEncoding.default).responseJSON { response in
print(response)
if let json = response.result.value as? [String:Any] {
print(json["calories"])
}
}
and then try

Ary alamofire.response, and then directly parse data in to JSON
Alamofire.request(url, method: .get).response { response in
if response.result.isSuccess {
let nutritionJSON : JSON = JSON(response.data)
self.updateNutritionData(json: nutritionJSON)
} else {
print("Error: \(String(describing: response.result.error))")
self.caloriesLable.text = "Connection Issues"
}
}
func updateNutritionData(json: JSON) {
let calories = json["calories"].intValue
print(calories)
}

You should definitely be using responseJSON() if you just wanna serialize JSON. If you're trying to use it with a Decodable object, you should be using responseData() and decoding one of your custom types with the data. Always validate.
func getNutritionData(url: String) {
Alamofire.request(url)
.validate(statusCode: 200...200)
.validate(contentType: ["application/json"])
.responseJSON { (response) in
switch response.result {
case .success(let json):
// Do something with the json.
let dict = json as? [String: Any]
print(dict["<KEY>"]!)
case .failure(let error):
print(error)
}
}
}

Related

struct for parsing google distance json in swif

This is the json output from https://maps.googleapis.com/maps/api/distancematrix/json?origins=rijssen&destinations=almelo&mode=driving&language=en&key=
{ "destination_addresses" : [ "Almelo, Netherlands" ],
"origin_addresses" : [ "Rijssen, Netherlands" ],
"rows" : [
{
"elements" : [
{
"distance" : {
"text" : "14.1 km",
"value" : 14090
},
"duration" : {
"text" : "21 mins",
"value" : 1267
},
"status" : "OK"
}
]
}
],
"status" : "OK"
}
this is the struct in Swift
struct Model: Codable{
let destination_addresses : [String]
let origin_addresses : [String]
let rows : [Elements]
let status : String
}
struct Elements: Codable {
let elements:[Distance]
}
struct Distance: Codable{
let distance:Value
let duration:Value
let status:String
}
struct Value: Codable{
let text:String
let value:Int
}
This is the api request
guard let url = URL(string: api) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
do {
if let data = data {
let result = try JSONDecoder().decode([Model].self, from: data)
error in compiler
The data couldn’t be read because it isn’t in the correct format.
Why is the code not working?
Thank you i.a.

Swift Json KeyNotFound

Please help make this work I've been trying to figure out JSON and Swift for a week and facing this problem for 5 hours so far today.
Error Received
ERROR WHEN DECODING JSON keyNotFound(CodingKeys(stringValue: "MP", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "MP", intValue: nil) ("MP").", underlyingError: nil))
YEAR IS NIL
Code
struct UserDay: Codable {
let MP: UserMP
let WP: UserWP
}
struct UserMP: Codable {
let M: [UserM]
let S: [UserS]
}
struct UserM : Codable {
let title: String
let description: String
let time: String
}
struct UserS : Codable {
let title: String
let description: String
let time: String
}
struct UserWP: Codable {
let WP: [WPData]
}
struct WPData: Codable {
let title: String
let values: [Int]
}
class LogDataHandler {
public func grabJSONInfo(){
guard let jsonURL = Bundle(for: type(of: self)).path(forResource: "newLogData", ofType: "json") else { return }
guard let jsonString = try? String(contentsOf: URL(fileURLWithPath: jsonURL), encoding: String.Encoding.utf8) else { return }
// print(jsonString)
// Print Info for TESTING
var year: UserDay?
do {
year = try JSONDecoder().decode(UserDay.self, from: Data(jsonString.utf8))
} catch {
print("ERROR WHEN DECODING JSON \(error)")
}
guard let results = year else {
print("YEAR IS NIL")
return
}
print(results)
}
}
JSON
{
"01/01/2020": {
"MP" : {
"M" : [
{"title" : "m1", "description" : "1", "time" : "12:30pm"},
{"title" : "m2", "description" : "2", "time" : "1:30pm"},
{"title" : "m3", "description" : "3", "time" : "2:30pm"}
],
"S" : [
{"title" : "s1", "description" : "1", "time" : "1pm"}
]
},
"WP" : [
{ "title" : "abc", "values" : [12, 10, 6]},
{ "title" : "def", "values" : [8]}
]
},
"01/29/2020": {
"MP" : {
"M" : [{"title" : "m1", "description" : "1", "time" : "12:30pm"}],
"S" : [{"title" : "s1", "description" : "1", "time" : "12:30pm"}]
},
"WP" :[{ "title" : "def", "values" : [8]}]
}
}
First, replace let WP: UserWP with an array:
struct UserDay: Codable {
let MP: UserMP
let WP: [WPData]
}
Then decode [String:UserDay] instead of UserDay:
try JSONDecoder().decode([String:UserDay].self, from: Data(jsonString.utf8))
Note that this will return a dict with key-value pairs "01/01/2020":UserDay object.
This means you can't assign a dictionary [String: UserDay] to a UserDay variable. Instead you can access the dict by a key which is some date (eg. "01/01/2020") and then assign it to your UserDay variable:
let result = try JSONDecoder().decode([String:UserDay].self, from: Data(jsonString.utf8))
let someYear = result["01/01/2020"]
Note that someYear will be optional so you may want to provide a default value or force-unwrap it.

How to check if value is in dictionary?

I'm using Alamofire and SwiftyJSON and I want to check if the response contains a value that I will type in a search bar.
I've just got the whole JSON file of 1666 objects and append it into my objects array and then I'm searching for value, but it takes too long.
func parseJSON(json: JSON, parse: ParseType) {
var i = 0
var j = 0
switch parse {
case .group:
for elements in json["groups"] {
if let groupId = json["groups"][i]["id"].int {
let groupName = json["groups"][i]["name"].string
// print(groupId)
let group = Groups(id: groupId, name: groupName!)
groupsArray.append(group)
i += 1
} else {
print("Error can't parse JSON")
}
}
func getGroupsData(url: String, groupName: String) {
Alamofire.request(url, method: .get).responseJSON { (response) in
if response.result.isSuccess {
print("Is Success")
let json = JSON(response.result.value)
self.parseJSON(json: json, parse: .group)
if let group = self.groupsArray.first(where: {$0.name == groupName}) {
print("found \(group)")
let searchScheduleUrl = url + "\(group.id)"
self.getGroupSchedule(url: searchScheduleUrl)
} else {
print("Can't find group")
}
} else {
print(response.result.error)
}
}
}
And here is JSON:
{
"groups" : [
{
"faculty" : {
"id" : 101,
"abbr" : "ИнГО",
"name" : "Гуманитарный институт"
},
"id" : 26262,
"spec" : "47.06.01 Философия, этика и религиоведение",
"kind" : 3,
"level" : 3,
"name" : "33865\/4702",
"type" : "common"
},
{
"faculty" : {
"id" : 95,
"abbr" : "ИКНТ",
"name" : "Институт компьютерных наук и технологий"
},
"id" : 27432,
"spec" : "09.03.04 Программная инженерия",
"kind" : 0,
"level" : 1,
"name" : "в13534\/22",
"type" : "evening"
},
{
"faculty" : {
"id" : 92,
"abbr" : "ИСИ",
"name" : "Инженерно-строительный институт"
},
"id" : 26322,
"spec" : "08.06.01 Техника и технологии строительства",
"kind" : 3,
"level" : 1,
"name" : "13163\/0801",
"type" : "common"
}, and so on...
I want to check for example:
if name: "13541/1" is in dictionary and if it is i want to get it's id
You can try
struct Root: Codable {
let groups: [Group]
}
struct Group: Codable {
let faculty: Faculty
let id: Int
let spec: String
let kind, level: Int
let name, type: String
}
struct Faculty: Codable {
let id: Int
let abbr, name: String
}
do {
let res = try JSONDecoder().decode(Root.self,from:data)
if let item = res.groups.first(where:{ $0.name == YourName }) {
print(item.id)
}
}
catch {
print(error)
}

Swift Codable with nested json as string

The services I use for backend calls returns all this json-structure:
{
"status" : "OK",
"payload" : **something**
}
Where something can be a simple string:
{
"status" : "OK",
"payload" : "nothing changed"
}
or a nested json (any json with any properties), for example:
{
"status" : "OK",
"payload" : {
"someInt" : 2,
"someString" : "hi",
...
}
}
This is my struct:
struct GenericResponseModel: Codable {
let status:String?
let payload:String?
}
I want to decode "payload" always as a string. So in the second case I want that the payload property of my "GenericResponseModel" contains the json string of that field, but If I try to decode that response I get the error:
Type 'String' mismatch: Expected to decode String but found a dictionary instead
Is possible to archive what I want?
Many thanks
How about this…
Declare a PayloadType protocol…
protocol PayloadType: Decodable { }
and make String, and struct Payload conform to it…
extension String: PayloadType { }
struct Payload: Decodable, PayloadType {
let someInt: Int
let someString: String
}
Then make GenericResponseModel generic…
struct GenericResponseModel<T: PayloadType>: Decodable {
let status: String
let payload: T
enum CodingKeys: CodingKey {
case status, payload
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
status = try container.decode(String.self, forKey: .status)
payload = try container.decode(T.self, forKey: .payload)
}
}
Then you can decode as follows…
let data = """
{
"status" : "OK",
"payload" : "nothing changed"
}
""".data(using: .utf8)!
print(try JSONDecoder().decode(GenericResponseModel<String>.self, from: data))
// GenericResponseModel<String>(status: "OK", payload: "nothing changed")
and
let data2 = """
{
"status" : "OK",
"payload" : {
"someInt" : 2,
"someString" : "hi"
}
}
""".data(using: .utf8)!
print(try JSONDecoder().decode(GenericResponseModel<Payload>.self, from: data2))
// GenericResponseModel<Payload>(status: "OK", payload: Payload(someInt: 2, someString: "hi"))
Of course, this relies on you knowing the payload type in advance. You could get around this by throwing a specific error if payload is the wrong type…
enum GenericResponseModelError: Error {
case wrongPayloadType
}
and then…
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
status = try container.decode(String.self, forKey: .status)
do {
payload = try container.decode(T.self, forKey: .payload)
} catch {
throw GenericResponseModelError.wrongPayloadType
}
}
Then handle this error when you decode…
let data = """
{
"status" : "OK",
"payload" : {
"someInt" : 2,
"someString" : "hi"
}
}
""".data(using: .utf8)!
do {
let response = try JSONDecoder().decode(GenericResponseModel<String>.self, from: data) // Throws
print(response)
} catch let error as GenericResponseModelError where error == .wrongPayloadType {
let response = try JSONDecoder().decode(GenericResponseModel<Payload>.self, from: data2) // Success!
print(response)
}

typeMismatch on decodable Type when the type is correct

When I hit my configuration API with Postman I am given the following json response back. In this response the two apiVersion keys are numbers and not strings.
{
"data": {
"availability": {
"auth": true,
"ab": true,
"cd": true
},
"helloWorldConfiguration": {
"apiKey": "abcefg",
"rootUrl": "https://foo",
"apiVersion": 3
},
"fooBarConfiguration": {
"baseUrl": "https://foo",
"apiVersion": 1,
"privateApiPath": "",
"publicApiPath": "dev",
"tokenPath": ""
}
},
"errors": []
}
When I try to decode it it fails with a typeMismatch error. When I output the contents of the response, I see the following which looks fine to me.
data = {
availability = {
auth = 1;
ab = 1;
cd = 1;
};
helloWorldConfiguration = {
apiVersion = 1;
baseUrl = "https://foo";
privateApiPath = "";
publicApiPath = dev;
tokenPath = "";
};
fooBarConfiguration = {
apiKey = abcefg;
apiVersion = 3;
rootUrl = "https://foo";
};
};
errors = (
);
The error given to me indicates that data.helloWorldConfiguration.apiVersion is of type string instead of int. We can see from the original HTTP response I get from Postman that's not the case.
typeMismatch(Swift.Int, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "data", intValue: nil), CodingKeys(stringValue: "helloWorldConfiguration", intValue: nil), CodingKeys(stringValue: "apiVersion", intValue: nil)], debugDescription: "Expected to decode Int but found a string/data instead.", underlyingError: nil))
21:17:40 ERROR Unable to decode the response data into a model representation.
My model represents those properties as integers so it would appear that it receives the response and considers those numbers to be strings, which they're not.
public struct ServerConfiguration: Decodable {
let availability: AvailabilityConfiguration
let helloWorldConfiguration: HelloWorldConfiguration
let fooBarConfiguration: FooBarConfiguration
init(availability: AvailabilityConfiguration, helloWorldConfiguration: HelloWorldConfiguration, fooBarConfiguration: FloatSinkConfiguration) {
self.availability = availability
self.helloWorldConfiguration = helloWorldConfiguration
self.fooBarConfiguration = fooBarConfiguration
}
}
public struct FooBarConfiguration: Decodable {
let baseUrl: String
let apiVersion: Int
let privateApiPath: String
let publicApiPath: String
let tokenPath: String
init(baseUrl: String, apiVersion: Int, privateApiPath: String, publicApiPath: String, tokenPath: String) {
self.baseUrl = baseUrl
self.apiVersion = apiVersion
self.privateApiPath = privateApiPath
self.publicApiPath = publicApiPath
self.tokenPath = tokenPath
}
}
public struct AvailabilityConfiguration: Decodable {
let auth: Bool
let ab: Bool
let cd: Bool
init(auth: Bool, ab: Bool, cd: Bool) {
self.auth = auth
self.ab = ab
self.cd = cd
}
}
public struct HelloWorldConfiguration: Codable {
let apiKey: String
let rootUrl: String
let apiVersion: Int
init(apiKey: String, rootUrl: String, apiVersion: Int) {
self.apiKey = apiKey
self.rootUrl = rootUrl
self.apiVersion = apiVersion
}
}
As you can see my apiVersion members are both of type integer along with the json response. What am I doing wrong here? I assume what's happening is Swift is considering the numbers in the json string, regardless of how they're actually represented in the json. Is that the case?
Edit to show utf8 string of Alamofire response data
21:44:06 INFO GET: https:foo/configuration
{
"data" : {
"availability" : {
"auth" : true,
"ab" : true,
"cb" : true
},
"helloWorldConfiguration" : {
"apiKey" : "abcd",
"rootUrl" : "https://foo",
"apiVersion" : "3"
},
"fooBarConfiguration" : {
"baseUrl" : "https://foo",
"apiVersion" : "1",
"privateApiPath" : "",
"publicApiPath" : "dev",
"tokenPath" : "auth/token"
}
},
"errors" : []
}
It would seem that despite the API correctly returning apiVersion as a number, Swift is turning it into a string. Am I decoding it incorrectly?
func getRoute<TResponseData: Decodable>(route:String, completion: #escaping (TResponseData) -> Void) throws {
let headers = try! self.getHeaders(contentType: ContentType.json)
let completeUrl: String = self.getUrl(route: route, requestUrl: nil)
logger.info("GET: \(completeUrl)")
Alamofire.request(
completeUrl,
method: .get,
parameters: nil,
encoding: JSONEncoding.default,
headers: headers)
.validate()
.responseJSON { (response) -> Void in
self.logger.info("GET Response: \(String(describing:response.response?.statusCode))")
switch response.result {
case .success(_):
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom(Date.toFooBarDate)
do {
let result = try decoder.decode(TResponseData.self, from: response.data!)
completion(result)
} catch DecodingError.dataCorrupted(let error) {
self.logger.error(error.underlyingError!)
return
} catch {
print(response.result.value!)
print(error)
self.logger.error("Unable to decode the response data into a model representation.")
return
}
}
}
I checked in my playground and it seems that everything is working fine.To find the real issue i think you are required to provide the real url from where you are getting json and can be checked with alamofire
import Foundation
let json = """
{
"data": {
"availability": {
"auth": true,
"ab": true,
"cd": true
},
"helloWorldConfiguration": {
"apiKey": "abcefg",
"rootUrl": "https://foo",
"apiVersion": 3
},
"fooBarConfiguration": {
"baseUrl": "https://foo",
"apiVersion": 1,
"privateApiPath": "",
"publicApiPath": "dev",
"tokenPath": ""
}
},
"errors": []
}
"""
let data = json.data(using: .utf8)
struct Response : Codable {
let data : Data?
let errors : [String]?
}
struct Availability : Codable {
let auth : Bool?
let ab : Bool?
let cd : Bool?
}
struct Data : Codable {
let availability : Availability?
let helloWorldConfiguration : HelloWorldConfiguration?
let fooBarConfiguration : FooBarConfiguration?
}
struct FooBarConfiguration : Codable {
let baseUrl : String?
let apiVersion : Int?
let privateApiPath : String?
let publicApiPath : String?
let tokenPath : String?
}
struct HelloWorldConfiguration : Codable {
let apiKey : String?
let rootUrl : String?
let apiVersion : Int?
}
let decoder = JSONDecoder()
let response = try decoder.decode(Response.self, from: data!)
print(response)
And here is the response
Response(data: Optional(__lldb_expr_11.Data(availability: Optional(__lldb_expr_11.Availability(auth: Optional(true), ab: Optional(true), cd: Optional(true))), helloWorldConfiguration: Optional(__lldb_expr_11.HelloWorldConfiguration(apiKey: Optional("abcefg"), rootUrl: Optional("https://foo"), apiVersion: Optional(3))), fooBarConfiguration: Optional(__lldb_expr_11.FooBarConfiguration(baseUrl: Optional("https://foo"), apiVersion: Optional(1), privateApiPath: Optional(""), publicApiPath: Optional("dev"), tokenPath: Optional(""))))), errors: Optional([]))