How to map [[String:AnyObject]] to a class from a string - ios

I'm trying to add an Array of objects from a string. I am getting an encrypted string from an API call. After decrypting the data, getting as a string of JSON structure as below:
{
"request_id”:”abcds123”,
"status”:”18”,
"message":"SUCCESS",
"customer_name”:”XXXX X XX”,
"mobile_no”:”123456789”,
"email_id":"xxx#xxx.com",
“function”:
[
{“funcCode”:”OPN”,,”funcVal”:0,”funcType":"M"},
{“funcCode”:”CLO”,”funcVal”:1,”funcType":"M"}
],
“dataID”:”ASD1234”,
”valid”:”Y”
}
This is generic API response which will very based on response.
I can map "function" element to [[String:AnyObject]]. But not able to map it to class directly. Do we have an easier approach to extract "function" array to a class in swift without iterating the data and add to an array variable:
var mainObject : [String:Any] = [:]
if let data = result.data(using: .utf8) {
do {
mainObject = (try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any])!
if let status = mainObject[“status”] as? String {
switch(status) {
case 200:
if mainObject.keys.contains(“customer_name”) {
//do something
}
if mainObject.keys.contains(“function”) {
if let functionList = mainObject[“function”] as? [[String:AnyObject]] {
//map functionList to class [Function]
for function in functionList {
print(“Function ====> ", function)
//create a class and add to an array of Function class
}
}
}
}
} catch {
print(error.localizedDescription)
}
}
}
The result string coming from response.
Objective is to extract "function" data alone and map it to class without creating container class/struct.

You can use the Codable protocol that will help you to map your JSON data into objects.
First create your structs:
struct MyFunction: Codable {
let funcCode: String
let funcVal: Int
let funcType: String
}
struct MyContainer: Codable {
let status: string,
let message: string,
// Another properties
let function: [MyFunction]
}
Then you just have to map your JSON string to the object using the decode function:
if let jsonData = result.data(using: .utf8) {
do {
let decoder = JSONDecoder()
let mainObject = try decoder.decode(MyContainer.self, for: jsonData)
} catch {
print(error.localizedDescription)
}
}
For more information you can check out this blog post.

State of the art is the (De)Codable protocol, you can decode JSON dictionaries directly into structs
struct Main: Decodable {
let status, customerName: String
let function : [Function]
}
struct Function: Codable {
let funcCode, funcType: String
let funcVal : Int
}
let data = Data(result.utf8)
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let mainObject = try decoder.decode(Main.self, from: data)
print(mainObject.customerName)
print(mainObject.function)
} catch {
print(error)
}
Note: A JSON dictionary is never [String:AnyObject] in Swift 3+, it's [String:Any]
Edit: If you want only the Function struct keep the JSONSerialization code, omit the Main struct, declare mainObject as
var functions = [Function]()
and map the JSON array
if let functionList = mainObject["function"] as? [[String:Any]] {
functions = functionList.map {Function(funcCode: $0["funcCode"] as! String,
funcVal: $0["funcVal"] as! Int,
funcType: $0["funcType"] as! String) }
}
To write the array to UserDefaults encode it with JSONEncoder to Data.

Related

How to convert stored values to JSON format using Swift?

I am trying to convert stored coredata values to JSON format and the JSON format value need to assign a single variable, because this generated JSON I need to send to server. Below code I tried to get coredata stored values but don’t know how to generate JSON required format.
Getting values from coredata
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "User")
do {
let results = try context.fetch(fetchRequest)
let dateCreated = results as! [Userscore]
for _datecreated in dateCreated {
print("\(_datecreated.id!)-\(_datecreated.name!)") // Output: 79-b \n 80-c \n 78-a
}
} catch let err as NSError {
print(err.debugDescription)
}
Need to Convert Coredata Value to Below JSON format
{
    "status": true,
    "data": [
        {
            "id": "20",
            "name": "a"
        },
        {
            "id": "21",
            "name": "b"
        },
        {
            "id": "22",
            "name": "c"
        }
    ]
}
Probably the easiest is to convert your object(s) to either dictionaries or arrays (depending on what you need).
First you need to be able to convert your Userscore to dictionary. I will use extension on it since I have no idea what your entity looks like:
extension Userscore {
func toDictionary() -> [String: Any]? {
guard let id = id else { return nil }
guard let name = name else { return nil }
return [
"id": id,
"name": name
]
}
}
Now this method can be used to generate an array of your dictionaries simply using let arrayOfUserscores: [[String: Any]] = userscores.compactMap { $0.toDictionary() }.
Or to build up your whole JSON as posted in question:
func generateUserscoreJSON(userscores: [Userscore]) -> Data? {
var payload: [String: Any] = [String: Any]()
payload["status"] = true
payload["data"] = userscores.compactMap { $0.toDictionary() }
return try? JSONSerialization.data(withJSONObject: payload, options: .prettyPrinted)
}
This will now create raw data ready to be sent to server for instance
var request = URLRequest(url: myURL)
request.httpBody = generateUserscoreJSON(userscores: userscores)
You can use the properties of an Encodable to make this happen. This has the added benefit of not resorting to the Any type.
For the JSON, you could use the following types:
struct JSONMessage: Encodable {
var status: Bool
var data: [JSONDataEntry]
}
struct JSONDataEntry: Encodable {
var id: String
var name: String
}
Then you can adjust your do/try/catch as follows:
do {
let results = try context.fetch(fetchRequest)
let dateCreated = results as! [Userscore]
// *starting here*
let data = dateCreated.map { JSONDataEntry(id: String($0.id!), name: $0.name!) }
let status = true // <- not sure where status comes from, so adding here
let message = JSONMessage(status: status, data: data)
let jsonData = try JSONEncoder().encode(message)
if let json = String(data: jsonData, encoding: .utf8) {
// do something with the JSON string
print(json)
}
// *ending here*
} catch let err as NSError {
print(err.debugDescription)
}

How to store json response into variable?

I am getting json response from api. I need to store into variable using model,how can i store?
struct StructLogin {
var status:String?
var contentarr = [content]()
}
struct content {
var AuthoToken:String?
var user_name:String?
var user_type:String?
}
let jsonResponse = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
StructLoginObj.status = jsonResponse!["status"] as? String
StructLoginObj.contentarr = contentArray
contentObj.AuthoToken = jsonResponse!["auth_token"] as? String
contentObj.user_name = jsonResponse!["user_name"] as? String
contentObj.user_type = jsonResponse!["user_type"] as? String
{"status":"200","content":{"user_type":"1","user_name":"Super Admin","auth_token":"7500b440c0f8035e864e1541c650b888"}}
Use Codable to parse your JSON response into an object.
struct Login: Codable {
var status: String?
var content:Content?
}
struct Content: Codable {
var auth_token:String?
var user_name:String?
var user_type:String?
}
Let's take the example of JSON response provided by you.
let str = """
{"status":"200","content":{"user_type":"1","user_name":"Super Admin","auth_token":"7500b440c0f8035e864e1541c650b888"}}
"""
Since we don't have the actual Data from API, we'll be converting str to data for parsing.
if let data = str.data(using: .utf8) {
let login = try? JSONDecoder().decode(Login.self, from: data)
print(login)
}
You need to use Codable for easy convert the object to data , also your content key is a dictionary not an array
Save:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let res = try! decoder.decode(Root.self, from: data)
print(res.content)
let data = try! JSONEncoder().encode(res.content)
// save data anywhere e.x userDefaults if it's a settings
UserDefaults.shared.set(data,forKey:"User")
Read:
if let data = UserDefaults.shared.data(forKey:"User") {
print(data)
}
struct Root: Codable {
let status: String
let content: Content
}
struct Content: Codable {
let userType, userName, authToken: String
}
You can use JSON.parse() method to convert API return value to JavaScript object :
let result = JSON.parse(data);

Passing JSON result into a struct model

I am receiving a result from an API, I can iterate through the result. My understanding is I can pass the value into a model immediately.
Apple Developer article on struct models
My issue is I am not doing it properly and am receiving a nil value. Perhaps someone can see where I need to change. I am using Swift 4.2
Here is my struct model.
import Foundation
struct ProfileModel {
//MARK: Properties
var name: String
var email: String
var profileURL: String
//MARK: Initialization
}
extension ProfileModel{
init?(json: [String:AnyObject]) {
guard
let name = json["name"] as? String,
let email = json["email"] as? String,
let profileURL = json["profileURL"] as? String
else { return nil }
self.name = name
self.email = email
self.profileURL = profileURL
}
}
Here is my result code from my urlConnection. Let me know if we want to see the entire swift file
//create dataTask using the session object to send data to the server
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
guard error == nil else {
return
}
guard let data = data else {
return
}
do {
//create json object from data
if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:AnyObject] {
self.onSuccess(data: json)
}
} catch let error {
print(error.localizedDescription)
}
})
task.resume()
}
func onSuccess(data: [String:AnyObject]){
print("onSuccess")
let myProfile = ProfileModel(json: data)
//myProfile is nil while unwrapping
let title: String = myProfile!.name
print(title)
}
I could just iterate through the strings since I am able to print 'data'. I just figured it would be cleaner to put everything into a ProfileModel and manage that object as a whole.
This json is my more simple one which is why I used it for this question. I also can't remember but I had to use "[String:AnyObject]" to get the json properly. This was pulled directly from my terminal, this was the data being passed in my JsonResponse. The output json from Xcode has [] on the outside instead.
{
'detail': 'VALID',
‘name’: ‘Carson,
'email': ‘carson.skjerdal#somethingelselabs.com',
'pic_url': None
}
EDIT:
So my problem is solved, and ultimately moving to Codable was the key. Here is my fixed code for anyone who might need a working solution.
URLSession.shared.dataTask(with: request as URLRequest) { (data, response
, error) in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let gitData = try decoder.decode(ProfileModel.self, from: data)
print(gitData.name)
self.onSuccess(data: gitData)
} catch let err {
print("Err", err)
}
}.resume()
}
func onSuccess(data: ProfileModel){
print("onSuccess")
print(data.email)
}
My Codable Struct - slightly simplified
import Foundation
struct ProfileModel: Codable {
let detail, name, email: String
private enum CodingKeys: String, CodingKey {
case detail, email
case name = "firstname"
//case picUrl = "pic_url"
}
}
After "Codable" has been introduced I always uses that.
You can take your JSON ans pars it in to QuickType.io, and you will get a Struct that confirms to the codadable
// To parse the JSON, add this file to your project and do:
//
// let aPIResponse = try? newJSONDecoder().decode(APIResponse.self, from: jsonData)
import Foundation
struct APIResponse: Codable {
let detail, name, email, picUrl: String
enum CodingKeys: String, CodingKey {
case detail, name, email
case picUrl = "pic_url"
}
}

IOS/Swift: Parse JSON data and dictionary

I am trying to parse some nested JSON retrieved through an API but am having trouble isolating specific key-value pairs. In fact, I have some confusion over the difference between the JSON data and the dictionary obtained through serialization.
To retrieve the data I am using:
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
return
}
To convert the data to a JSON dictionary, I am doing
do {
let stringDic = try JSONSerialization.jsonObject(with: data, options: []) as? [String : Any]
} catch let error {
print(error)
}
When printed, this produces nested output of the form:
Optional(["document_tone": {
"tone_categories" = (
{
"category_id" = "emotion_tone";
"category_name" = "Emotion Tone";
and so forth
My question is how can I get a unique value such as that for the key category_name?
If I try to use
let myCat = stringDic["category_name"]
Fix-it requires let document_tone = stringDic?["document_tone"] which if printed to console just prints whole dictionary over again.
Thanks in advance for any suggestions.
It's pretty easy: () is array, {} is dictionary and the compiler must know the static types of all subscripted objects:
if let documentTone = stringDic?["document_tone"] as? [String:Any],
let toneCategories = documentTone["tone_categories"] as? [[String:Any]] {
for category in toneCategories {
print(category["category_name"])
}
}
I think it's better to use Decodable
struct Root:Decodable {
let documentTone : InnerItem
}
struct InnerItem:Decodable {
let toneCategories: [BottomItem]
}
struct BottomItem:Decodable {
let categoryId: String
let categoryName: String
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(Root.self, from: data)
//print all names
result.documentTone.toneCategories.forEach {print($0.categoryName) }
} catch {
print(error)
}

struct for nested dictionary API in swift

i'm trying to import JSON data from the v2 of coinmarketcap API. I had it working with v1 as it was an array, however the new version is a dictionary and i cant quite get my struct correct.
The API im using is : https://api.coinmarketcap.com/v2/ticker/?convert=AUD
My struct is set up as below:
struct Coin: Decodable {
private enum CodingKeys: String, CodingKey {
case id = "rank", symbol, name, priceAUD = "quotes"
}
var id: String
var symbol : String
var name : String
var priceAUD : quoteStruct
}
struct quoteStruct{
let aud : priceStruct
}
struct priceStruct{
let price : String
}
My code for fetching the data is:
var coins = [Coin]()
func getCoinData() {
let jsonURL = "https://api.coinmarketcap.com/v2/ticker/?convert=AUD"
let url = URL(string: jsonURL)
URLSession.shared.dataTask(with: url!) { [unowned self] (data, response, error) in
guard let data = data else { return }
do {
self.coins = try JSONDecoder().decode([Coin].self, from: data)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print("Error is : \n\(error)")
}
}.resume()
}
My code for fetching the data i have used the same as previously which worked with v1 of the API, however i don't think i made my struct correctly.
Thanks in advance!
Your response Changed i try to configure it by converting it to array of dictionary you will need to change quotes to be [String:priceStruct]
struct Coin: Decodable {
private enum CodingKeys: String, CodingKey {
case id,rank,symbol, name, priceAUD = "quotes"
}
var id: Int
var rank: Int
var symbol : String
var name : String
var priceAUD : [String: priceStruct]
}
struct priceStruct : Decodable{
let price : Double
}
func getCoinData() {
var coins = [Coin]()
let jsonURL = "https://api.coinmarketcap.com/v2/ticker/?convert=AUD"
let url = URL(string: jsonURL)
URLSession.shared.dataTask(with: url!) { [unowned self] (data, response, error) in
guard let data = data else { return }
do {
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any], let resultData = json["data"] as? [String:Any] {
let dataObject = try JSONSerialization.data(withJSONObject: resultData.values.map({$0}) , options: .prettyPrinted)
coins = try JSONDecoder().decode([Coin].self, from: dataObject)
print(coins.count)
}
} catch {
print("Error is : \n\(error)")
}
}.resume()
}
You response in data parameter should be an array rather than a dictionary. You will not be able to iterate a dictionary over undefined keys. It would be good to get your response of API updated first.
But, If you wish to continue with the existing API response, first you need to convert your response in an array and use your Decodable structs as:
struct Coin: Decodable {
var id: String
var symbol : String
var name : String
var priceAUD : QuoteStruct
private enum CodingKeys: String, CodingKey {
case id = "rank", symbol, name, priceAUD = "quotes"
}
}
struct QuoteStruct: Decodable {
let aud : PriceStruct
}
struct PriceStruct: Decodable {
let price : String
}
Update your data parsing in API block as:
guard let responseData = data else { return }
do {
let json = try? JSONSerialization.jsonObject(with: responseData, options: [])
if let jsonData = json as? [String: Any], let dataObject = jsonData["data"] as? [Int: Any] {
let coinArray = dataObject.map { $0.1 }
if let jsonData = try? JSONSerialization.data(withJSONObject: coinArray, options: .prettyPrinted) {
coins = try JSONDecoder().decode([Coin].self, from: jsonData)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
} catch {
print("Error is : \n\(error)")
}

Resources