Xcode - Parse string into usable data structure - json

I have a string (snippet below) that I know is valid json by parsing it here. The final parsing that I want to achieve is based off this PHP code. I can not seem to keep xcode happy when I try the following code, as I seem to continue to make array's of <Any> and either get the error Value of type 'Any' has no subscripts or an unwrapping of a nil
any thoughts how to parse this would be greatly appreciated.
if let jsonArray = try JSONSerialization.jsonObject(with: jsonString.data(using: .utf8)!,options : .allowFragments) as? Array<Any> {
let a = jsonArray[0] as? Array<Any>
let a = jsonArray[0] as? Array<Any>
let b = a?[1] as? Array<Any>
let c = b?[1] as? Array<Array<Any>>
}
Snippet - full string here
[["comic books",[["","","","","","","","","Ba6eX5HWBc3btAa6mJ_4Cg","0ahUKEwjR7NSWsOHsAhXNLc0KHTrMB68QmBkIAigA",["comic books",0]
]
,["","","","","","","","","Ba6eX5HWBc3btAa6mJ_4Cg","0ahUKEwjR7NSWsOHsAhXNLc0KHTrMB68QmBkIBCgB","","","","",["Ba6eX5HWBc3btAa6mJ_4Cg","0ahUKEwjR7NSWsOHsAhXNLc0KHTrMB68Q8BcIBSgAMAA",["330 Army Trail Rd","Glendale Heights, IL 60139"]
,"",["","","",["https://www.google.com/search?q\u003dGot+Comics+Inc,+330+Army+Trail+Rd,+Glendale+Heights,+IL+60139\u0026ludocid\u003d7330282631276370350#lrd\u003d0x880fac8c25bbd32b:0x65ba6537347199ae,1","105

First, let's declare the data type:
struct Item {
let name: String?
let location: String?
let phone: String?
}
Using SwiftyJSON, you can nicely convert the PHP code into something like this:
var items = [Item]()
if let json = try? JSON(data: jsonData), let arr = json[0][1].array {
for itemArray in arr {
guard let itemDetails = itemArray[14].array else { continue }
let name = itemDetails[11].string
let location = itemDetails[18].string
let phone = itemDetails[178][0][0].string
items.append(Item(name: name, location: location, phone: phone))
}
}
For fun, here's a shorter, more functional, less readable, implementation of the above code:
let items = (try? JSON(data: jsonData))?[0][1].array?
.compactMap { $0[14].array }
.map { Item(name: $0[11].string, location: $0[18].string, phone: $0[178][0][0].string) }
?? []

Related

Converting API JSON data to a Swift struct

I am using Swift for the first time and I'd like to be able to process some info from an API response into a usable Swift object.
I have (for example) the following data coming back from my API:
{
data: [{
id: 1,
name: "Fred",
info: {
faveColor: "red",
faveShow: "Game of Thrones",
faveIceCream: "Chocolate",
faveSport: "Hockey",
},
age: "28",
location: "The Moon",
},{
...
}]
}
In swift I have the data coming back from the API. I get the first object and I'm converting it and accessing it like so:
let json = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
let dataParentNode = json["data"] as! [[String:Any]]
let firstObject = dataParentNode[0]
let _id = firstObject["id"] as? String ?? "0"
let _name = firstObject["name"] as? String ?? "Unknown"
This is fine until I want to start processing the sub-objects belonging to the first object so I came up with the following structs to try and make this cleaner.
Please note - I don't need to process all of the JSON data coming back so I want to convert it to what I need in the structs
struct PersonInfo : Codable {
let faveColor: String?
let faveShow: String?
}
struct Person : Codable {
let id: String?
let name: String?
let info: PersonInfo?
}
When I take this:
let json = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
let dataParentNode = json["data"] as! [[String:Any]]
let firstObject = dataParentNode[0]
and then try to convert firstObject to Person or firstObject["info"] to PersonInfo I can't seem to get it to work (I get nil).
let personInfo = firstObject["info"] as? PersonInfo
Can anyone advise please? I just need to get my head around taking API response data and mapping it to a given struct (with sub-objects) ignoring the keys I don't need.
You can simply use decode(_:from:) function of JSONDecoder for this:
let decoder = JSONDecoder()
do {
let decoded = try decoder.decode([String: [Person]].self, from: data)
let firstObject = decoded["data"]?.first
} catch {
print(error)
}
Even better you can add another struct to you model like this:
struct PersonsData: Codable {
let data: [Person]
}
And map your JSON using that type:
let decoder = JSONDecoder()
do {
let decoded = try decoder.decode(PersonsData.self, from: data)
let firstObject = decoded.data.first
} catch {
print(error)
}
Update: Your Person struct might need a little change, because the id property is integer in your JSON.
So, it will end up like this:
struct Person : Codable {
let id: Int?
let name: String?
let info: PersonInfo?
}

Parsing json blob field in Swift

I am reading JSON from a web service in swift which is in the following format
[{
"id":1,
"shopName":"test",
"shopBranch":"main",
"shopAddress":"usa",
"shopNumber":"5555555",
"logo":[-1,-40,-1,-32],
"shopPath":"test"
},
{
"id":2,
"shopName":"test",
"shopBranch":"main",
"shopAddress":"usa",
"shopNumber":"66666666",
"logo":[-1,-50,-2,-2],
"shopPath":"test"
}]
I have managed to read all the strings easily but when it comes to the logo part I am not sure what should I do about it, this is a blob field in a mySQL database which represent an image that I want to retrieve in my swift UI, here is my code for doing that but i keep getting errors and not able to figure the right way to do it:
struct Brand: Decodable {
private enum CodingKeys: String, CodingKey {
case id = "id"
case name = "shopName"
case branch = "shopBranch"
case address = "shopAddress"
case phone = "shopNumber"
case logo = "logo"
case path = "shopPath"
}
let id: Int
let name: String
let branch: String
let address: String
let phone: String
let logo: [String]
let path: String
}
func getBrandsJson() {
let url = URL(string: "http://10.211.55.4:8080/exam/Test")
URLSession.shared.dataTask(with: url!, completionHandler: {(data, response, error) in
guard let data = data, error == nil else {
print(error!);
return
}
print(response.debugDescription)
let decoder = JSONDecoder()
let classes = try! decoder.decode([Brand].self, from: data)
for myClasses in classes {
print(myClasses.branch)
if let imageData:Data = myClasses.logo.data(using:String.Encoding.utf8){
let image = UIImage(data:imageData,scale:1.0)
var imageView : UIImageView!
}
}
}).resume()
}
Can someone explain how to do that the right way I have searched a lot but no luck
First of all, you should better tell the server side engineers of the web service, that using array of numbers is not efficient to return a binary data in JSON and that they should use Base-64 or something like that.
If they are stubborn enough to ignore your suggestion, you should better decode it as Data in your custom decoding initializer.
struct Brand: Decodable {
private enum CodingKeys: String, CodingKey {
case id = "id"
case name = "shopName"
case branch = "shopBranch"
case address = "shopAddress"
case phone = "shopNumber"
case logo = "logo"
case path = "shopPath"
}
let id: Int
let name: String
let branch: String
let address: String
let phone: String
let logo: Data
let path: String
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int.self, forKey: CodingKeys.id)
self.name = try container.decode(String.self, forKey: CodingKeys.name)
self.branch = try container.decode(String.self, forKey: CodingKeys.branch)
self.address = try container.decode(String.self, forKey: CodingKeys.address)
self.phone = try container.decode(String.self, forKey: CodingKeys.phone)
self.path = try container.decode(String.self, forKey: CodingKeys.path)
//Decode the JSON array of numbers as `[Int8]`
let bytes = try container.decode([Int8].self, forKey: CodingKeys.logo)
//Convert the result into `Data`
self.logo = Data(bytes: bytes.lazy.map{UInt8(bitPattern: $0)})
}
}
And you can write the data decoding part of your getBrandsJson() as:
let decoder = JSONDecoder()
do {
//You should never use `try!` when working with data returned by server
//Generally, you should not ignore errors or invalid inputs silently
let brands = try decoder.decode([Brand].self, from: data)
for brand in brands {
print(brand)
//Use brand.logo, which is a `Data`
if let image = UIImage(data: brand.logo, scale: 1.0) {
print(image)
//...
} else {
print("invalid binary data as an image")
}
}
} catch {
print(error)
}
I wrote some lines to decode array of numbers as Data by guess. So if you find my code not working with your actual data, please tell me with enough description and some examples of actual data. (At least, you need to show me a first few hundreds of the elements in the actual "logo" array.)
Replace
let logo: [String]
with
let logo: [Int]
to get errors use
do {
let classes = try JSONDecoder().decode([Brand].self, from: data)
}
catch {
print(error)
}

Use Swift Decoder to pull attributes from JSON array

I have a JSON array created using this call:
guard let json = (try? JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers)) as? [Any] else {
print("This is not JSON!!!")
return
}
I am trying to get elements from the JSON objects in the array to display them using the following code:
struct sWidget: Codable{
var createdBy: String
var createdDate: Date
var status: String
var widgetNumber: String
var updatedBy: String
var updatedDate: Date
}
do {
let decoder = JSONDecoder()
for (index, value) in json.enumerated() {
let currentWidget = try decoder.decode(sWidget.self, from: json[index] as! Data)
let currentNum = currentWidget.widgetNumber
//print(currentNum)
widgetNums.append(currentNum)
}
}
catch {
print("decoding error")
}
The code compiles but when I run it I get this error in the output:
Could not cast value of type '__NSDictionaryM' (0x1063c34f8) to
'NSData' (0x1063c1090). 2018-08-09 09:41:02.666713-0500
TruckMeterLogScanner[14259:1223764] Could not cast value of type
'__NSDictionaryM' (0x1063c34f8) to 'NSData' (0x1063c1090).
I am still investigating but any tips would be helpful.
Did you try that fetching objects like above mentioned? Because i see that you are using Codable. Fetching is very simple with that actually.
let yourObjectArray = JSONDecoder().decode([sWidget].self, data: json as! Data)
May be this line can be buggy but you can fetch them with one line.
Extending #Cemal BAYRI's answer:
JSONDecoder() throws, so make sure to either us try? or try (don't forget do-catch with try)
guard let data = content as? Data else {
return [sWidget]()
}
let jsonDecoder = JSONDecoder()
1. try?
let yourObjectArray = try? jsonDecoder.decode([sWidget].self, data: data)
2. try
do {
let yourObjectArray = try jsonDecoder.decode([sWidget].self, data: data)
} catch let error {
}
Note: You would need to take care of Data and Date formatting. Below is an example for Date:
jsonDecoder.dateDecodingStrategy = .iso8601
You can also check it out here

Parsing string json in swift 3

I have a json saved in String format and I´m trying to use:
let json = "{'result':[{'name':'Bob','age':'27'}]}";
Using JSONSerialization comes error about Cannot invoke jsonOject with an argument...
if let json = try JSONSerialization.jsonObject(with: json) as? [String: Any],
let array = json["result"] as? [[String: Any]] {
for item in array {
if let name = item["name"] as? String {
if name == "Bob" {
self.age = Int((item["age"] as? String)!)!
}
}
}
}
I tryed to use this solution but with no success.
Please look at the declaration of jsonObject(with) by ⌥-click on the symbol.
The first parameter is of type Data so you have to convert String to Data.
A major issue is that the JSON is not formatted correctly. The string indicators must be double quotes.
let json = "{\"result\":[{\"name\":\"Bob\",\"age\":27}]}"
let jsonData = json.data(using: .utf8)
do {
if let result = try JSONSerialization.jsonObject(with: jsonData!) as? [String: Any],
...
self.age = item["age"] as! Int

Swift :: Traverse a json string

Here is a string looks like a json.
let text2 = " [{ \"insertion_date\" :\""+"2015-07-31 11:21:04 +0000"+"\",\"mood\": \""+"Happy"+"\",\"temperature\": \""+"22"+"\"},{ \"insertion_date\" :\""+"2015-07-31 11:21:04 +0000"+"\",\"mood\": \""+"Sad"+"\",\"temperature\": \""+"22"+"\"}]"
I can access the whole string like this.
var data = text2.dataUsingEncoding(NSASCIIStringEncoding, allowLossyConversion: false)
var localError: NSError?
var json3: AnyObject! = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers, error: &localError)
println(json3)
But now I want to access individual element of this string, like - I want to access the second "mood" key, which has the value "Sad".
How can I access it?
You could use something like this:
var jsonArr: NSArray! = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers, error: &localError) as! NSArray
for dict in jsonArr {
print(dict.objectForKey("mood"))
}
Which will print each of the moods.
Swift's native JSON parsing is a pain due to all the optional unwrapping:
if let days = json3 as? NSArray {
if let secondDay = days[1] as? NSDictionary {
let mood = secondDay["mood"] as! String
print(mood)
}
}
You can use other framework like SwiftyJson for an easier time.
You can also use Swift's native types instead of NSArray and NSDictionary, and cast the result of NSJSONSerialization as an array of dictionaries ([[String:AnyObject]]):
if let json3 = NSJSONSerialization.JSONObjectWithData(data!, options: nil, error: &localError) as? [[String:AnyObject]] {
for dict in json3 {
if let mood = dict["mood"] as? String {
println(mood)
}
}
}
Note also that you don't need to give specific options to NSJSONSerialization, you can pass nil in this case.
If you're absolutely certain that your values will always be of type String, you can also cast the result directly (and avoid later casts when accessing values):
if let json3 = NSJSONSerialization.JSONObjectWithData(data!, options: nil, error: &localError) as? [[String:String]] {
for dict in json3 {
if let mood = dict["mood"] {
println(mood)
}
}
}