Decoding JSON Swift 4 - Nested Objects/Arrays - json

Can anyone see what I'm missing? I can't decode anything past results. Nothing will print under results. I've reviewed several other posts that are relevant to JSON/Swift but still don't understand what I'm doing wrong. This is my JSON:
{
"results": [
{
"user.ldap.principal": "OHWIL3336IPM101",
"common.os_version": "11.4",
"common.wifi_mac_address": "100caef1001d",
"common.status": "ACTIVE",
"common.creation_date": "2018-17-05T16:42:49.000Z",
"ios.iPhone UDID": "a8a7a2e52359353dfbacf026a4fada9ew1cb4c10",
"user.ldap.user_attributes.custom1": [
"3336"
],
"common.SerialNumber": "F9FWEF74GHMN",
"common.uuid": "01cd1ed3-b3af-48c0-8499-654c0a9ab996"
}
],
"totalCount": 1,
"resultCount": 1,
"searchTimeMillis": 1,
"currentServerTimeMilliseconds": 1531558334959,
"hasMore": false
}
Here is what I have currently.
struct DeviceData: Codable {
let results: [Result]
let totalCount, resultCount, searchTimeMillis, currentServerTimeMilliseconds: Int
let hasMore: Bool
}
struct Result: Codable {
let commonOSVersion, commonStatus, commonImei, commonCreationDate: String?
let iosDeviceName, commonUUID, userLDAPPrincipal, commonWifiMACAddress: String?
let iosIPhoneUDID: String?
let userLDAPUserAttributesCustom1: [String]?
let commonSerialNumber: String?
let userLDAPGroupsName: [String]?
let iosIPhoneICCID: String?
enum CodingKeys: String, CodingKey {
case commonOSVersion = "common.os_version"
case commonStatus = "common.status"
case commonImei = "common.imei"
case commonCreationDate = "common.creation_date"
case iosDeviceName = "ios.DeviceName"
case commonUUID = "common.uuid"
case userLDAPPrincipal = "user.ldap.principal"
case commonWifiMACAddress = "common.wifi_mac_address"
case iosIPhoneUDID = "ios.iPhone UDID"
case userLDAPUserAttributesCustom1 = "user.ldap.user_attributes.custom1"
case commonSerialNumber = "common.SerialNumber"
case userLDAPGroupsName = "user.ldap.groups.name"
case iosIPhoneICCID = "ios.iPhone ICCID"
}
}
Trying to decode:
let decoder = JSONDecoder()
guard let data = data else {return}
do {
let json = try decoder.decode(DeviceData.self, from: data)
dump(json)
print(json.commonImei) //Does not print - Does not auto-populate - Error Here
}
catch let jsonError {
print("JSON Failed to Decode: ", jsonError)
}
Error:
Value of type 'DeviceData' has no member 'commonImei'
The json will print to the console in full but if I try to print any fields within Result (results) the values don't auto populate and I receive an error. Am I missing something with decoding?

You need do-catch
do {
let decoder = JSONDecoder()
let json = try decoder.decode(DeviceData.self, from: data)
dump(json)
print(json.results[0].commonImei)
}
catch {
print(error)
}
//
The json represents an object of the struct DeviceData which doesn't contain commonImei directly , but it has an array results where all it's elemnts contain that key

Related

Swift Data Model from JSON Response

