I'm pulling a JSON array of dictionaries trying to add them to a class I created and use them for a UITableView. The JSON would look like this:
{
"inventory":[
{
"item":"item 1",
"description":"item 1 description",
"quantityOnHand":"42",
"supplier_id":"1",
"supplierName":"Supplier 1"
},
{
"item":"item 2",
"description":"item 2 description",
"quantityOnHand":"1001",
"supplier_id":"1",
"supplierName":"Supplier 1"
} ...
and so on...
I'm grabbing all this in my viewDidLoad() and trying to add each dictionary to a class (called Inventory) to work with later. Here's where I'm serializing my JSON:
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "my url to json data";
let session = NSURLSession.sharedSession();
let url = NSURL(string: urlString)!;
session.dataTaskWithURL(url) { (data: NSData?, response:NSURLResponse?, error: NSError?) -> Void in
if let responseData = data {
do {
let json = try NSJSONSerialization.JSONObjectWithData(responseData, options: NSJSONReadingOptions.AllowFragments)
print(json) //this prints out the above formatted json
if let dict = json as? Dictionary<String, AnyObject> {
print(dict["inventory"]![0]!["description"]);
print(dict["inventory"]![0]!["item"]);
print(dict["inventory"]![0]!["quantityOnHand"]);
}
} catch {
print("Could not serialize");
}
}
}.resume()
}
I'm able to print out each value using something like print(dict["inventory"]![0]!["description"]); but that seems inefficient.
Do I need a for loop counting the number of dictionaries? Or a for (key, value) loop? The fact that it's a bunch of dictionaries inside of an array named inventory is really throwing me off. If it were JSON returning key:value pairs in a single dictionary I think I could figure it out on my own. I'm sort of stuck on what to do after putting my json["inventory"] into a variable.
First of all cast the JSON serialization to something meaningful,
in this case Dictionary<String, AnyObject>
let json = try NSJSONSerialization.JSONObjectWithData(responseData, options: NSJSONReadingOptions.AllowFragments) as! Dictionary<String, AnyObject>
Then retrieve the array of dictionaries, the JSON string reveals that it contains only String types.
let inventoryArray = dict["inventory"] as! [Dictionary<String, String>]
if inventory is optional, use optional bindings
if let inventoryArray = dict["inventory"] as? [Dictionary<String, String>] { }
Now you can get the items in the array with a simple loop, any type casting is not needed.
for anItem in inventoryArray {
print("description:", anItem["description"])
print("item: ", anItem["item"])
print("quantityOnHand: ", anItem["quantityOnHand"])
}
Related
In a simple test project at Github I am downloading JSON file, parse it into [[String:Any]] and then would like to pass the data structure to Core Data's NSBatchInsertRequest:
func fetchTopModels(language:String) {
guard let url = urls[language] as? URL else { return }
URLSession.shared.dataTaskPublisher(for: url)
.tryMap(handleOutput)
.tryMap { jsonData -> [[String: Any]] in
let json = try? JSONSerialization.jsonObject(with: jsonData, options: [])
guard let jsonDict = json as? [String:Any],
let dataList = jsonDict["data"] as? [[String:Any]]
else { throw URLError(.badServerResponse) }
return dataList
}
// TODO How to set language on each dataList member?
//.map {
//$0.language = language
//}
.sink { completion in
print("fetchTopModels completion=\(completion)")
} receiveValue: { fetchedTops in
PersistenceController.shared.container.performBackgroundTask { backgroundContext in
backgroundContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
backgroundContext.automaticallyMergesChangesFromParent = true
backgroundContext.perform {
let batchInsert = NSBatchInsertRequest(entity: TopEntity.entity(), objects: fetchedTops)
However the [[String:Any]] data structure is missing the language String property on each of its members. It is mandatory on the Core Data entity.
My question is how to iterate through the array and set the language on each member (for example to the value of "en")?
I have searched, but it is difficult to find a good pointer because the search keywords are too common :-)
To update the dictionary you need to create a mutable copy of each and insert the language and also a copy of the array itself. Something like this
.map { array in
array.map { dict -> [String: Any] in
var temp = dict
temp["language"] = language
return temp
}
}
I'm trying to parse a JSON string returned from a new API. The returned JSON string looks like this.
QuerySearchResult":{
"StartAt":"1",
"Count":"40",
"TotalAvailable":"500",
"Items":[
{"TITLE":"OST420 Generation",
"PATH":"http:\\Test.pdf",
"WRITE":"2016-12-12T15:47:42",
"RANK":"32286574",
"SIZE":"145091",
"ISDOCUMENT":"true",
"ID":"18548",
"WPTASK":"Onsite Generation",
"WPDOCTYPE":"Local Operating Procedure",
"WPDOCREFID":"304580",
"WPCONTENTTYPE":"Document"},
{"TITLE":"OST420 Measurement",
"PATH":"http:\Test33.pdf",
.
.
I'm using the code below which accepts the JSON variable but fails when I try to load item. I've tried using Array around Dictionary but it still fails. What declaration do I need to read in Items?
if let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as? Dictionary<String, AnyObject> {
for item in json {
if let dict = item as? Dictionary<String, AnyObject> {
if let items = json["Items"] as? Array<Dictionary<String, AnyObject>> {
for rec in items {
if let title = rec["TITLE"] as? String {
let xx = title
}
}
}
}
}
First of all you are not correctly iterating through Dictionary also instead of looping through Dictionary for accessing single value, try by directly accessing it through subscripting and the proper JSON notation of Dictionary in Swift 3 is [String : Any].
if let json = try? JSONSerialization.jsonObject(with: data!, options: []) as? [String : Any],
let queryDic = json["QuerySearchResult"] as? [String : Any],
let items = queryDic["Items"] as? [[String : Any]] {
for item in items {
if let title = item["TITLE"] as? String {
print(title)
}
}
}
In Swift 3.1 :-
func parseJson(anyObj:AnyObject) {
if let anyObj = anyObj as? Array<Dictionary<String,AnyObject>> {
self.yourArrayName = anyObj.flatMap({yourModelName(json:$0) })
}
}
The Json returns the data into the dictionary and array format, so treat the json data like it, it makes you easier to understand.
Data in braces '{' and '}' is dictionary.
and Data in braces '[' or ']' is array.
Now start parsing the json data by the dictionary and array properties it exactly works. Or for confirmation print your json data.
Use SwiftyJSON : which can simply and easily parse the JSON more than your code.
So, how do we parse using that?
First get your response as Data()
let jsonData = JSON(data: results!)
Then here how we parse that JSON.
import Foundation
import SwiftyJSON
enum JSONParseError : ErrorType {
case UnknownField
case EmptyJSON
}
extension JSONParseError : CustomStringConvertible {
var description: String {
switch self {
case .UnknownField:
return "Error when parsing json because there is no field"
case .EmptyJSON:
return "Error when parsing empty json"
}
}
}
guard let querySearchResult : [String : JSON] = jsonData["QuerySearchResult"].dictionary else{
throw JSONParseError.UnknownField
}
guard let startAt : String = querySearchResult["StartAt"].string else{
throw JSONParseError.UnknownField
}
guard let count : String = querySearchResult["Count"].string else{
throw JSONParseError.UnknownField
}
guard let totalAvailable : String = querySearchResult["TotalAvailable"].string else{
throw JSONParseError.UnknownField
}
guard let items : [JSON] = querySearchResult["Items"].array else{
throw JSONParseError.UnknownField
}
if items.count > 0 {
for i in 0 ..< items.count{
guard let title = items[i]["TITLE"].string else{
throw JSONParseError.UnknownField
}.... //So On
}
}else{
throw JSONParseError.EmptyJSON
}
I created an API that has a JSON response in the following format:
[{"name":"xxx","direct_link":"http:\/\/domain.com\/images\/xxx.png","image":"http:\/\/domain.com\/images\/xxx.png"},{"name":"yyy","direct_link":"http:\/\/domain.com\/images\/yyy.png","image":"http:\/\/domain.com\/images\/yyy.png"}]
Notice how the JSON response has no array title.
My Swift code looks like this:
do {
//converting resonse to NSDictionary
var teamJSON: NSDictionary!
teamJSON = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as? NSDictionary
//getting the JSON array teams from the response
let teams: NSArray = teamJSON["teams"] as! NSArray
//looping through all the json objects in the array teams
for i in 0 ..< teams.count{
//getting the data at each index
let teamId:Int = teams[i]["name"] as! String!
let teamName:String = teams[i]["direct_link"] as! String!
let teamMember:Int = teams[i]["image"] as! Int!
//displaying the data
print("name -> ", teamId)
print("direct_link -> ", teamName)
print("image -> ", teamMember)
print("===================")
print("")
}
Notice how the array is looking for a title "teams". How can I ensure the JSON is properly parsed and displays the 3 values I need for the JSON response? I'm new to app coding and have a web background, still trying to wrap my head around this.
As it stands when I try to build and run I get the following error: fatal error unexpectedly found nil while unwrapping an optional value
Try this:
do {
guard let teams = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as? NSArray else {
//Doesn't exist, or isn't an NSArray
return
}
for team in teams {
//getting the data at each index
let teamId = team["name"] as! String
let teamName = team["direct_link"] as! String
let teamMember = team["image"] as! Int
//displaying the data
print("name -> ", teamId)
print("direct_link -> ", teamName)
print("image -> ", teamMember)
print("===================")
print()
}
}
//...
Some notes:
Don't cast to implicitly unwrapped optionals (e.g. String!)
Don't add unnecessary type annotations
Use guard let to enforce preconditions (e.g. the JSON isn't nil, and is castable to NSArray).
Prefer iterating over elements of an Array (for team in teams) over iterating a range (for i in 0..<teams.count)
If you need only the indices, use for i in teams.indices
If you need the indices and elements, use for (index, team) in teams.enumerate()
The problem is this line:
teamJSON = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as? NSDictionary
because you are trying to cast a JSON array to a dictionary.
Just use
teamJSON = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as? NSArray
And then you also won't need to do this:
//getting the JSON array teams from the response
let teams: NSArray = teamJSON["teams"] as! NSArray
Because teamJSON is already the array.
I think the key issue you have to be aware of is that NSJSONSerialization.JSONObjectWithData() can return either an NSDictionary OR an NSArray, depending on whether the root level data in the JSON is an array or a dictionary.
ISSUE:
Your NSJSONSerialization.JSONObjectWithData would return an NSArray and not NSDictionary
SOLUTION:
Update code as below:
var teamJSON = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as? NSArray
//getting the JSON array teams from the response
let teams: NSArray = teamJSON
ALSO
Don't force cast values using !
Always cast it as optional using ?
when decoding JSON from webservice(API) i get error :
Could not cast value of type '__NSDictionaryM' (0x1037ad8a8) to 'NSArray' (0x1037ad470).
My Code :
var kGetURL = "http://bitnami.local/cscart_demo/api/users"
//var kGetURL = "http://localhost/fendy/getjson.php"
var json : Array<AnyObject> = []
override func viewDidLoad() {
super.viewDidLoad()
start()
}
func getData(data : NSData){
//error at this line :
json = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as! Array<AnyObject>
//error
tableView.reloadData()
}
func start(){
var url : NSURL = NSURL(string: kGetURL)!
var data : NSData = NSData(contentsOfURL: url)!
getData(data)
}
if i change url to http://localhost/fendy/getjson.php, its working so nice.
i get error if my url is http://bitnami.local/cscart_demo/api/users
Response from webservice http://localhost/fendy/getjson.php :
[{"id":"17","Name":"KFC","Message":"awesome"},
{"id":"18","Name":"McDonald","Message":"good"},
{"id":"23","Name":"Burger King","Message":"tasty"},
{"id":"38","Name":"Pizza hut","Message":"yummy"},
{"id":"39","Name":"Steak","Message":"very Delicious"}]
Response from webservice http://bitnami.local/cscart_demo/api/users :
{"users":
[{"user_id":"4","user_login":"user_4","is_root":"N","timestamp":"1441608048","user_type":"C","status":"A","firstname":"","lastname":"","email":"fendy.w#mvig.net","company":"","company_id":"1","company_name":"Simtech"},
{"user_id":"3","user_login":"customer","is_root":"N","timestamp":"1441604240","user_type":"C","status":"A","firstname":"Customer","lastname":"Customer","email":"customer#example.com","company":"Simtech","company_id":"1","company_name":"Simtech"},
{"user_id":"1","user_login":"admin","is_root":"Y","timestamp":"1441604240","user_type":"A","status":"A","firstname":"John","lastname":"Doe","email":"robby#mvig.net","company":"Your company","company_id":"0","company_name":null}],
"params":{"page":1,"items_per_page":"10","sort_order":"asc","sort_by":"name","sort_order_rev":"desc","total_items":"3"}}
i think it's Style is same, but why not working with url http://bitnami.local/cscart_demo/api/users . anyone can help?
The bitnami response starts with a { and it is therefore a JSON object, which corresponds to an NSDictionary. The other one starts with [ which indicates an array.
You need to change the type of json to Dictionary<String, AnyObject>, and deserialize as follows:
json = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as! Dictionary<String, AnyObject>
Your method is casting JSON result to an array. It works fine with the URL that returns an array represented as JSON, but it does not work with the URL that returns a dictionary, not an array, represented as JSON.
Although the "style" of returned values looks the same, the second one is a one-item dictionary. What you probably want is to extract the "users" element from it, which is an array.
If you do not know which of the two URLs you are getting, you could try both styles with as? cast instead of as!:
let tmp : AnyObject! = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil)
if let arr = tmp as? Array<AnyObject> {
json = arr
} else if dict = tmp as? [String: AnyObject] {
json = dict["users"] as! Array<AnyObject>
} else {
// Handle an error: the input was unexpected
}
tableView.reloadData()
try cache for Serialization
do {
if let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String : Any] { // as? data type
if let otherDict = json["dataKey"] as? [String : Any] {
//do something
}
}
} catch {
// can't json serialization
}
I tried to get the value of "seed" from json response. But i am getting nil.
Json Response:
{
"response": {
"params": {
"rows": "20",
"defType": "abc",
"seed": "381786611"
}
}
}
Swift Parsing:
if let responseHeader:AnyObject = object?["response"] as? NSDictionary {
if let t = (responseHeader["params"] as? NSDictionary){
let t1 = t["seed"] as? String
println("result is \(t1)") // This returns nil
}
}
Json Parsing
func processJsonToDictionary(object:AnyObject?) -> AnyObject?{
if object != nil {
if let data: AnyObject = object {
var parseError: NSError?
var jsonResult = NSJSONSerialization.JSONObjectWithData(object as NSData!, options: NSJSONReadingOptions.MutableContainers, error: &parseError) as? NSDictionary
if(parseError != nil){
return parseError
}
else{
return jsonResult
}
}
}
return nil
}
I am not able to get the value of t1. it always returns nil.
How can i get the value.
Also, I put a breakpoint and tried to print the value of t1. But the Xcode Keeps crashing. Why?
I think the major problem here is only accessing a JSON object in swift.
var error: NSError?
let jsonDict = NSJSONSerialization.JSONObjectWithData(jsonData, options: nil, error: &error) as NSDictionary
let resp = jsonDict["response"] as? NSDictionary
let params = resp?["params"]?["seed"]
let seed = params!! as NSString
This is just to show you how a JSON object is accessed in swift. You can ofcourse change it according to your needs to remove unwanted Optional Chaining.
For easy JSON manipulation in Swift you could try this little library. It seems pretty easy and you could do this:
var dictionary: [String: AnyObject]!
if let json = NKJSON.parse(yourNSDataObject) {
dictionary <> json[NKJSON.rootKey]
}
First confirm that the response which you are getting is in json format or in string format.
For json parsing you can use swifty json pod