Swift & json : Array of objects - json

I have a json file of the format :
{
"houses": [
{
"description": "House 1",
"surface": "500"
},
{
"description": "House 2",
"surface": "200"
}
]
}
I would like to fetch these data and be able to manipulate the inner data only (without the word houses).
Here is a part of my request where I get stuck
let jsonResult = try JSONSerialization.jsonObject(with: data, options: .mutableContainers)
if let result = jsonResult as? [String : Any] {
var housesArray: [String:Any] = ["":""]
result = ["houses":housesArray]
print(housesArray)
}
but my housesArray is always empty. How can proceed please ? Thank you

You are (kind of) writing to the result but you need to read from the result
if let jsonResult = try JSONSerialization.jsonObject(with: data) as? [String:Any],
let housesArray = jsonResult["houses"] as? [[String:String]] {
print(housesArray)
}

QuickType will write the decoder for you:
struct Houses: Codable {
var houses: [House]
}
struct House: Codable {
var houseDescription, surface: String
enum CodingKeys: String, CodingKey {
case houseDescription = "description"
case surface
}
}
Then decode and extract .houses:
let houses = try JSONDecoder().decode(Houses.self, from: jsonData).houses

This will be
if let result = jsonResult as? [String : Any] {
if let housesArray = result["houses"] as? [[String:Any]] {
print(housesArray)
}
}
Now check.

Related

parsing YouTube json with JSONSerialization

I have trouble parsing json data from YouTube api with JSONSerialization. when I try to fetch I return error. this is my code
this is json data I want to parse, I want to get video id, url, title, and desctiption
{
"kind": "youtube#searchListResponse",
"etag": "\"j6xRRd8dTPVVptg711_CSPADRfg/mBDPbwkuU2lLUxWHYPI1X54CUwQ\"",
"nextPageToken": "CAUQAA",
"regionCode": "ID",
"pageInfo": {
"totalResults": 3552,
"resultsPerPage": 5
},
"items": [
{
"kind": "youtube#searchResult",
"etag": "\"j6xRRd8dTPVVptg711_CSPADRfg/73cXngXOrGm_Bt7McNY945A6koc\"",
"id": {
"kind": "youtube#video",
"videoId": "-0ZZzOuuV3c"
},
"snippet": {
"publishedAt": "2018-09-20T08:00:01.000Z",
"channelId": "UCjHoMXZXAIx_QHgk9qsAJ-Q",
"title": "HADIST-HADIST PALSU TAPI POPULER - Ustadz Adi Hidayat LC MA",
"description": "\"Kebersihan sebagian dari iman\". Sering dogn mendengar ucapan ini. Sebagian orang mengatakan ini hadist dari Rasulullah. Tapi taukah kamu, bahwa ini ...",
"thumbnails": {
"default": {
"url": "https://i.ytimg.com/vi/-0ZZzOuuV3c/default.jpg",
"width": 120,
"height": 90
},
"medium": {
"url": "https://i.ytimg.com/vi/-0ZZzOuuV3c/mqdefault.jpg",
"width": 320,
"height": 180
},
"high": {
"url": "https://i.ytimg.com/vi/-0ZZzOuuV3c/hqdefault.jpg",
"width": 480,
"height": 360
}
},
"channelTitle": "Audio Dakwah",
"liveBroadcastContent": "none"
}
}
This is my code to parse json I create it in youtubeAPI struct, when I try to run it invalidJSONData
static func videos(fromJSON data: Data) -> VideoResults {
do {
let jsonObject = try JSONSerialization.jsonObject(with: data, options: [])
guard
let jsonDictionary = jsonObject as? [AnyHashable: Any],
let itemsArray = jsonDictionary["items"] as? [[String: Any]]
else {
return .failure(YoutubeError.invalidJSONData)
}
var finalItems = [Video]()
for itemJSON in itemsArray {
if let item = video(fromJSON: itemJSON) {
finalItems.append(item)
}
}
if finalItems.isEmpty && !itemsArray.isEmpty {
return .failure(YoutubeError.invalidJSONData)
}
return .success(finalItems)
} catch let error {
return .failure(error)
}
}
private static func video(fromJSON json: [String: Any]) ->Video? {
guard
let videoID = json["videoID"] as? String,
let title = json["title"] as? String,
let description = json["description"] as? String,
let stringURL = json["url"] as? String,
let url = URL(string: stringURL)
else {
return nil
}
return Video(videoID: videoID, title: title, description: description, url: url)
}
with the question I have post. finally I have the answer for my problem. hopefully this will help other developer who want to parse json with JSONSerialization, with such a complex data like YouTube api or other api. so this is my answer.
Since items is an array and inside item have another nested data such as snippet, so I need to iterate snippet to get the data. This is the code.
do {
let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
var finalItems = [Video]()
if let items = jsonObject?["items"] as? Array<Dictionary<String, Any>> {
for item in items {
if
let id = item["id"] as? [String: String],
let snippet = item["snippet"] as? [String: Any] {
if
let videoID = id["videoId"],
let description = snippet["description"] as? String,
let title = snippet["title"] as? String,
let thumbnails = snippet["thumbnails"] as? [String: Any],
let medium = thumbnails["medium"] as? [String: Any],
let urlString = medium["url"] as? String,
let url = URL(string: urlString) {
finalItems.append(Video(videoID: videoID, title: title, description: description, url: url))
}
}
}
}
print(finalItems.count)
if finalItems.isEmpty {
return .failure(YoutubeError.invalidJSONData)
}
return .success(finalItems)
} catch let error {
return .failure(error)
}
And the the next step is to clean up the code for parsing that iterate the item, and put it in extension in Video Model, I was found a good article from apple developer website to clean up the code so the code not bloated in my videos function. if you are curious check out this link https://developer.apple.com/swift/blog/?id=37. so the extension for video model is like this.
extension Video {
init(dict: [String: Any]) throws {
guard
let id = dict["id"] as? [String: String],
let videoID = id["videoId"]
else {
throw SerializationError.missing("id")
}
guard
let snippet = dict["snippet"] as? [String: Any],
let description = snippet["description"] as? String,
let title = snippet["title"] as? String,
let thumbnails = snippet["thumbnails"] as? [String: Any],
let medium = thumbnails["medium"] as? [String: Any],
let urlString = medium["url"] as? String,
let url = URL(string: urlString) else { throw SerializationError.missing("snippet") }
self.videoID = videoID
self.title = title
self.description = description
self.url = url
}
}
and then update the videos function, and add do catch block when you try to append since the extension have potential error to parse json data. this is the update method
static func videos(fromJSON data: Data) -> VideoResults {
do {
let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
var finalItems = [Video]()
if let items = jsonObject?["items"] as? Array<Dictionary<String, Any>> {
for item in items {
do {
try finalItems.append(Video(dict: item))
} catch let error {
print("Failed to append video: \(error)")
}
}
}
print(finalItems.count)
if finalItems.isEmpty {
return .failure(YoutubeError.invalidJSONData)
}
return .success(finalItems)
} catch let error {
return .failure(error)
}
}
and voila the code is safe now, why I not use JSONDecoder? since a lot of people recommended to use JSONDecoder. the reason is, this is my personal reason by the way other might not agree with me. I know how to use JSONDecoder but the thing is i don't want to create a lot struct to decode the json data, since with this JSONSerialization doesn't have to create struct it help me reduce a file and step up my learning curve. and hey even a library like Alamofire have a choice to parse using JSONSerialization. it's up to you now if you want to use JSONDecoder or JSONSerialization I hope this help :).

The JSON output does not contain a key: "value"

I have a JSON parsing issue with my Swift code below. The error I am getting says that my JSON output does not contain a key value.
My code:
Alamofire.request(url, method: .get, headers: headers).responseJSON { (response) -> Void in
let jsonValue = response.result.value as! NSDictionary
if let bpArray = jsonValue["value"] as? [NSDictionary]{
for results in bpArray {...}
Issue:
This conversion doesnt work: if let bpArray = jsonValue["value"] as? [NSDictionary]
My JSON Structure:
{
d: {
results: [
{
__metadata: {},
Key: "AFBWhULFHtKU4j4FhWCmKg==",
ParentKey: "AAAAAAAAAAAAAAAAAAAAAA==",
RootKey: "AFBWhULFHtKU4j4FhWCmKg==",
Partner: "MM-CARR-01",
Type: "2",
Description: "MM Demo Carrier Created for Single Stop / MA",
FrieghtOrder: {}
},
...
Assuming you want to access the results key so try like this:-
if let bpArray = jsonValue["results"] as? [String: AnyObject]{
//yourcode
}
Well your json structure haven't got any key named as value and that's why it's giving an error.
In order to get results array, you first need to get the object in which they are nested and for example 'g' in your case:
if let data = jsonValue["d"] as? [NSDictionary]{
if let resultsArray = data["results"] as? NSArray {
//your code
}
}
Please Use Swifty Json Pod And Try this code
pod 'SwiftyJSON'
In Your file where you get response
import SwiftyJSON
Then After Use This Code
switch response.result {
case .success(let JSON2):
print("Success with JSON: \(JSON2)")
// print("Request failed with error: \(response.response?.statusCode)")
if let response = JSON2 as? NSMutableDictionary
{
}
else if let response = JSON2 as? NSDictionary
{
if let data = response?.object(forKey: "d") as? NSDictionary
{
if let resultsArray = data?.object(forKey: "results") as? NSArray
{
}
}
}
else if JSON2 is NSArray
{
let swjson = JSON(response.result.value!)
print(swjson)
// callback(swjson,nil)
var myMutableDictionary = [AnyHashable: Any]()
myMutableDictionary["myArray"] = swjson
callback(myMutableDictionary as NSDictionary?,nil)
print("accc")
}
else if ((JSON2 as? String) != nil)
{
let userDic : [String : String] = ["quatid":JSON2 as! String]
print(userDic)
}
break
case .failure(let error):
print("Request failed with error: \(error)")
}
Remember it's better we not use NSArray & NSDictionary like things in SWIFT, SWIFT it self providing let, var keyword for various data type.
First you can create your model where you can save the data.
MyModel.swift
class MyModel: NSObject {
var ParentKey : Int?
init(jsonObject:[String:Any]) {
ParentKey = jsonObject["ParentKey"] as? String ?? ""
}
}
Viewcontroller.Swift
var myModel : [MyModel] = []
if let responseData = response["d"] as? [String:Any]{
if let dataObject = responseData["results"] as? [[String:Any]]{
self.myModel = dataObject.map({return MyModel(jsonObject: $0)})//for assigning data into model
}
}
As most people said, I was wrong with "value". Hussain's answer to using [String:AnyObject] Helped.
I am not sure if the below is neat to achieve, but it did the magic. App loaded as expected. Code expert below:
Solution:
if let bpData = jsonValue["d"] as? [String: AnyObject]{
for results in bpData {
let arrayInterim = results.value as? NSArray
for i in 0 ..< arrayInterim!.count {

Swift3 JSON using Alamofire

I'm new to Alamofire and Swift.
Now, I'm trying to convert JSON from the API, but I don't know how to convert it.
API like this :
[
{
"myid": "10303210302003",
"mySubid": "10303210302003",
"area_pkid": "3"
},
{
"myid": "10303210302004",
"mySubid": "10303210302004",
"area_pkid": "4"
},
....]
I'm so confused about "[" and "]" , I don't know how to convert it and get myid.
Here is my code
Alamofire.request(MyURL, method: .get).responseJSON { (response) in
guard let totalJSON = response.result.value as? [String : Any] else { return } // My code is just return !! WHY??
}
It's simply
guard let totalJSON = response.result.value as? [[String : Any]] else { return }
for item in totalJSON {
print(item["myid"] as? String ?? "n/a")
}
since the enclosing object is an array ([])
You can even cast the array to [[String:String]] if all values are String
Yep, [] is an array and {} represents as Dictionary. So to get myid just do:
guard let totalJSON = response.result.value as? [Any] else { return }
if let i = a["myID"] as? String, let a = totalJSON[0] as? [String: Any]
{
print(i)
}

TableView Json Swift

I currently developing an app which list user object by making an request to a webservice, which send a response in JSON in this format :
{
"0":
{
"id":"30",
"title":"galaxys6",
"price":"550",
"description":"neuf",
"addedDate":"2015-07-16 15:04:24",
"user_id":"2",
"user_name":"",
"user_zipCode":"69003",
"category_id":"1",
"category_label":"PHONE",
"subcategory_id":"1",
"subcategory_label":"Phone",
"picture":"",
"bdd":{},
"picture_url":"http:\/\/jdl-barreme-orange.dyndns.org\/WEBSERVICE\/pictures\/galaxy s6.JPG"
},
"1":
{
"id":"31",
"title":"iphone4",
"price":"570",
"description":"neuf",
"addedDate":"2015-07-16 15:14:54",
"user_id":"2",
"user_name":"",
"user_zipCode":"69003",
"category_id":"1",
"category_label":"PHONE",
"subcategory_id":"1",
"subcategory_label":"Phone",
"picture":"",
"bdd":{},
"picture_url":"http:\/\/jdl-barreme-orange.dyndns.org\/WEBSERVICE\/pictures\/iphone.JPG"
},
}
For each object my webservice create a dictionary (0;1;2;3....)
I search a method to retrieve for each dictionary the value title and price and put them in a tableView.
Code I used (tableviewcontroller) :
if let jsonData:NSDictionary = NSJSONSerialization.JSONObjectWithData(urlData!, options:NSJSONReadingOptions.MutableContainers , error: &error) as? NSDictionary{
// 4
if let resp = jsonData["1"] as? [NSDictionary] {
NSLog("%#", resp)
// 5
for item in resp {
repositories.append(Repository(jsonData: item))
}
repository controller :
class Repository {
var name: String?
var description: String?
var html_url: String?
init(jsonData: NSDictionary) {
self.name = jsonData["id"] as? String
self.description = jsonData["description"] as? String
self.html_url = jsonData["title"] as? String
}
}
But it doesn't work, I put a breakpoint, and xcode stop to interpret here :
if let resp = jsonData["1"] as? [NSDictionary] {
NSLog("%#", resp)
What am I doing wrong?
Thank you.
Here's how to get the title and price for your JSON:
if let json = NSJSONSerialization.JSONObjectWithData(urlData!, options: nil, error: nil) as? [String:AnyObject] {
for (_, value) in json {
if let dict = value as? [String:AnyObject] {
if let title = dict["title"] as? String {
println(title)
}
if let price = dict["price"] as? String {
println(price)
}
}
}
}
This can also be used to init your Repository classes if you want:
class Repository {
var name: String?
var description: String?
var html_url: String?
init(jsonData: [String:AnyObject]) {
self.name = jsonData["id"] as? String
self.description = jsonData["description"] as? String
self.html_url = jsonData["title"] as? String
}
}
var repos = [Repository]()
if let json = NSJSONSerialization.JSONObjectWithData(urlData!, options: nil, error: nil) as? [String:AnyObject] {
for (_, value) in json {
if let dict = value as? [String:AnyObject] {
let repo = Repository(jsonData: dict)
repos.append(repo)
}
}
}
for repo in repos {
println(repo.name)
println(repo.description)
println(repo.html_url)
}
In the loop I'm ignoring the key: for (_, value) in json but you can use it if needed of course:
for (key, value) in json {
println(key) // "1", "2", ...
// ...
}
UPDATE:
Following your comment asking how to use this answer if your data format is different: if you want an array of dictionaries, change the typecast of the NSJSONSerialization result to reflect that: [[String:AnyObject]]. Next you can iterate over your array to get each dictionary properties:
if let jsonArray = NSJSONSerialization.JSONObjectWithData(urlData!, options: nil, error: nil) as? [[String:AnyObject]] {
for dict in jsonArray {
if let title = dict["title"] as? String {
println(title)
}
}
}
You are making a mistake here
if let resp = jsonData["1"] as? [NSDictionary]
This should be a NSDictionary not [NSDictionary], (which would be an array of dictionaries).
Also this conditional block
if let reposArray = jsonData["items"] as? [NSDictionary]
will never be executed because jsonData does not contain a key "items".
I guess it is the [NSDictionary]
if let resp = jsonData["1"] as? [NSDictionary]
[NSDictionary] is array of NSDictionary same as Array<NSDictionary>
just remove the brackets [] and change to
if let resp = jsonData["1"] as? NSDictionary

Parse json in Swift, AnyObject type

I'm trying to parse a json but I have some difficulties with the data types and notably the AnyObject type + downcasting.
Let's consider the following json (it's an extract of a full json).
{ "weather":
[
{
"id":804,
"main":"Clouds",
"description":"overcast clouds",
"icon":"04d"
}
],
}
To me, the json can be described as follow :
- json: Dictionary of type [String: AnyObject] (or NSDictionary, so = [NSObject, AnyObject] in Xcode 6 b3)
- "weather": Array of type [AnyObject] (or NSArray)
- Dictionary of type [String: AnyObject] (or NSDictionary, so = [NSObject, AnyObject] in Xcode 6 b3)
My json is of type AnyObject! (I use JSONObjectWithData to get the JSON from a URL).
I then want to access the weather Dictionary. Here is the code I wrote.
var localError: NSError?
var json: AnyObject! = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &localError)
if let dict = json as? [String: AnyObject] {
if let weatherDictionary = dict["weather"] as? [AnyObject] {
// Do stuff with the weatherDictionary
}
}
Here is the error I got
Playground execution failed: error: <EXPR>:28:56: error: '[AnyObject]' is not a subtype of '(String, AnyObject)'
if let weatherDictionary = dict["weather"] as? [AnyObject] {
I don't understand why dict["weather"] is compared to a subtype of (String, AnyObject) and not AnyObject.
I declared my dictionary as [String: AnyObject], so I i access a value using the String key, I should have an AnyObject, no ?
If I use NSDictionary instead of [String: AnyObject], it works.
If I use NSArray instead of [AnyObject], it works.
- The Xcode 6 beta 3 release notes tell that "NSDictionary* is now imported from Objective-C APIs as [NSObject : AnyObject].".
- And the Swift book: "When you bridge from an NSArray object to a Swift array, the resulting array is of type [AnyObject]."
EDIT
I forgot to force unwrapping the dict["weather"]!.
if let dict = json as? [String: AnyObject] {
println(dict)
if let weatherDictionary = dict["weather"]! as? [AnyObject] {
println("\nWeather dictionary:\n\n\(weatherDictionary)")
if let descriptionString = weatherDictionary[0]["description"]! as? String {
println("\nDescription of the weather is: \(descriptionString)")
}
}
}
Note that we should double check for the existence of the first Optional.
if let dict = json as? [String: AnyObject] {
for key in ["weather", "traffic"] {
if let dictValue = dict[key] {
if let subArray = dictValue as? [AnyObject] {
println(subArray[0])
}
} else {
println("Key '\(key)' not found")
}
}
}
This works fine for me in the playground and in the terminal using env xcrun swift
UPDATED FOR SWIFT 4 AND CODABLE
Here is a Swift 4 example using the Codable protocol.
var jsonStr = "{\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04d\"}],}"
struct Weather: Codable {
let id: Int
let main: String
let description: String
let icon: String
}
struct Result: Codable {
let weather: [Weather]
}
do {
let weather = try JSONDecoder().decode(Result.self, from: jsonStr.data(using: .utf8)!)
print(weather)
}
catch {
print(error)
}
UPDATED FOR SWIFT 3.0
I have updated the code for Swift 3 and also showed how to wrap the parsed JSON into objects. Thanks for all the up votes!
import Foundation
struct Weather {
let id: Int
let main: String
let description: String
let icon: String
}
extension Weather {
init?(json: [String: Any]) {
guard
let id = json["id"] as? Int,
let main = json["main"] as? String,
let description = json["description"] as? String,
let icon = json["icon"] as? String
else { return nil }
self.id = id
self.main = main
self.description = description
self.icon = icon
}
}
var jsonStr = "{\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04d\"}],}"
enum JSONParseError: Error {
case notADictionary
case missingWeatherObjects
}
var data = jsonStr.data(using: String.Encoding.ascii, allowLossyConversion: false)
do {
var json = try JSONSerialization.jsonObject(with: data!, options: [])
guard let dict = json as? [String: Any] else { throw JSONParseError.notADictionary }
guard let weatherJSON = dict["weather"] as? [[String: Any]] else { throw JSONParseError.missingWeatherObjects }
let weather = weatherJSON.flatMap(Weather.init)
print(weather)
}
catch {
print(error)
}
-- Previous Answer --
import Foundation
var jsonStr = "{\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04d\"}],}"
var data = jsonStr.dataUsingEncoding(NSASCIIStringEncoding, allowLossyConversion: false)
var localError: NSError?
var json: AnyObject! = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers, error: &localError)
if let dict = json as? [String: AnyObject] {
if let weather = dict["weather"] as? [AnyObject] {
for dict2 in weather {
let id = dict2["id"]
let main = dict2["main"]
let description = dict2["description"]
println(id)
println(main)
println(description)
}
}
}
Since I'm still getting up-votes for this answer, I figured I would revisit it for Swift 2.0:
import Foundation
var jsonStr = "{\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04d\"}],}"
var data = jsonStr.dataUsingEncoding(NSASCIIStringEncoding, allowLossyConversion: false)
do {
var json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)
if let dict = json as? [String: AnyObject] {
if let weather = dict["weather"] as? [AnyObject] {
for dict2 in weather {
let id = dict2["id"] as? Int
let main = dict2["main"] as? String
let description = dict2["description"] as? String
print(id)
print(main)
print(description)
}
}
}
}
catch {
print(error)
}
The biggest difference is that the variable json is no longer an optional type and the do/try/catch syntax. I also went ahead and typed id, main, and description.
Try:
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 // ""
Using my library (https://github.com/isair/JSONHelper) you can do this with your json variable of type AnyObject:
var weathers = [Weather]() // If deserialization fails, JSONHelper just keeps the old value in a non-optional variable. This lets you assign default values like this.
if let jsonDictionary = json as? JSONDictionary { // JSONDictionary is an alias for [String: AnyObject]
weathers <-- jsonDictionary["weather"]
}
Had your array not been under the key "weather", your code would have been just this:
var weathers = [Weather]()
weathers <-- json
Or if you have a json string in your hands you can just pass it as well, instead of creating a JSON dictionary from the string first. The only setup you need to do is writing a Weather class or struct:
struct Weather: Deserializable {
var id: String?
var name: String?
var description: String?
var icon: String?
init(data: [String: AnyObject]) {
id <-- data["id"]
name <-- data["name"]
description <-- data["description"]
icon <-- data["icon"]
}
}