I am running into an issue building the correct data model for the following JSON response.
{
"resources": [
{
"courseid": 4803,
"color": "Blue",
"teeboxtype": "Championship",
"slope": 121,
"rating": 71.4
},
{
"courseid": 4803,
"color": "White",
"teeboxtype": "Men's",
"slope": 120,
"rating": 69.6
},
{
"courseid": 4803,
"color": "Red",
"teeboxtype": "Women's",
"slope": 118,
"rating": 71.2
}
]
}
Here is the current model. No matter what I do I can't seem to get the model populated. Here is also my URL session retrieving the data. I am new to Swift and SwiftUI so please be gentle. I am getting data back however I am missing something.
import Foundation
struct RatingsResources: Codable {
let golfcourserating : [GolfCourseRating]?
}
struct GolfCourseRating: Codable {
let id: UUID = UUID()
let courseID: Int?
let teeColor: String?
let teeboxtype: String?
let teeslope: Double?
let teerating: Double?
enum CodingKeysRatings: String, CodingKey {
case courseID = "courseid"
case teeColor = "color"
case teeboxtype
case teeslope = "slope"
case teerating = "rating"
}
}
func getCoureRating(courseID: String?) {
let semaphore = DispatchSemaphore (value: 0)
print("GETTING COURSE TEE RATINGS..........")
let urlString: String = "https://api.golfbert.com/v1/courses/\(courseID ?? "4800")/teeboxes"
print ("API STRING: \(urlString) ")
let url = URLComponents(string: urlString)!
let request = URLRequest(url: url.url!).signed
let task = URLSession.shared.dataTask(with: request) { data, response, error in
let decoder = JSONDecoder()
guard let data = data else {
print(String(describing: error))
semaphore.signal()
return
}
if let response = try? JSONDecoder().decode([RatingsResources].self, from: data) {
DispatchQueue.main.async {
self.ratingresources = response
}
return
}
print("*******Data String***********")
print(String(data: data, encoding: .utf8)!)
print("***************************")
let ratingsData: RatingsResources = try! decoder.decode(RatingsResources.self, from: data)
print("Resources count \(ratingsData.golfcourserating?.count)")
semaphore.signal()
}
task.resume()
semaphore.wait()
} //: END OF GET COURSE SCORECARD
First of all, never use try? while decoding your JSON. This will hide all errors from you. Use try and an appropriate do/catch block. In the catch block at least print the error.
Looking at your model there seem to be three issues here.
You donĀ“t have an array of RatingsResources in your array. It is just a single instance.
let response = try JSONDecoder().decode(RatingsResources.self, from: data)
RatingsResources is not implemented correct.
let golfcourserating : [GolfCourseRating]?
should be:
let resources: [GolfCourseRating]?
Your coding keys are implemented wrong instead of:
enum CodingKeysRatings: String, CodingKey {
it should read:
enum CodingKeys: String, CodingKey {
You should add enum CodingKey with resources at struct RatingsResources
And decode:
if let response = try? JSONDecoder().decode(RatingsResources.self, from: data) {
// Your response handler
}

How to read local JSON file and output JSON in swift?

import Foundation
class ReadLocalJSON {
static func readJSONFromFile(fileName: String) -> JSON
{
var json: JSON
if let path = Bundle.main.path(forResource: fileName, ofType: "json") {
do {
let fileUrl = URL(fileURLWithPath: path)
let data = try Data(contentsOf: fileUrl, options: .mappedIfSafe)
json = try? JSONSerialization.jsonObject(with: data)
} catch {
print("Something goes wrong when reading local json file.")
}
}
return json
}
}
I try to read the local json file and output json. But the line json = try? JSONSerialization.jsonObject(with: data) gives an error saying Cannot assign value of type 'Any?' to type 'JSON'.
My json data looks like
{
"leagues":
[
{ "name": "Hockey",
"image": "hockey",
"games":
[
{
"game_state": "Final",
"game_time": 1456662600,
"home_team_city": "Alberta",
"home_team_name": "Pigs",
"home_team_score": 1,
"home_team_logo": "pig",
"visit_team_city": "Montreal",
"visit_team_name": "Fishes",
"visit_team_score": 4,
"visit_team_logo": "fish"
}
]
}
]
}
When I change the output type to be Any? I print the output and it seems missing some elements.
{
leagues = (
{
games = (
{
"game_state" = Final;
"game_time" = 1456662600;
...
How can I fix it?
Check the solution below, I used Codable for the JSON decoding.
import Foundation
struct Sports: Codable {
let leagues: [League]
}
struct League: Codable {
let name, image: String
let games: [Game]
}
struct Game: Codable {
let gameState: String
let gameTime: Int
let homeTeamCity, homeTeamName: String
let homeTeamScore: Int
let homeTeamLogo, visitTeamCity, visitTeamName: String
let visitTeamScore: Int
let visitTeamLogo: String
enum CodingKeys: String, CodingKey {
case gameState = "game_state"
case gameTime = "game_time"
case homeTeamCity = "home_team_city"
case homeTeamName = "home_team_name"
case homeTeamScore = "home_team_score"
case homeTeamLogo = "home_team_logo"
case visitTeamCity = "visit_team_city"
case visitTeamName = "visit_team_name"
case visitTeamScore = "visit_team_score"
case visitTeamLogo = "visit_team_logo"
}
}
class ReadLocalJSON {
static func readJSONFromFile(fileName: String) -> Sports?
{
let path = Bundle.main.path(forResource: fileName, ofType: "json")
let url = URL(fileURLWithPath: path!)
let sportsData = try? Data(contentsOf: url)
guard
let data = sportsData
else { return nil }
do {
let result = try JSONDecoder().decode(Sports.self, from: data)
print(result)
return result
} catch let error {
print("Failed to Decode Object", error)
return nil
}
}
}
ReadLocalJSON.readJSONFromFile(fileName: "test")
Step 1:- first make a modal class in your project
struct Welcome: Codable {
let leagues: [League]?
}
// MARK: - League
struct League: Codable {
let name, image: String?
let games: [Game]?
}
// MARK: - Game
struct Game: Codable {
let gameState: String?
let gameTime: Int?
let homeTeamCity, homeTeamName: String?
let homeTeamScore: Int?
let homeTeamLogo, visitTeamCity, visitTeamName: String?
let visitTeamScore: Int?
let visitTeamLogo: String?
enum CodingKeys: String, CodingKey {
case gameState = "game_state"
case gameTime = "game_time"
case homeTeamCity = "home_team_city"
case homeTeamName = "home_team_name"
case homeTeamScore = "home_team_score"
case homeTeamLogo = "home_team_logo"
case visitTeamCity = "visit_team_city"
case visitTeamName = "visit_team_name"
case visitTeamScore = "visit_team_score"
case visitTeamLogo = "visit_team_logo"
}
}
Step 2 : - After getting response write this line,
let decoder = JSONDecoder()
let obj = try! decoder.decode(Welcome.self, from: jsonData!)
IF you have still problem let me know

JSON object parses but omits the first item in the set

I'm trying to access the first result from this query:
https://www.instagram.com/web/search/topsearch/?query=_myUsername
I'm able to get a JSON object like so:
var request = URLRequest(url: URL(string: api)!)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error ?? "" as! Error)")
return
}
do {
let jsonResponse = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
completionHandler(jsonResponse,nil)
} catch let parsingError {
print("Error", parsingError)
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(String(describing: response))")
}
}
task.resume()
The result is a JSON object that omits the first user in "users". For example, if I parse the JSON object to get the username of the first user in the result like this...
if let users = jsonResponse!["users"] as? [Any] {
if let first = users.first as? [String: Any] {
if let user = first["user"] as? [String: Any] {
self.igUser = user["username"] as! String
... It returns the username of the 'position = 1' user, while I actually want the 'position = 0' user. Am I parsing this wrong?
As you can see there is a key position you should assume that the list isn't sorted. You have to find the nth element of the list.
The minimal Codable implementation would be:
struct TopSearchAPIResponse: Codable {
let users: [User]
//let places, hashtags: [Type] // As these two are empty arrays you don't know
// their type in advance. So you can omit them
// for now. When you know their type you can
// use them by providing actual type.
let hasMore: Bool
let rankToken: String
let clearClientCache: Bool
let status: String
struct User: Codable {
let position: Int
let user: UserInfo
struct UserInfo: Codable {
let pk: String
let username: String
let fullName: String
let isPrivate: Bool
let profilePicURL: URL
let profilePicID: String?
let isVerified: Bool
let hasAnonymousProfilePicture: Bool
let followerCount: Int
let reelAutoArchive: ReelAutoArchive
let byline: String
let mutualFollowersCount: Int
let unseenCount: Int
private enum CodingKeys: String, CodingKey {
/* This enum is necessary as we want profile_pic_url & profile_pic_id
to be decoded as profilePicURL & profilePicID respectively (instead of
profilePicUrl & profilePicId) so that we follow Swift conventions */
case pk
case username
case fullName
case isPrivate
case profilePicURL = "profilePicUrl"
case profilePicID = "profilePicId"
case isVerified
case hasAnonymousProfilePicture
case followerCount
case reelAutoArchive
case byline
case mutualFollowersCount
case unseenCount
}
enum ReelAutoArchive: String, Codable {
case off
case on
case unset
}
}
}
}
You will use it as:
do {
let jsonDecoder = JSONDecoder()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
let response = try jsonDecoder.decode(TopSearchAPIResponse.self, from: data)
if let firstUser = response.users.first(where: { $0.position == 0 }) {
print(firstUser.user.username) // prints "myusernameisverygay"
}
} catch {
print(error)
}
Note: Some modifications had been made after the answer was accepted.

How to convert JSON into [WeatherModel] in Swift/SwiftyJSON?

I was trying to make a weather app. And having a problem using SwiftyJSON.
I need to assign [WeatherModel] to my JSON data.
Basically, I need to set json variable to weatherData. The code is below.
Here is my controller:
var weatherData = [WeatherModel]()
func getJSONData(completed: #escaping () -> ()) {
if let filepath = Bundle.main.path(forResource: "weather", ofType: "json") {
do{
let data = try Data(contentsOf: URL(fileURLWithPath: filepath), options: .alwaysMapped)
let json = JSON(data: data)
// And here I need to set json to weatherData
} catch let error{
print(error.localizedDescription)
}
DispatchQueue.main.async {
completed()
}
} else {
print("file not found")
}
}
Here is my WeatherModel struct:
struct WeatherModel {
let cod: String
let message: Double
let cnt: Int
let list: [List]
let city: City
}
Note: I really need this to be made using only SwiftJSON. Any help will be appreciated :]
Well, we don't know what your JSON looks like.
To provide a sample, if this is what your JSON looked like:
{
"data": [
{
"cod": "some string here",
"message": 2.0,
"cnt": 1
...
}
]
}
... you would decode it as follows:
for (_, dict) in json["data"] {
guard let cod = dict["cod"].string else { continue }
guard let message = dict["message"].double else { continue }
guard let cnt = dict["cnt"].int else { continue }
// ...
let weather = WeatherModel(cod: cod, message: message, cnt: cnt, ...)
weatherData.append(weather)
}
You would have to modify this to work with your json format and the exact requirements.
Try this, i am not sure about your json structure correctly.
struct WeatherModel:Codable {
let cod: String
let message: Double
let cnt: Int
let list: [List]
let city: City
enum CodingKeys: String, CodingKey
{
case title = "name"
case url = "message"
case cnt
case list
case city
}
}
struct City:Codable {
let name
}
struct List:Codable {
//define your list data as in json
}
after this decode your json data.
if let wheatherData = try? JSONDecoder().decode(WeatherModel.self, from: data) {
// here Is your json model weatherData
}

Swift Codable expected to decode Dictionary<String, Any>but found a string/data instead

I have been working with the Codable protocol
Here is my JSON file :
{
"Adress":[
],
"Object":[
{
"next-date":"2017-10-30T11:00:00Z",
"text-sample":"Some text",
"image-path":[
"photo1.png",
"photo2.png"
],
"email":"john.doe#test.com",
"id":"27"
},
{
"next-date":"2017-10-30T09:00:00Z",
"text-sample":"Test Test",
"image-path":[
"image1.png"
],
"email":"name.lastename#doe.com",
"id":"28"
}
]
}
I only have to focus on the Object array, and the "image-path" array can contain 0, 1, or 2 strings.
So here is my implementation:
struct Result: Codable {
let Object: [MyObject]
}
struct MyObject: Codable {
let date: String
let text: String
let image: [String]
let email: String
let id: String
enum CodingKeys: String, CodingKey {
case date = "next-date"
case text = "text-sample"
case image = "image-path"
case email = "email"
case id = "id"
}
init() {
self.date = ""
self.text = ""
self.image = []
self.email = ""
self.id = ""
}
}
I call it from my service class after requesting and getting the JSON data this way:
if let data = response.data {
let decoder = JSONDecoder()
let result = try! decoder.decode(Result, from: data)
dump(result.Object)
}
Everything is working except the [String] for the image property
But it can't compile, or I get an "Expected to decode..." error.
How should I handle the nil/no data scenario?
I have made a small change in your MyObject struct, i.e.,
1. Marked all properties as optionals
2. Removed init() (I don't think there is any requirement of init() here.)
3. Use Result.self instead of Result in decoder.decode(...) method
struct MyObject: Codable
{
let date: String?
let text: String?
let image: [String]?
let email: String?
let id: String?
enum CodingKeys: String, CodingKey
{
case date = "next-date"
case text = "text-sample"
case image = "image-path"
case email = "email"
case id = "id"
}
}
To test the above, I have used the below code and it is working fine.
let jsonString = """
{"Adress": [],
"Object": [{"next-date": "2017-10-30T11:00:00Z",
"text-sample": "Some text",
"image-path": ["photo1.png", "photo2.png"],
"email": "john.doe#test.com",
"id": "27"},
{"next-date": "2017-10-30T09:00:00Z",
"text-sample": "Test Test",
"image-path": ["image1.png"],
"email": "name.lastename#doe.com",
"id": "28"}
]
}
"""
if let data = jsonString.data(using: .utf8)
{
let decoder = JSONDecoder()
let result = try? decoder.decode(Result.self, from: data) //Use Result.self here
print(result)
}
This is the result value that I am getting: