I am working on an iOS application written in Swift which parse a lot of JSON files.
The JSON structures are sophisticated and I would to test them before to map JSON to object.
For example test if the key 'users' exists and for each user the structure('name', 'first', 'last').
{
"users": [
{
"name": {
"first": "emmi",
"last": "wiita"
}
},
{
"name": {
"first": "erdi",
"last": "nielen"
}
},
{
"name": {
"first": "daniel",
"last": "beck"
}
}
]
}
Are there any good way to do this?
Thanks.
The only way to accomplish that is really opening the JSON file and testing each property.
A good news is that since Swift 2.0 you can use guard to test if you can assign a valid value to a var or let, so you can create a function as follows:
func isValidJSON(data: NSData) -> Bool {
json = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments)
// if "users" exists on the JSON
// AND we can cast it to an array of dictionaries
guard let users = json["users"] as [[String: AnyObject]] else {
return false
}
for user in users {
guard let name = user["name"] as [[String: AnyObject]]
firstName = name["first"] as String,
lastName = name["last"] as String else {
return false
}
}
// valid JSON
return true
}
A best practice would be also to implement the use of Exceptions instead returning false in each guard statement.
Thank you for your post #Felipe Plets. I found a good way to test JSON file.
I have implemented an enum ErrorType(Exception):
/**
Enumeration describing possible errors that occur while parsing
a message from JSON file.
*/
enum ReceiverError: ErrorType {
/**
Error trigge when the key is missed or the type.
- Parameter key: Key missed.
*/
case MissingKeyOrType(key: String)
}
then I can test all the JSON file like this:
func isValidJSON(message: [String: AnyObject]) -> throws {
guard let sosId = message["id"] as? String
else { throw ReceiverError.MissingKeyOrType(key: "sos - id") }
guard let text = message["text"] as? String
else { throw ReceiverError.MissingKeyOrType(key: "text")
}
let json = ... Get JSON
do {
try isValidJSON(json)
} catch CustomReceiver.ReceiverError.MissingKeyOrType(let key) {
print("Error: Missing key or type [key: \(key)].")
}
Related
I have a swift program that reads in stock data in JSON format from multiple files. One such file is laid out with a string date and an associated key value pair. If there was a key for the string date I could create a struct with an init. The code below allows me to read the file into a dictionary but I end up with a string where I want a date. In addition, the only way I know how to sort is via mapping to a key value pair first which produces an array. Any input that would point me in the right direction will be appreciated.
Below is a snippet of the JSON file and the code I use to read it into a dictionary.
Regards,
Chris
{
"20200921": {
"NAV": 173.67997
},
"20200922": {
"NAV": 175.49292
},
"20200923": {
"NAV": 171.35833
},
struct TimeSeriesValue: Decodable {
let value : Double
enum CodingKeys: String, CodingKey {
case value = "NAV"
}
}
class getTimeSeriesData: ObservableObject {
#Published var tmpData : [String:TimeSeriesValue] = [:]
init() {
getData()
}
func getData() {
guard let url = Bundle.main.url(forResource: "JSONDataPrincipal", withExtension: "json")
else {
print("Json file not found")
return
}
let decoder = JSONDecoder()
do {
let data = try Data(contentsOf: url)
self.tmpData = try decoder.decode([String:TimeSeriesValue].self, from: data)
} catch {
print(error)
}
}
}
struct JSONModelPrincipal {
#ObservedObject var prinJSONData = getTimeSeriesData()
}
After some research and testing, I would agree that dictionaries are not designed for being ordered. So, I modified the JSON file as shown below. With this one line modification I have been able to create an init and convert the string to a date. Thank you for the responses.
Chris
{ "Time Series Data" : {
"20200921": {
"NAV": 173.67997
},
"20200922": {
"NAV": 175.49292
},
I am using graphql for get the API values using Apollo. I have successfully downloaded schema.json and get the values from grpahql. but i can't get the values array of json values.
This is my sample response:
{
"data": {
"team_Dashboard": {
"activeMission": "Food Mission",
"currentMissionChallenges": [
{
"id": "123",
"title": "Challenge",
"estTime": "5",
"location": "Anywhere",
"maxPts": "30",
"status": "Not yet started"
},
{
"id": "1234",
"title": " II Challenge",
"estTime": "5",
"location": "Anywhere",
"maxPts": "70",
"status": "Not yet started"
}
]
}
}
}
Graphql query:
query teamDashboard($teamId: ID!) {
team_Dashboard(teamId: $teamId) {
activeMission
currentMissionChallenges
}
}
Graphql schema response:
missionDeadLine: String
currentMissionChallenges: [JSON]
When i add the currentMissionChallenges([JSON]) in my Graphql query get error response from the server. but When i remove currentMissionChallenges from Graphql query, get success response and values from the server.
The problem is currentMissionChallenges is [JSON] format. When i change my graphql query This is graphql Response
query teamDashboard($teamId: ID!) {
team_Dashboard(teamId: $teamId) {
activeMission
currentMissionChallenges {
id
title
estTime
location
maxPts
status
}
}
}
Following error display in dashBord.grpahql
Field "currentMissionChallenges" must not have a selection since type "[JSON]" has no subfields.
How can i get the json array values from graphql. what is problem for getting Json Values? Please help me!
The best thing about GraphQL is we can use the query as model
Because the response would be as same as the query, so better we can assign the response to a variable of Query type.
Let me explain with an example:-
Suppose if I need to query about my profile data,
Profile.graphql
query MyProfile{
player {
me {
id
secret
name
email
state
country
timezone
picture
pictureType
audio
rank
}
}
countries{
value
viewValue
}
}
Once we'll build the app, it'll create MyProfileQuery in API.swift. In viewController we can use the response as below-
var myProfileData: MyProfileQuery.Data.Player.Me? // Declaring the valiable of player Type
ApolloClientManager.sharedInstance
.fetchQuery(MyProfileQuery(), showLoader: true,
viewController: self) { (response) in // Fetching response using Apollo Client Manager
if let allData = response {
if let profiledata = allData.player?.me {
self.myProfileData = profiledata // Assigning response into the variable declared
self.myEdittingProfileData = profiledata
self.updateUI()
}
if let countryData = allData.countries {
self.allCountrydata = countryData
self.getPickerDataForState(comppletion: {
self.openTimeZonePicker(completion: {
print("got timeZone Data")
})
})
}
}
}
Now we have response into the myProfileData variable which we can use
as follows -
Now we can access all the values we have mentioned in our query as below
print("player id is- \(myProfileData?.id)")
print("player name is- \(myProfileData?.name)")
print("player email is- \(myProfileData?.email)")
print("player state is- \(myProfileData?.state)")
print("player rank is- \(myProfileData?.rank)")
print("player pictureType is- \(myProfileData?.pictureType)")
// player id is- 10
// player name is- jordan
// player email is- jordan#domain.com
// player state is- Ohio
// player rank is- 101
// player pictureType is- custome
//
Hope this will help you out đđđ
i suggest you use custom scalar.
import Apollo
public typealias JSON = [String: Any]
extension Dictionary: JSONDecodable {
public init(jsonValue value: JSONValue) throws {
if let array = value as? NSArray {
self.init()
if var dict = self as? [String: JSONDecodable & JSONEncodable] {
dict["data"] = array as! [[String: Any]]
self = dict as! Dictionary<Key, Value>
return
}
}
guard let dictionary = value as? Dictionary else {
throw JSONDecodingError.couldNotConvert(value: value, to: Dictionary.self)
}
self = dictionary
}
}
var currentMissionChallanges = [JSON]()
func getTeamDashboard(id:String) {
let query = TeamDashboardQuery(id:id)
apollo.fetch(query:query) { [weak self] result, error in
if let dashboards = result.data?.team_dashboard {
if let array = dashboards!["currentMissionChallanges"] as?
[JSON] {
self?.currentMissionChallanges = array
}
}
}
}
I receive from a post request, this JSON:
"clinic_info": {
"city": "Querétaro",
"state": "Querétaro",
"country": "MĂ©xico",
"phone": null,
"ext": null,
"coords": "20.6046089,-100.37826050000001",
"location": "Querétaro"
}
But when it is empty the JSON is:
"clinic_info": []
This produces an error: Expected to decode Dictionary but found an array instead.
This is happening because decoder want dictionary and your JSON is array
Need to check before decoding that JSON response is dictionary or Array and do decoding accordingly.
If you find Dictionary then do like this
let myData = try JSONDecoder().decode(YourModel.self, from: jsonData)
If you find Array then do like this
let myData = try JSONDecoder().decode([YourModel].self, from: jsonData)
You can do it using try, throw like that
import Foundation
struct ClinicData: Codable {
let clinicInfo: ClinicInfo?
enum CodingKeys: String, CodingKey {
case clinicInfo = "clinic_info"
}
}
struct ClinicInfo: Codable {
let city, state, country: String
let coords, location: String
}
// MARK: Convenience initializers
extension ClinicData {
init(data: Data) throws {
self = try JSONDecoder().decode(ClinicData.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
func jsonData() throws -> Data {
return try JSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
**get clinicInfo**
if let clinicData = try? ClinicData.init(data: Data()), let clinicInfo =
clinicData.clinicInfo{
}
The service that provides those JSON responses replies with:
"clinic_info": { ... }
Where ... is a valid JSON object.
But when it is empty, you are saying it looks like this:
"clinic_info": []
Notice the [] that say this is an empty array of objects.
You might want to change the service response (if possible), since it looks inconsistent to me having it return an object when it has valid data, and an array when there is no valid data.
The error message you are getting is clear:
Expected to decode Dictionary but found an array instead.
It expected an object {}, but found an array [].
The Array class has a method for this.
Using typeof will always return "object".
The code below shows how to use the isArray() method in the Array class.
const obj = {
_array: [],
_object: {}
}
console.log(Array.isArray(obj._array)); // true
console.log(Array.isArray(obj._object)); // false
I got this JSON object which I sent from my server to my Swift application.
{
"625289": {
"id": 1,
"subject": "Hello World"
},
"625277": {
"id": 2,
"subject":"Bye World!"
}
}
So i tried to get the subject for each result ("625289" and "625277") by doing as below in my Swift class:
struct Resultat : Decodable {
let subject: String
}
var result = [Resultat]()
let urlll = URL(string:"http://localhost:8888/api/pouet.php")
URLSession.shared.dataTask(with: urlll!) { (data, response, error) in
do {
print("coucoulol")
//print(response)
self.result = try JSONDecoder().decode([Resultat].self, from: data!)
print(self.result)
for eachTicket in self.result {
print(eachTicket.subject)
}
} catch {
print("error"+error.localizedDescription)
}
}.resume()
However, when I tried to execute the code, it says "The data couldnât be read because it isnât in the correct format." From what I understand, the loop for in the code is suffice to get the values in the arrays or maybe I'm wrong. Any help is appreciated, thanks.
The root object is a dictionary. You can decode the object to [String:Resultat]. The dictionary contains also dictionaries. An array is not involved.
struct Resultat : Decodable {
let subject: String
let id : Int
}
...
let result = try JSONDecoder().decode([String:Resultat].self, from: data!)
for (key, value) in result {
print(key, value.subject)
}
You can try using SwiftyJSON below
$0.0 = Key
$0.1 = value
let data = JSON(result)
data.dictionaryValue.forEach({
print($0.1)
})
I parse JSON in my application and some of the JSONs have nil values which I handled. However, the app still shows me the error that JSON contains nil values. My code:
struct Response : Decodable {
let articles: [Article]
}
struct Article: Decodable {
let title: String
let description : String
let url : String
let urlToImage : String
}
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
let article = try JSONDecoder().decode(Response.self , from : data)
for i in 0...article.articles.count - 1 {
if type(of: article.articles[i].title) == NSNull.self {
beforeLoadNewsViewController.titleArray.append("")
} else {
beforeLoadNewsViewController.titleArray.append(article.articles[i].title)
}
if type(of : article.articles[i].urlToImage) == NSNull.self {
beforeLoadNewsViewController.newsImages.append(newsListViewController.newsImages[newsListViewController.newsIndex])
} else {
let url = URL(string: article.articles[i].urlToImage ?? "https://static.wixstatic.com/media/b77fe464cfc445da9003a5383a3e1acf.jpg")
let data = try? Data(contentsOf: url!)
if url != nil {
//make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
let img = UIImage(data: data!)
beforeLoadNewsViewController.newsImages.append(img!)
} else {
beforeLoadNewsViewController.newsImages.append(newsListViewController.newsImages[newsListViewController.newsIndex])
}
This is the error running the app:
FC91DEECC1631350EFA71C9C561D).description], debugDescription: "Expected String value but found null instead.", underlyingError: nil))
Note:
This JSON works with other urls that don't have nil values.
and here is the json
{
"status": "ok",
"source": "entertainment-weekly",
"sortBy": "top",
"articles": [
{
"author": null,
"title": "Hollywood's Original Gone Girl",
"description": null,
"url": "http://mariemcdonald.ew.com/",
"urlToImage": null,
"publishedAt": null
},
{
"author": "Samantha Highfill",
"title": "âSupernaturalâ: Jensen Ackles, Jared Padalecki say the boys arenât going âfull-mopeâ",
"description": "",
"url": "http://ew.com/tv/2017/10/18/supernatural-jensen-ackles-jared-padalecki-season-13/",
"urlToImage": "http://ewedit.files.wordpress.com/2017/10/supernatural-season-13-episode-1.jpg?crop=0px%2C0px%2C2700px%2C1417.5px&resize=1200%2C630",
"publishedAt": "2017-10-18T17:23:54Z"
},
{
The error is quite clear. JSONDecoder maps NSNull to nil so the decoder throws an error if it's going to decode a nil value to a non-optional type.
The solution is to declare all affected properties as optional.
let title: String
let description : String?
let url : String
let urlToImage : String?
or customize the decoder to replace nil with an empty string.
And because JSONDecoder maps NSNull to nil the check if ... == NSNull.self { is useless.
Edit:
Don't use ugly C-style index based loops use
let response = try JSONDecoder().decode(Response.self , from : data)
for article in response.articles {
beforeLoadNewsViewController.titleArray.append(article.title)
}
PS: But why for heaven's sake do you map the article instance to â apparently â separate arrays? You got the Article instances which contain everything related to one article respectively.
I am sorry, I cannot comment yet. But why don't you test for "null" the way, you did at the URLSession data check, with the guard-statement?
It would then look like:
guard let title = article.articles[i].title else {
beforeLoadNewsViewController.titleArray.append("")
return
}
beforeLoadNewsViewController.titleArray.append(title)
You try to parse a JSON structure here, in case the JSON structure would not fit to your needs, you still expect it to have the according attributes.
Could you also provide your JSON structure and how your decoding
try JSONDecoder().decode(Response.self , from : data)
looks like?