I am able to retrieve some JSON from an endpoint but then when I try to decode that JSON into my model object something goes wrong to where the json can't be parsed. I've made sure the properties in my model object matched the json keys, so I dont understand what the issue is. It prints out the catch block every time.
Here is my code to retrieve the json:
func getServiceProviders() {
let session = URLSession.shared
let url = URL(string: "http://exampleendpoint.com/example")!
URLSession.shared.dataTask(with: url) { data, response, error in
//Here is where I try to decode the json into my model object
do {
let jsonContent = try JSONDecoder().decode(ServiceObject.self, from: data!)
print(jsonContent)
} catch {
print("Ooops")
}
}.resume()
Here is my model object:
struct ServiceObject: Decodable {
let serviceproviders: [ServiceProvider]
struct ServiceProvider: Decodable {
let city: String
let coordinates: Location
let name: String
let overallGrade: String
let postalCode: Int
let state: String
let reviewCount: Int
}
struct Location: Decodable {
let latitude: Double
let longitude: Double
}
}
Here is what the json object I retrieve looks like:
{
"serviceproviders": [
{
"city" : "Beech Grove",
"coordinates" : {
"latitude" : "39.715417",
"longitude" : "-86.095646"
},
"name" : "William J Ciriello Plumbing Co Inc",
"overallGrade" : "A",
"postalCode" : "46107",
"state" : "Indiana",
"reviewCount" : 309
},
{
"city" : "Indianapolis",
"coordinates" : {
"latitude" : "39.922607",
"longitude" : "-86.0267094"
},
"name" : "Roby's Plumbing & Appliance Service",
"overallGrade" : "B",
"postalCode" : "46256",
"state" : "Indiana",
"reviewCount" : 903
},
Related
I have following JSON:
[
{
"id": 1,
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
37.5741167,
55.7636592
]
},
"properties": {
"hintContent": "переулок Волков, 13с1",
"balloonContentHeader": "переулок Волков, 13с1"
}
]
I am trying to use JSONDecoder:
struct Point : Codable {
let id: Int
let type: String
let properties: Properties
}
struct Properties : Codable {
let hintContent: String
let balloonContentHeader: String
}
struct Points : Codable {
var data : [Point]
}
func parse(fileName: String) {
let url = Bundle.main.url(forResource: fileName, withExtension: "json")
let data = try? Data(contentsOf: url!)
if let jsonPoints = try? JSONDecoder().decode([Point].self, from: data!) {
print(jsonPoints)
}
}
What is wrong?
Make sure your JSON is valid -- this one has a missing }.
Paste your (valid) JSON into app.quicktype.io to generate models ie. make sure that your models match the JSON. In the event that your JSON isn't valid, that site will warn you about it as well.
Always use do/try/catch and not try? so that you can get meaningful errors when JSON decoding fails.
let data = """
[
{
"id": 1,
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
37.5741167,
55.7636592
]
},
"properties": {
"hintContent": "t",
"balloonContentHeader": "t"
}
}
]
""".data(using: .utf8)
struct Point: Codable {
let id: Int
let type: String
let geometry: Geometry
let properties: Properties
}
// MARK: - Geometry
struct Geometry: Codable {
let type: String
let coordinates: [Double]
}
// MARK: - Properties
struct Properties: Codable {
let hintContent, balloonContentHeader: String
}
func parse(fileName: String) {
do {
let jsonPoints = try JSONDecoder().decode([Point].self, from: data!)
print(jsonPoints)
} catch {
print(error)
}
}
Without the adjusted JSON, your original code would generate this error in the catch block:
The given data was not valid JSON....Badly formed object around character 224.
Problem Stuck On
I am trying to be able to read out my json file data to the console log for testing so I can use it later on.
Im not sure how to finish off my other structs due to varying data size and possible bad json format.
Once I finish that I believe I would need to use a for loop for read the varying size of data from "M", "S" and "WP" (This part shouldn't be complicated I believe)
Possible Things to Consider
I want to write and add data to "M" "S" "WP"
The data amount for ("M", "S") could be any number of String Array data objects
The data in "WP" Might need a different format I would like to add a name("abc") with a Int array containing any number of data points
Note: My Json Format Might Be Wrong in Some Areas concerning MP and WP
Swift Code To Grab Data
import Foundation
struct UserDay: Codable {
let mp: UserMP
let wp: UserWP
}
struct UserMP: Codable {
let m: [UserM]
let s: [UserS]
}
struct UserM : Codable {
let title: String
let description: String
let time: String
}
struct UserS : Codable {
let title: String
let description: String
let time: String
}
struct UserWP: Codable {
let wp: [WPData]
}
struct WPData: Codable {
let title: String
let values: [Int]
}
class LogDataHandler {
public func grabJSONInfo(){
guard let jsonURL = Bundle(for: type(of: self)).path(forResource: "LogData", ofType: "json") else { return }
guard let jsonString = try? String(contentsOf: URL(fileURLWithPath: jsonURL), encoding: String.Encoding.utf8) else { return }
// Print Info for TESTING
var year: UserDay?
do {
year = try JSONDecoder().decode(UserDay.self, from: Data(jsonString.utf8))
} catch {
print("ERROR WHEN DECODING JSON")
}
guard let results = year else {
print("YEAR IS NIL")
return
}
print(results)
}
}
JSON Example Data Below
{
"01/01/2020": {
"MP" : {
"M" : [
{"title" : "m1", "description" : "1", "time" : "12:30pm"},
{"title" : "m2", "description" : "2", "time" : "1:30pm"},
{"title" : "m3", "description" : "3", "time" : "2:30pm"}
],
"S" : [
{"title" : "s1", "description" : "1", "time" : "1pm"}
]
},
"WP" : [
{ "title" : "abc", "values" : [12, 10, 6]},
{ "title" : "def", "values" : [8]}
]
},
"01/29/2020": {
"MP" : {
"M" : [{"title" : "m1", "description" : "1", "time" : "12:30pm"}],
"S" : [{"title" : "s1", "description" : "1", "time" : "12:30pm"}]
},
"WP" :[{ "title" : "def", "values" : [8]}]
}
}
Based on the comments and our chat, this seems to be a question of the right way to construct the Swift models and the JSON object.
Based on your (updated) JSON, you might want to decode your data into a [String: UserDay] - a dictionary with a date string as key and UserDay as a value.
First, WP property in your JSON is just an array of objects (that map to WPData), so it's best to change your UserDay.wp to be [WPData] instead of UserWP:
struct UserDay: Codable {
let mp: UserMP
let wp: [WPData] // <-- changed
}
Second, some of your models' properties don't match directly to what's in JSON because keys-properties mapping is case sensitive. You can explicitly define CodingKeys to map them:
struct UserDay: Codable {
let mp: UserMP
let wp: [WPData]
enum CodingKeys: String, CodingKey {
case mp = "MP", wp = "WP"
}
}
struct UserMP: Codable {
let m: [UserM]
let s: [UserS]
enum CodingKeys: String, CodingKey {
case m = "M", s = "S"
}
}
Now you're ready to decode [String: UserDay]:
let userDays = try JSONDecoder().decoder([String: UserDay].self, from: jsonData)
let userDay = userDays["01/29/2020"]
Of course, working with String instead of Date isn't very convenient. Unfortunately, Dictionary's conformance to Codable only supports Int or String as keys (AFAIK).
So, let's do a manual decoding into a new root object UserData that works with Dates:
struct UserData: Codable {
var userDays: [Date: UserDay]
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let dict = try container.decode([String: UserDay].self)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/dd/yyyy"
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
// decode (String, UserDay) pairs into an array of (Date, UserDay)
let pairs = dict.compactMap { (key, value) -> (Date, UserDay)? in
guard let date = dateFormatter.date(from: key) else { return nil }
return (date, value)
}
// uniquing is used just in case there non unique keys
self.userDays = Dictionary(pairs, uniquingKeysWith: {(first, _) in first})
}
}
Now, we can decode into this UserData object:
let userData = try JSONDecoder().decode(UserData.self, from: jsonData)
let todaysData = userData.userDays[Date()]
{"dataList":{"1547795650562": {
"c0a8007b-6759-111d-8167-59e8dabe0086": {
"recordDate": 1547795650562,
"resultValue": "160",
"vitalParameter": {
"uom": {
"code": "KG",
"name": "KG",
"id": "c0a8007b-6759-111d-8167-59e76204007f"
},
"resultType": {
"code": "VSRTNUMERIC",
"name": "Numeric",
"id": "20cf4756-40b0-4cc1-acb5-861765370a41"
},
"code": "29463-7",
"name": "Weight",
"id": "c0a8007b-6759-111d-8167-59e8dabe0086"
},
"id": "c0a8007b-6855-1d16-8168-5fd18fa301b7"
}}
}}
getting 1547795650562 and c0a8007b-6759-111d-8167-59e8dabe0086 as class names. But I dont want like this;
class DataList : NSObject, NSCoding{
var 1547795650562 : 1547795650562!
}
class 1547795650562 : NSObject, NSCoding{
var c0a8007b6759111d816759e8dabe0086 : VitalParameter!
}
But the problem here is, 1547795650562 and c0a8007b-6759-111d-8167-59e8dabe0086 cannot be hard coded because they may change.
c0a8007b-6759-111d-8167-59e8dabe0086 is dynamic id and 1547795650562 is recordDate. Inner object is repetitive.
But I have to map as the keys are of recordDate and id respectively.
Try using Codable instead of NSCoding to parse your JSON data.
Models:
struct Root: Codable {
let dataList: [String:[String:Record]]
}
struct Record: Codable {
let recordDate: Int
let resultValue: String
let vitalParameter: VitalParameter
let id: String
}
struct VitalParameter: Codable {
let uom, resultType: ResultType
let code, name, id: String
}
struct ResultType: Codable {
let code, name, id: String
}
Parse the JSON data using above models like,
do {
let response = try JSONDecoder().decode(Root.self, from: data)
print(response)
} catch {
print(error)
}
Note: You can use https://app.quicktype.io to get the models from your JSON instantly. Make the changes as per your requirement and you're good to go.
I'm trying to parse some data from JSON, I already got that working with another API but now I have another struct and I'm getting typeMissmatch Erros...
The JSON looks like:
{
"status": 200,
"data": {
"date": "2018-04-07T00:00:00.508Z",
"featured": [
{
"id": "2345",
"name": "name",
"price": "1,000",
"priceIcon": "String",
"priceIconLink": "URLString",
"images": {
"icon": "URLString",
"png": "URLString",
"gallery": "URLString",
"featured": "URLString"
},
"rarity": "1String",
"type": "1String",
"readableType": "1String"
}
],
"daily": [
{
"id": "12324",
"name": "name",
"price": "1,500",
"priceIcon": "String",
"priceIconLink": "URLString",
"images": {
"icon": "URLString",
"png": "URLString",
"gallery": "URLString",
"featured": "URLString"
},
"rarity": "1String",
"type": "1String",
"readableType": "1String"
}
]
}}
And a Codable struct like that:
struct Base : Codable {
let status : Int
let data : DataItems
}
struct DataItems : Codable {
let date : String
let featured : [Featured]
let daily : [Daily]
}
struct Featured : Codable {
let id : String
let name : String
let price : String
let priceIcon : String
let priceIconLink : String
let images : Images
let rarity : String
let type : String
let readableType : String
}
struct Daily : Codable {
let id : String
let name : String
let price : String
let priceIcon : String
let priceIconLink : String
let images : Images
let rarity : String
let type : String
let readableType : String
}
struct Images : Codable {
let icon : String
let png : String
let gallery : String
let featured : String
}
But when I try to decode that Json I get a "Swift.DecodingError.typeMismatch" Error:
▿ Swift.DecodingError.typeMismatch
▿ typeMismatch: (2 elements)
- .0: Swift.String #0
▿ .1: Swift.DecodingError.Context
▿ codingPath: 5 elements
- CodingKeys(stringValue: "data", intValue: nil)
- CodingKeys(stringValue: "daily", intValue: nil)
▿ _JSONKey(stringValue: "Index 0", intValue: 0)
- stringValue: "Index 0"
▿ intValue: Optional(0)
- some: 0
- CodingKeys(stringValue: "images", intValue: nil)
- CodingKeys(stringValue: "featured", intValue: nil)
- debugDescription: "Expected to decode String but found a number instead."
- underlyingError: nil
My JSON Decoder:
enum Result<Value> {
case success(Value)
case failure(Error)
}
func getItems(for userId: Int, completion: ((Result<Base>) -> Void)?) {
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "api.jsonbin.io"
urlComponents.path = "/myurl"
let userIdItem = URLQueryItem(name: "userId", value: "\(userId)")
urlComponents.queryItems = [userIdItem]
guard let url = urlComponents.url else { fatalError("Could not create URL from components") }
var request = URLRequest(url: url)
request.httpMethod = "GET"
let config = URLSessionConfiguration.default
config.httpAdditionalHeaders = [
"secret-key": "xyz"
]
let session = URLSession(configuration: config)
let task = session.dataTask(with: request) { (responseData, response, responseError) in
DispatchQueue.main.async {
if let error = responseError {
completion?(.failure(error))
} else if let jsonDataTest = responseData {
// Now we have jsonData, Data representation of the JSON returned to us
// from our URLRequest...
// Create an instance of JSONDecoder to decode the JSON data to our
// Codable struct
let decoder = JSONDecoder()
do {
// We would use Post.self for JSON representing a single Post
// object, and [Post].self for JSON representing an array of
// Post objects
let posts = try decoder.decode(Base.self, from: jsonDataTest)
completion?(.success(posts))
} catch {
completion?(.failure(error))
}
} else {
let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey : "Data was not retrieved from request"]) as Error
completion?(.failure(error))
}
}
}
task.resume()
}
var base:Base?
func loadJson() {
getItems(for: 1) { (result) in
switch result {
case .success(let base):
self.base = base
dump(base)
case .failure(let error):
fatalError("error: \(error.localizedDescription)")
}
}
}
I'm new to swift and not sure what this Error is telling me or where the problem "decode String but found a number" is. I think there is something wrong with me struct.. I hope someone can help me there.
Please show the code where you want to parse the data to json.
let urlString = "your_url.json"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data else { return }
do {
//Decode retrived data with JSONDecoder and assing type of Article object
let baseData = try JSONDecoder().decode(Base.self, from: data)
print(baseData) //whole project
print(baseData.status) //200.0
print(baseData.data.date)
for day in baseData.data.daily {
print(day.id)
print(day.images.icon)
print(day.images.featured)
print(day.images.gallery)
print(day.images.png)
print(day.name)
print(day.price)
print(day.priceIcon)
print(day.priceIconLink)
print(day.rarity)
print(day.readableType)
print(day.type)
}
for feature in baseData.data.featured {
print(feature.id)
print(feature.images.icon)
print(feature.images.featured)
print(feature.images.gallery)
print(feature.images.png)
print(feature.name)
print(feature.price)
print(feature.priceIcon)
print(feature.priceIconLink)
print(feature.rarity)
print(feature.readableType)
print(feature.type)
}
} catch let jsonError {
print(jsonError)
}
}.resume()
I tried this and it works for me.
By the way I was a little bit confused that Featured and Daily have all the same variables but are different models.
EDIT
Your posted data in the question are valid. The json from https://api.jsonbin.io/b/5acbd2dc214f9a2b84c6f167/1 is wrong or not consistent.
There is "featured": false in Daily and in Featured it is a string. In the struct is a string expected. So you will get a mismatch. Once you try to parse a string (works) and then you try to parse a boolean to a string (error).
JSON response
tranArr
[{
"total_amt" : -10000,
"tran_type" : "C3",
"pay_type" : "05",
"tran_time" : "20180125 133122",
"point_total" : 0
},
{
"total_amt" : -1004,
"tran_type" : "C5",
"pay_type" : "05",
"tran_time" : "20180124 163602",
"point_total" : 0
}]
=====================
I want to filter tran_type = "C3"
What should I do?
"total_amt" : -10000,
"tran_type" : "C3",
"pay_type" : "05",
"tran_time" : "20180125 133122",
"point_total" : 0
Try something like this:
let string = "[{ \"total_amt\" : -10000,\"tran_type\" : \"C3\", \"pay_type\" : \"05\", \"tran_time\" : \"20180125 133122\", \"point_total\" : 0},{\"total_amt\" : -1004,\"tran_type\" : \"C5\", \"pay_type\" : \"05\", \"tran_time\" : \"20180124 163602\", \"point_total\" : 0 }]"
// Convert string to Data
let jsonData = string.data(using: .utf8)!
do {
// Serialize the json Data and cast it into Array of Dictionary
let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: .allowFragments) as? [[String: Any]]
// Apply filter on `tran_type`
let tranArr = jsonObject?.filter({ (dictionary) -> Bool in
dictionary["tran_type"] as? String == "C3"
})
print(tranArr)
} catch {
print(error)
}
Filtered result will be in tranArr object.