here is the string(my whole response is string) which i get after api call .
"{'result': {'ip':'49.36.183.40','id':'T1199','Date':'2022-7-24','Time':'20:58:36','Temp':38.94,'PM25':117.00,'lux':7.00,'VOC':586.00,'CO':0.97,'CO2':828.00,'O3':118.00,'RH':48.88,'Pres':989.00}}",,,
I tried this since I was receiving the string back :
#Published var singleData : [iotData] = []
func loadSingleData() {
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) { [weak self ] data , response, error in
DispatchQueue.main.async {
let decoder = JSONDecoder()
if let data = data {
if let users = try? decoder.decode([String : iotData].self, from: data) {
print(users)
} else {
print("failed to decode the data")
}
} else {
print("Failed to load the data")
}
}
}.resume()
} else {
print("wrong url")
}
}
but my output is failed to decode the data
also there are commas outside json string how do i take care of those .
i am beginner in ios development so i don't have any clue how to decode this json string and use it.how can i get past this
here is my model for this
struct iotData : Codable {
var result : Result
}
struct Result: Codable {
let ip, id, Date, Time: String
let Temp: Double
let PM25, lux, VOC: Int
let CO: Double
let CO2, O3: Int
let RH: Double
let Pres: Int
}
Use:
decoder.decode(iotData.self, from: data) { }
Instead of
decoder.decode(iotData, from: data) { }
Also make sure 'data' is actually 'Data' object
I know there are already some articles regarding this issue, but I could not find anything related to my JSON.
This is how my JSON likes like:
{
"message": {
"affenpinscher": [],
"african": [],
"airedale": [],
"akita": [],
"appenzeller": [],
"australian": [
"shepherd"
],
"basenji": []
},
"status: "succes"
}
So, if I understand it correctly it is dictionary because it starts with {, but what are the things inside the "message"?
This is my Dog.swift class where I am re-writing the JSON, but I am not sure if it is correct:
class Dog: Decodable, Identifiable {
var message: Message?
var status: String?
}
struct Message: Decodable {
var affenpinscher: [String:[String]]?
var african: [String]?
var airedale: [String]?
var akita: [String]?
var appenzeller: [String]?
var australian: [String]?
var basenji: [String]?
}
As you can see in the first value I was trying to play with data types, but no success.
I am decoding and parsing JSON here:
class ContentModel: ObservableObject {
#Published var dogs = Message()
init() {
getDogs()
}
func getDogs(){
// Create URL
let urlString = Constants.apiUrl
let url = URL(string: urlString)
if let url = url {
// Create URL request
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 10)
request.httpMethod = "GET"
// Get URLSession
let session = URLSession.shared
// Create Data Task
let dataTask = session.dataTask(with: request) { (data, response, error) in
// Check that there is not an error
if error == nil {
do {
// Parse JSON
let decoder = JSONDecoder()
let result = try decoder.decode(Dog.self, from: data!)
print(result)
// Assign result to the dogs property
DispatchQueue.main.async {
self.dogs = result.message!
}
} catch {
print(error)
}
}
}
// Start the Data Task
dataTask.resume()
}
}
}
And here I would love to iterate through it eventually, which I also have no idea how to do it:
struct ContentView: View {
#EnvironmentObject var model: ContentModel
var body: some View {
NavigationView {
ScrollView {
LazyVStack {
if model.dogs != nil {
// ForEach(Array(model.dogs.keys), id: \.self) { d in
// Text(d)
// }
}
}
.navigationTitle("All Dogs")
}
}
}
}
What can I try next to resolve this?
First of all don't use classes for a JSON model and to conform to Identifiable you have to add an id property and CodingKeys if there is no key id in the JSON.
My suggestion is to map the unhandy [String: [String]] dictionary to an array of an extra struct
I renamed Dog as Response and named the extra struct Dog
struct Dog {
let name : String
let types : [String]
}
struct Response: Decodable, Identifiable {
private enum CodingKeys: String, CodingKey { case message, status }
let id = UUID()
let dogs: [Dog]
let status: String
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
status = try container.decode(String.self, forKey: .status)
let message = try container.decode([String:[String]].self, forKey: .message)
dogs = message.map(Dog.init).sorted{$0.name < $1.name}
}
}
In the model declare
#Published var dogs = [Dog]()
and decode
let result = try decoder.decode(Response.self, from: data!)
print(result)
// Assign result to the dogs property
DispatchQueue.main.async {
self.dogs = result.dogs
}
The dogs array can be displayed seamlessly in a List
PS: Actually appenzeller is supposed to be
"appenzeller": ["sennenhund"],
or correctly in English
"appenzell": ["mountain dog"],
😉😉😉
Using the native Swift approach that #vadian answered is a much lighter weight solution, but if you work with JSON often I'd recommend using SwiftyJSON.
You can parse the URL data task response into a Swifty json object like so:
import SwiftyJSON
guard let data = data, let json = try? JSON(data: data) else {
return
}
// Make sure the json fetch was successful
if json["status"].stringValue != "success" {
return
}
Then you can access the message object safely without the verbosity of using Decodable. Here the message is parsed into an array of dog structs:
struct Dog {
let name : String
let types : [String]
}
var dogs: [Dog] = []
/// Load the docs into an array
for (name, typesJson) in json["message"].dictionaryValue {
dogs.append(Dog(name: name, types: typesJson.arrayValue.map { $0.stringValue }))
}
print("dogs", dogs)
Here is my code. I am pulling JSON data from CalorieNinjas API:
struct Result: Codable {
var items: [FoodItem]?
}
struct FoodItem: Codable {
var name: String?
var calories: String?
}
public class API {
func apiRequest(search: String, completion: #escaping (Result) -> ()) {
//URL
var query = search.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
let url = URL(string: "https://calorieninjas.p.rapidapi.com/v1/nutrition?query=" + query!)
//URL REQUEST
var request = URLRequest(url: url!, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0)
//Specify header
let headers = [
"x-rapidapi-key": "3be44a36b7msh4d4738910c1ca4dp1c2825jsn96bcc44c2b19",
"x-rapidapi-host": "calorieninjas.p.rapidapi.com"
]
request.httpMethod="GET"
request.allHTTPHeaderFields = headers
//Get the URLSession
let session = URLSession.shared
//Create data task
let dataTask = session.dataTask(with: request) { (data, response, error) in
let result = try? JSONDecoder().decode(Result.self, from: data!)
print(result)
DispatchQueue.main.async {
completion(result!)
}
}
//Fire off data task
dataTask.resume()
}
}
this is what my view looks like:
struct ContentView: View {
#State var result = Result()
#State private var searchItem: String = ""
var body: some View {
ZStack(alignment: .top) {
Rectangle()
.fill(Color.myPurple)
.ignoresSafeArea(.all)
VStack {
TextField("Enter food", text: $searchItem)
.background(Color.white)
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
SearchButton()
.padding(.top)
.onTapGesture {
API().apiRequest(search: searchItem, completion: { (result) in
self.result = result
})
}
}
}
}
}
This is the output to the terminal as a result of my print statement so I know my data is being fetched and stored:
Optional(CalorieCountApp.Result(items: Optional([CalorieCountApp.FoodItem(name: Optional("pizza"), calories: Optional(262.9))])))
what I was trying to do was something like Text(result.items.name/calories) but I am not able to access the variables like that. I am new to swift and making apps as a whole any help is much appreciated
Looks like you have a few Optionals in there, which means you'll probably be using the ? operator to unwrap them.
Given your type, this should work:
let index = 0
let name = result?.items?[index].name // will be `String?`
let calories = result?.items?[index].calories // according to your code you provided, this says `String?` but in your console output it looks like `Double?`
or in your example:
Text(result?.items?[index].name ?? "unknown")
You might want to do some more reading about unwrapping Optionals or dealing with nil in Swift -- there are a few different strategies. For example, you can see I used ?? in the last example there.
Here's a helpful link: https://www.hackingwithswift.com/sixty/10/2/unwrapping-optionals
I know the question sounds weird but I don't know another way to ask this, first of all, I am playing with the Pokemon API and I am new in swift. what is my problem I am parsing the data to show Pokemon information but the endpoint to show pokemon comes like this:
https://pokeapi.co/api/v2/pokemon/
{
"count": 949,
"previous": null,
"results": [
{
"url": "https://pokeapi.co/api/v2/pokemon/1/",
"name": "bulbasaur"
},
{
"url": "https://pokeapi.co/api/v2/pokemon/2/",
"name": "ivysaur"
},
{
"url": "https://pokeapi.co/api/v2/pokemon/3/",
"name": "venusaur"
},
With a name and other URL to see more about a specific pokemon. I could get the dictionary of array and I can show the pokemon name but I don't know how to get the other data that is in the other endpoint.
This is my code for now:
#IBAction func generatePokemon(_ sender: Any) {
// TODO: Improve API request
let apiUrl = URL(string: "https://pokeapi.co/api/v2/pokemon")
let request = URLRequest(url: apiUrl!)
// Request to Pokemon API
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if error == nil {
// Optional binding to get data
if let data = data {
let parsedResult: [String:AnyObject]!
do {
parsedResult = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String:AnyObject]
if let resultDictonary = parsedResult["results"] as? [[String:AnyObject]] {
print(resultDictonary[0])
}
} catch {
print("Error in parse json")
}
}
}
}
task.resume()
}
So I am not sure if I need to create another function to get the data of that endpoint and then call that inside my generatePokemon function so I can fill the view with more information? or what is the best way to consume that data.
Here is my repo too if someone wanna see it I have a branch there where I am doing all this first network request.
Github
Thank for your time guys!
Hey I did something like what you need here is my repo:
https://bitbucket.org/pokemonred/pokedexgr2/
Check for the branch pokedexSebas, if you have any questions let me know.
I'm using alamofire to perform the requests.
In the repo you have to take a look into two clases, the first one is: SBBackendManager and SebasObjectMapper.
In the SBBackendManager I have these 2 methods:
func getAllPokemon () { // This will retrieve all pokemon
let url = "https://pokeapi.co/api/v2/pokemon"
Alamofire.request(url).responseObject { (response: DataResponse<SPokemonApiResponse>) in
let pokemonResponse = response.result.value
if let sPokeArray = pokemonResponse?.resultados {
contador = sPokeArray.count
}
}
}
func getPokemon(_ url:String){ // This will retrieve a single pokemon
Alamofire.request(url).responseObject { (response: DataResponse<SPokemon>) in
let spokemon = response.result.value
pokemonArray += [spokemon!]
contador = contador! - 1
}
}
And on SebasObjectMapper I have this:
class SPokemonApiResponse:Mappable{
var resultados:[SPokemonResult]?
required init?(map: Map) { }
func mapping(map: Map) {
resultados <- map["results"]
}
}
class SPokemonResult:Mappable {
var url:String? {
didSet { // HERE: every time a result is added to the array will trigger the get a single pokemon method
let bm = SBackendManager()
bm.getPokemon(url!)
}
}
required init(map:Map) {}
func mapping(map: Map) {
url <- map["url"]
}
}
Is there a way to properly deserialize a JSON response to Swift objects resp. using DTOs as containers for fixed JSON APIs?
Something similar to http://james.newtonking.com/json or something like this example from Java
User user = jsonResponse.readEntity(User.class);
whereby jsonResponse.toString() is something like
{
"name": "myUser",
"email": "user#example.com",
"password": "passwordHash"
}
SWIFT 4 Update
Since you give a very simple JSON object the code prepared for to handle that model. If you need more complicated JSON models you need to improve this sample.
Your Custom Object
class Person : NSObject {
var name : String = ""
var email : String = ""
var password : String = ""
init(JSONString: String) {
super.init()
var error : NSError?
let JSONData = JSONString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
let JSONDictionary: Dictionary = NSJSONSerialization.JSONObjectWithData(JSONData, options: nil, error: &error) as NSDictionary
// Loop
for (key, value) in JSONDictionary {
let keyName = key as String
let keyValue: String = value as String
// If property exists
if (self.respondsToSelector(NSSelectorFromString(keyName))) {
self.setValue(keyValue, forKey: keyName)
}
}
// Or you can do it with using
// self.setValuesForKeysWithDictionary(JSONDictionary)
// instead of loop method above
}
}
And this is how you invoke your custom class with JSON string.
override func viewDidLoad() {
super.viewDidLoad()
let jsonString = "{ \"name\":\"myUser\", \"email\":\"user#example.com\", \"password\":\"passwordHash\" }"
var aPerson : Person = Person(JSONString: jsonString)
println(aPerson.name) // Output is "myUser"
}
I recommend that you use code generation (http://www.json4swift.com) to create native models out of the json response, this will save your time of parsing by hand and reduce the risk of errors due to mistaken keys, all elements will be accessible by model properties, this will be purely native and the models will make more sense rather checking the keys.
Your conversion will be as simple as:
let userObject = UserClass(userDictionary)
print(userObject!.name)
Swift 2: I really like the previous post of Mohacs! To make it more object oriented, i wrote a matching Extension:
extension NSObject{
convenience init(jsonStr:String) {
self.init()
if let jsonData = jsonStr.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
{
do {
let json = try NSJSONSerialization.JSONObjectWithData(jsonData, options: []) as! [String: AnyObject]
// Loop
for (key, value) in json {
let keyName = key as String
let keyValue: String = value as! String
// If property exists
if (self.respondsToSelector(NSSelectorFromString(keyName))) {
self.setValue(keyValue, forKey: keyName)
}
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
}
else
{
print("json is of wrong format!")
}
}
}
custom classes:
class Person : NSObject {
var name : String?
var email : String?
var password : String?
}
class Address : NSObject {
var city : String?
var zip : String?
}
invoking custom classes with JSON string:
var jsonString = "{ \"name\":\"myUser\", \"email\":\"user#example.com\", \"password\":\"passwordHash\" }"
let aPerson = Person(jsonStr: jsonString)
print(aPerson.name!) // Output is "myUser"
jsonString = "{ \"city\":\"Berlin\", \"zip\":\"12345\" }"
let aAddress = Address(jsonStr: jsonString)
print(aAddress.city!) // Output is "Berlin"
Yet another JSON handler I wrote:
https://github.com/dankogai/swift-json
With it you can go like this:
let obj:[String:AnyObject] = [
"array": [JSON.null, false, 0, "", [], [:]],
"object":[
"null": JSON.null,
"bool": true,
"int": 42,
"double": 3.141592653589793,
"string": "a α\t弾\n𪚲",
"array": [],
"object": [:]
],
"url":"http://blog.livedoor.com/dankogai/"
]
let json = JSON(obj)
json.toString()
json["object"]["null"].asNull // NSNull()
json["object"]["bool"].asBool // true
json["object"]["int"].asInt // 42
json["object"]["double"].asDouble // 3.141592653589793
json["object"]["string"].asString // "a α\t弾\n𪚲"
json["array"][0].asNull // NSNull()
json["array"][1].asBool // false
json["array"][2].asInt // 0
json["array"][3].asString // ""
As you see no !? needed between subscripts.
In addition to that you can apply your own schema like this:
//// schema by subclassing
class MyJSON : JSON {
override init(_ obj:AnyObject){ super.init(obj) }
override init(_ json:JSON) { super.init(json) }
var null :NSNull? { return self["null"].asNull }
var bool :Bool? { return self["bool"].asBool }
var int :Int? { return self["int"].asInt }
var double:Double? { return self["double"].asDouble }
var string:String? { return self["string"].asString }
var url: String? { return self["url"].asString }
var array :MyJSON { return MyJSON(self["array"]) }
var object:MyJSON { return MyJSON(self["object"]) }
}
let myjson = MyJSON(obj)
myjson.object.null // NSNull?
myjson.object.bool // Bool?
myjson.object.int // Int?
myjson.object.double // Double?
myjson.object.string // String?
myjson.url // String?
There's a great example by Apple for deserializing JSON with Swift 2.0
The trick is to use the guard keyword and chain the assignments like so:
init?(attributes: [String : AnyObject]) {
guard let name = attributes["name"] as? String,
let coordinates = attributes["coordinates"] as? [String: Double],
let latitude = coordinates["lat"],
let longitude = coordinates["lng"],
else {
return nil
}
self.name = name
self.coordinates = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
}
I personally prefer native parsing vs any 3rd party, as it is transparent and magic-less. (and bug less?)
Using quicktype, I generated your model and serialization helpers from your sample:
import Foundation
struct User: Codable {
let name: String
let email: String
let password: String
}
extension User {
static func from(json: String, using encoding: String.Encoding = .utf8) -> OtherUser? {
guard let data = json.data(using: encoding) else { return nil }
return OtherUser.from(data: data)
}
static func from(data: Data) -> OtherUser? {
let decoder = JSONDecoder()
return try? decoder.decode(OtherUser.self, from: data)
}
var jsonData: Data? {
let encoder = JSONEncoder()
return try? encoder.encode(self)
}
var jsonString: String? {
guard let data = self.jsonData else { return nil }
return String(data: data, encoding: .utf8)
}
}
Then parse User values like this:
let user = User.from(json: """{
"name": "myUser",
"email": "user#example.com",
"password": "passwordHash"
}""")!
I wrote this small open-source library recently that lets you quickly and easily deserialize dictionaries into Swift objects: https://github.com/isair/JSONHelper
Using it, deserializing data becomes as easy as this:
var myInstance = MyClass(data: jsonDictionary)
or
myInstance <-- jsonDictionary
And models need to look only like this:
struct SomeObjectType: Deserializable {
var someProperty: Int?
var someOtherProperty: AnotherObjectType?
var yetAnotherProperty: [YetAnotherObjectType]?
init(data: [String: AnyObject]) {
someProperty <-- data["some_key"]
someOtherProperty <-- data["some_other_key"]
yetAnotherProperty <-- data["yet_another_key"]
}
}
Which, in your case, would be:
struct Person: Deserializable {
var name: String?
var email: String?
var password: String?
init(data: [String: AnyObject]) {
name <-- data["name"]
email <-- data["email"]
password <-- data["password"]
}
}
If you would like parse from and to json without the need to manually map keys and fields, then you could also use EVReflection. You can then use code like:
var user:User = User(json:jsonString)
or
var jsonString:String = user.toJsonString()
The only thing you need to do is to use EVObject as your data objects base class.
See the GitHub page for more detailed sample code
I am expanding upon Mohacs and Peter Kreinz's excellent answers just a bit to cover the array of like objects case where each object contains a mixture of valid JSON data types. If the JSON data one is parsing is an array of like objects containing a mixture of JSON data types, the do loop for parsing the JSON data becomes this.
// Array of parsed objects
var parsedObjects = [ParsedObject]()
do {
let json = try NSJSONSerialization.JSONObjectWithData(jsonData, options: []) as [Dictionary<String, AnyObject>]
// Loop through objects
for dict in json {
// ParsedObject is a single instance of an object inside the JSON data
// Its properties are a mixture of String, Int, Double and Bool
let parsedObject = ParsedObject()
// Loop through key/values in object parsed from JSON
for (key, value) in json {
// If property exists, set the value
if (parsedObject.respondsToSelector(NSSelectorFromString(keyName))) {
// setValue can handle AnyObject when assigning property value
parsedObject.setValue(keyValue, forKey: keyName)
}
}
parsedObjects.append(parsedObject)
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
This way lets you get the user from a URL. It's parse the NSData to a NSDictionary and then to your NSObject.
let urlS = "http://api.localhost:3000/"
func getUser(username: Strung) -> User {
var user = User()
let url = NSURL(string: "\(urlS)\(username)")
if let data = NSData(contentsOfURL: url!) {
setKeysAndValues(user, dictionary: parseData(data))
}
return user
}
func setKeysAndValues (object : AnyObject, dictionary : NSDictionary) -> AnyObject {
for (key, value) in dictionary {
if let key = key as? String, let value = value as? String {
if (object.respondsToSelector(NSSelectorFromString(key))) {
object.setValue(value, forKey: key)
}
}
}
return object
}
func parseData (data : NSData) -> NSDictionary {
var error: NSError?
return NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &error) as! NSDictionary
}
In Swift 4, You can use the Decoding, CodingKey protocols to deserialize the JSON response:
Create the class which confirm the decodable protocol
class UserInfo: Decodable
Create members of the class
var name: String
var email: String
var password: String
Create JSON key enum which inherits from CodingKey
enum UserInfoCodingKey: String, CodingKey {
case name
case password
case emailId
}
Implement init
required init(from decoder: Decoder) throws
The whole class look like :
Call Decoder
// jsonData is JSON response and we get the userInfo object
let userInfo = try JsonDecoder().decode(UserInfo.self, from: jsonData)
You do this by using NSJSONSerialization. Where data is your JSON.
First wrap it in an if statement to provide some error handling capablity
if let data = data,
json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [String: AnyObject] {
// Do stuff
} else {
// Do stuff
print("No Data :/")
}
then assign them:
let email = json["email"] as? String
let name = json["name"] as? String
let password = json["password"] as? String
Now, This will show you the result:
print("Found User iname: \(name) with email: \(email) and pass \(password)")
Taken from this Swift Parse JSON tutorial. You should check out the tutorial as it goes a lot more in depth and covers better error handling.