I'm at a loss right now of how to properly convert my JSON file to Swift Struct then parse it accordingly. I cant seem to parse the retrieved data. When running a print statement in the do-try-catch block of my JSONDecoder, it catches an error during parsing.
My questions are:
Did I format my Structs correctly?
Am I missing something in my method?
See below for details...
Appreciate the help!
Here's a snippet of my JSON
{
"data": [
{
"day": 1,
"image": {
"attribution": "© YouVersion",
"url": "//imageproxy-cdn.youversionapi.com/{width}x{height},png/https://s3.amazonaws.com/static-youversionapi-com/images/base/10778/1280x1280.jpg"
},
"verse": {
"human_reference": "Proverbs 16:9",
"html": null,
"text": "A man’s heart plans his course, but Yahweh directs his steps.",
"url": "https://www.bible.com/bible/206/PRO.16.9",
"usfms": [
"PRO.16.9"
]
}
},
{
"day": 2,
"image": {
"attribution": "© YouVersion",
"url": "//imageproxy-cdn.youversionapi.com/{width}x{height},png/https://s3.amazonaws.com/static-youversionapi-com/images/base/27119/1280x1280.jpg"
},
"verse": {
"human_reference": "Psalms 90:12",
"html": null,
"text": "So teach us to count our days, that we may gain a heart of wisdom.",
"url": "https://www.bible.com/bible/206/PSA.90.12",
"usfms": [
"PSA.90.12"
]
}
},
{
"day": 3,
"image": {
"attribution": "© YouVersion",
"url": "//imageproxy-cdn.youversionapi.com/{width}x{height},png/https://s3.amazonaws.com/static-youversionapi-com/images/base/27117/1280x1280.jpg"
},
"verse": {
"human_reference": "Proverbs 16:3",
"html": null,
"text": "Commit your deeds to Yahweh, and your plans shall succeed.",
"url": "https://www.bible.com/bible/206/PRO.16.3",
"usfms": [
"PRO.16.3"
]
}
]
}
Here's my resulting Struct(s)
struct VerseData: Codable {
var data: [Datum]
}
struct Datum: Codable {
let day: Int
let image: VerseImageData
let verse: VerseDetails
}
struct VerseImageData: Codable {
var attribution: String?
var url: String = ""
}
struct VerseDetails: Codable {
var humanReference: String = ""
var url: String = ""
var text: String = ""
}
Here's my call in my VC.Swift
func getData() {
let urlStr = URL(string: "https://developers.youversionapi.com/1.0/verse_of_the_day?version_id=206")
let request = NSMutableURLRequest(url: urlStr!)
request.setValue("myAPIKey", forHTTPHeaderField: "X-YouVersion-Developer-Token")
request.addValue("en", forHTTPHeaderField: "Accept-Language")
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.httpMethod = "GET"
print(request)
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest) { (data, response, error) in
guard let data = data else { return }
do {
let verseData = try JSONDecoder().decode(VerseData.self, from: data)
print(verseData)
} catch {
print("error")
}
}
dataTask.resume()
}
}
Try to print error
"humanReference"? should be "human_reference" according to the JSON and API documentation
Related
I'm trying to filter JSON and get key & value to parse it. Here all JSON values are dynamic. Right now I need to find "type = object" if the type found is true then I need to check value ={"contentType" & "URL"}.
here is my JSON:
{
"date": {
"type": "String",
"value": "03/04/1982",
"valueInfo": {}
},
"Scanner": {
"type": "Object",
"value": {
"contentType": "image/jpeg ",
"url": "https://www.pexels.com/photo/neon-advertisement-on-library-glass-wall-9832438/",
"fileName": "sample.jpeg"
},
"valueInfo": {
"objectTypeName": "com.google.gson.JsonObject",
"serializationDataFormat": "application/json"
}
},
"startedBy": {
"type": "String",
"value": "super",
"valueInfo": {}
},
"name": {
"type": "String",
"value": "kucoin",
"valueInfo": {}
},
"ScannerDetails": {
"type": "Json",
"value": {
"accountNumber": "ANRPM2537J",
"dob": "03/04/1982",
"fathersName": "VASUDEV MAHTO",
"name": "PRAMOD KUMAR MAHTO"
},
"valueInfo": {}
}
}
decode code:
AF.request(v , method: .get, parameters: nil, encoding: URLEncoding.default, headers: headers).responseJSON { (response:AFDataResponse<Any>) in
print("process instance id api document view list::::",response.result)
switch response.result {
case .success:
let matchingUsers = response.value.flatMap { $0 }.flatMap { $0. == "object" }
print("new object doc:::", matchingUsers)
guard let data = response.value else {
return
}
print("new object doc:::", matchingUsers)
if let newJSON = response.value {
let json = newJSON as? [String: [String:Any]]
print("new object doc:::", json as Any)
// let dictAsString = self.asString(jsonDictionary: json)
let vc = self.stringify(json: json ?? [])
print("dictAsString ::: dictAsString::::==",vc)
let data = vc.data(using: .utf8)!
do{
let output = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: [String:String]]
print ("demo:::==\(String(describing: output))")
}
catch {
print (error)
}
do {
if let jsonArray = try JSONSerialization.jsonObject(with: data, options : .allowFragments) as? [String: [String:String]]
{
print("json array::::",jsonArray) // use the json here
} else {
print("bad json")
}
} catch let error as NSError {
print(error)
}
}
self.view.removeLoading()
case .failure(let error):
print("Error:", error)
self.view.removeLoading()
}
}
How to get specific values from JSON? Any help is much appreciated pls...
Here is code from my playground with your json sample:
import Foundation
let json = """
{
"date": {
"type": "String",
"value": "03/04/1982",
"valueInfo": {}
},
"Scanner": {
"type": "Object",
"value": {
"contentType": "image/jpeg ",
"url": "https://www.pexels.com/photo/neon-advertisement-on-library-glass-wall-9832438/",
"fileName": "sample.jpeg"
},
"valueInfo": {
"objectTypeName": "com.google.gson.JsonObject",
"serializationDataFormat": "application/json"
}
},
"startedBy": {
"type": "String",
"value": "super",
"valueInfo": {}
},
"name": {
"type": "String",
"value": "kucoin",
"valueInfo": {}
},
"ScannerDetails": {
"type": "Json",
"value": {
"accountNumber": "ANRPM2537J",
"dob": "03/04/1982",
"fathersName": "VASUDEV MAHTO",
"name": "PRAMOD KUMAR MAHTO"
},
"valueInfo": {}
}
}
"""
let data = json.data(using: .utf8, allowLossyConversion: false)!
struct ObjectScanner: Decodable {
let contentType: String
let url: String
let fileName: String
}
enum ObjectScannerType {
case object(ObjectScanner)
}
struct Scanner: Decodable {
enum ScannerType: String, Decodable {
case object = "Object"
}
enum CodingKeys: String, CodingKey {
case type, value
}
let scanner: ObjectScannerType
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(ScannerType.self, forKey: .type)
switch type {
case .object:
let value = try container.decode(ObjectScanner.self, forKey: .value)
scanner = .object(value)
}
}
}
struct DateResponse: Decodable {
let type: String
let value: String
// let valueInfo // Not enough information in sample for me to decode this object
}
struct Response: Decodable {
enum CodingKeys: String, CodingKey {
case date
case scanner = "Scanner"
}
let date: DateResponse
let scanner: Scanner
}
let decoder = JSONDecoder()
do {
let response = try decoder.decode(Response.self, from: data)
print(response)
} catch {
print("Error decoding: \(error.localizedDescription)")
}
Note: this example is very unforgiving. Any missing value or type that is not supported will lead to a DecodingError. It's up to you to determine all possible types and what is optional and what is not.
I also didn't decode everything nor do I handle the date object to it's fullest
This is as json goes a very complex example. Everything in it is polymorphic: the date, the Scanner, the ScannerDetails, etc. You need to be very careful how you decode this and make sure you handle all possibilities. I would suggest that if you're starting out, you should explore simpler examples.
I also chose to use enums. Not something everyone would chose but its my preference for decoding polymorphic types such as these.
You can read my article about dealing with polymorphic types as well as unknown types here: https://medium.com/#jacob.sikorski/awesome-uses-of-swift-enums-2ff011a3b5a5
Im trying to decode this json from the Unsplash API, but the ContentView is coming up as blank, and if I print the results then I am getting the "No value associated with key CodingKeys" error. Its strange, because I'm following this very new tutorial https://www.youtube.com/watch?v=CmOe9vNopjU. I am very puzzled, because this should be simple, but I am new to Swift.
here is the full error
"No value associated with key CodingKeys(stringValue: \"total\", intValue: nil) (\"total\").", underlyingError: nil))
here is the decoding request
class SearchObjectController : ObservableObject {
static let shared = SearchObjectController()
private init() {}
var token = "my client id"
#Published var results = [Result]()
#Published var searchText : String = "Forest"
func search () {
let url = URL(string: "https://api.unsplash.com/search/photos?query=\(searchText)")
var request = URLRequest(url: url!)
request.httpMethod = "GET"
request.setValue("Client-ID\(token)", forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else {return}
do {
let res = try JSONDecoder().decode(Results.self, from: data)
self.results.append(contentsOf: res.results)
print(self.results)
} catch {
print(error)
}
}
task.resume()
}
}
and these are the Structs i created for that request
struct Results : Codable {
var total : Int
var results : [Result]
}
struct Result : Codable {
var id : String
var description : String?
var urls : URLs
}
struct URLs : Codable {
var small : String
}
the json format for Unplash API Requests looks like this
{
"total": 133,
"total_pages": 7,
"results": [
{
"id": "eOLpJytrbsQ",
"created_at": "2014-11-18T14:35:36-05:00",
"width": 4000,
"height": 3000,
"color": "#A7A2A1",
"blur_hash": "LaLXMa9Fx[D%~q%MtQM|kDRjtRIU",
"likes": 286,
"liked_by_user": false,
"description": "A man drinking a coffee.",
"user": {
"id": "Ul0QVz12Goo",
"username": "ugmonk",
"name": "Jeff Sheldon",
"first_name": "Jeff",
"last_name": "Sheldon",
"instagram_username": "instantgrammer",
"twitter_username": "ugmonk",
"portfolio_url": "http://ugmonk.com/",
"profile_image": {
"small": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=32&w=32&s=7cfe3b93750cb0c93e2f7caec08b5a41",
"medium": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=64&w=64&s=5a9dc749c43ce5bd60870b129a40902f",
"large": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=128&w=128&s=32085a077889586df88bfbe406692202"
},
"links": {
"self": "https://api.unsplash.com/users/ugmonk",
"html": "http://unsplash.com/#ugmonk",
"photos": "https://api.unsplash.com/users/ugmonk/photos",
"likes": "https://api.unsplash.com/users/ugmonk/likes"
}
},
"current_user_collections": [],
"urls": {
"raw": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f",
"full": "https://hd.unsplash.com/photo-1416339306562-f3d12fefd36f",
"regular": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&s=92f3e02f63678acc8416d044e189f515",
"small": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&s=263af33585f9d32af39d165b000845eb",
"thumb": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&s=8aae34cf35df31a592f0bef16e6342ef"
},
"links": {
"self": "https://api.unsplash.com/photos/eOLpJytrbsQ",
"html": "http://unsplash.com/photos/eOLpJytrbsQ",
"download": "http://unsplash.com/photos/eOLpJytrbsQ/download"
}
},
// more photos ...
]
}
It's a common practice for a lot of APIs to return a detailed description of the error in the response body together with the error code when the request can't be processed. Seems that it's your case since the JSON parsing fails on the first field of the structure. Check the error and response.statusCode in the dataTask's completion block first to be sure that the API has successfully processed your request and returned valid data.
It was a missing a space between Client-ID and (token) that was causing the error.
How to parse local JSON data where nested (optional) property is same as main.
Items data may be available or may not be available.
struct Category: Identifiable, Codable {
let id: Int
let name: String
let image: String
var items: [Category]?
}
I am using common Bundle extension to parse JSON data.
extension Bundle {
func decode<T: Codable>(_ file: String) -> T {
guard let url = self.url(forResource: file, withExtension: nil) else {
fatalError("Failed to locate \(file) in bundle.")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load \(file) from bundle.")
}
let decoder = JSONDecoder()
let formatter = DateFormatter()
formatter.dateFormat = "y-MM-dd"
decoder.dateDecodingStrategy = .formatted(formatter)
guard let loaded = try? decoder.decode(T.self, from: data) else {
fatalError("Failed to decode \(file) from bundle.")
}
return loaded
}
}
For eg data :
[
{
"id": 1,
"name": "Apple",
"image": "img_url",
"items" : [
{
"id": 1,
"name": "iPhone",
"image": "img_url",
"items" : [
{
"id": 1,
"name": "iPhone 11 Pro",
"image": "img_url"
},
{
"id": 2,
"name": "iPhone 11 Pro Max",
"image": "img_url"
}
]
},
{
"id": 2,
"name": "iPad",
"image": "img_url",
"items" : [
{
"id": 1,
"name": "iPad mini",
"image": "img_url"
},
{
"id": 2,
"name": "iPad Air",
"image": "img_url"
},
{
"id": 3,
"name": "iPad Pro",
"image": "img_url"
}
]
}
]
},
{
"id": 2,
"name": "Samsung",
"image": "img_url",
"items" : [
{
"id": 1,
"name": "Phone",
"image": "img_url"
},
{
"id": 2,
"name": "Tablet",
"image": "img_url"
}
]
}
]
Nesting is not the issue here, You are facing an Array of Contents. so you should pass [Content] to the decoder like:
let jsonDecoder = JSONDecoder()
try! jsonDecoder.decode([Category].self, from: json)
🎁 Property Wrapper
You can implement a simple property wrapper for loading and decoding all of your properties:
#propertyWrapper struct BundleFile<DataType: Decodable> {
let name: String
let type: String = "json"
let fileManager: FileManager = .default
let bundle: Bundle = .main
let decoder = JSONDecoder()
var wrappedValue: DataType {
guard let path = bundle.path(forResource: name, ofType: type) else { fatalError("Resource not found") }
guard let data = fileManager.contents(atPath: path) else { fatalError("File not loaded") }
return try! decoder.decode(DataType.self, from: data)
}
}
Now you can have any property that should be loaded from a file in a Bundle like:
#BundleFile(name: "MyFile")
var contents: [Content]
Note that since the property should be loaded from the bundle, I raised a FatalError. Because the only person should be responsible for these errors is the developer at the code time (not the run time).
I was trying to post the following jSON body
request JSON :
let parameters = [
"createTransactionRequest": [
"merchantAuthentication": [
"name": "xxxxxxxx",
"transactionKey": "xxxxxxxxx"
],
"refId": "123456",
"transactionRequest": [
"transactionType": "authCaptureTransaction",
"amount": "5",
"payment": [
"opaqueData": [
"dataDescriptor": desc!,
"dataValue": tocken!
]
]
]
]
]
When I am trying to print(parameters) the order of node changes it looks like
["createTransactionRequest":
["refId": "123456",
"transactionRequest":
["payment": ["opaqueData": ["dataDescriptor": "COMMON.ACCEPT.INAPP.PAYMENT", "dataValue": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx="]],
"transactionType": "authCaptureTransaction",
"amount": "5"],
"merchantAuthentication": ["name": "xxxxxxx", "transactionKey":
"6gvE46G5seZt563w"]
]
]
I am getting response like
{ messages = {
message = (
{
code = E00003;
text = "The element 'createTransactionRequest' in namespace
'AnetApi/xml/v1/schema/AnetApiSchema.xsd' has invalid child element
'refId' in namespace 'AnetApi/xml/v1/schema/AnetApiSchema.xsd'. List of
possible elements expected: 'merchantAuthentication' in namespace
'AnetApi/xml/v1/schema/AnetApiSchema.xsd'.";
}
);
resultCode = Error;
};
}
This is really annoying. anyones help will be highly grateful.
You need to change your data structure as follow:
struct Transaction: Codable {
let createTransactionRequest: CreateTransactionRequest
}
struct CreateTransactionRequest: Codable {
let merchantAuthentication: MerchantAuthentication
let refId: String
let transactionRequest: TransactionRequest
}
struct MerchantAuthentication: Codable {
let name: String
let transactionKey: String
}
struct TransactionRequest: Codable {
let transactionType: String
let amount: String
let payment: Payment
}
struct Payment: Codable {
let opaqueData: OpaqueData
}
struct OpaqueData: Codable {
let dataDescriptor: String
let dataValue: String
}
Testing
let json = """
{ "createTransactionRequest":
{ "merchantAuthentication":
{ "name": "YOUR_API_LOGIN_ID",
"transactionKey": "YOUR_TRANSACTION_KEY"
},
"refId": "123456",
"transactionRequest":
{ "transactionType": "authCaptureTransaction",
"amount": "5",
"payment":
{ "opaqueData":
{ "dataDescriptor": "COMMON.ACCEPT.INAPP.PAYMENT",
"dataValue": "PAYMENT_NONCE_GOES_HERE"
}
}
}
}
}
"""
let jsonData = Data(json.utf8)
do {
let transaction = try JSONDecoder().decode(Transaction.self, from: jsonData)
print(transaction)
// encoding
let encodedData = try JSONEncoder().encode(transaction)
print(String(data: encodedData, encoding: .utf8)!)
} catch {
print(error)
}
{"createTransactionRequest":{"merchantAuthentication":{"name":"YOUR_API_LOGIN_ID","transactionKey":"YOUR_TRANSACTION_KEY"},"refId":"123456","transactionRequest":{"transactionType":"authCaptureTransaction","amount":"5","payment":{"opaqueData":{"dataValue":"PAYMENT_NONCE_GOES_HERE","dataDescriptor":"COMMON.ACCEPT.INAPP.PAYMENT"}}}}}
I am trying out the new JSONDecoder() in swift 4 and am trying to parse the following flickr API response : (https://api.flickr.com/services/rest/?api_key=api_key&method=flickr.photos.search&format=json&per_page=25&text=hello&nojsoncallback=1). However I get an error saying that "error processing json data: The data couldn’t be read because it isn’t in the correct format." However the data seems to be in the correct format to me. Am I doing anything wrong?
let dataTask = session.dataTask(with: urlRequest) { (data, response, error) in
if let error = error {
print("json error: \(error.localizedDescription)")
return
} else if let data = data {
print(response)
do {
let decoder = JSONDecoder()
print(data)
let flickrPhotos = try decoder.decode(FlickrImageResult.self, from: data)
} catch {
dump(data)
print("json error: \(error.localizedDescription)")
}
}
}
dataTask.resume()
My data models are
struct FlickrImageResult : Codable {
var photos : FlickrPhoto? = nil
}
struct FlickrPhoto : Codable {
var photo : [FlickrURLs]? = nil
}
struct FlickrURLs: Codable {
var id : String? = nil
var owner: String? = nil
var secret: String? = nil
var server: String? = nil
var farm: String? = nil
}
I changed the number of images requested to 2 and Printing out the data returns
Optional({"photos":{"page":1,"pages":120197,"perpage":2,"total":"240393","photo":[{"id":"36729752762","owner":"152440263#N02","secret":"e62ba3e18b","server":"4432","farm":5,"title":"Hello there","ispublic":1,"isfriend":0,"isfamily":0},{"id":"36729384952","owner":"141041947#N06","secret":"bc0e5af630","server":"4380","farm":5,"title":"POST\ud83d\udd25 #891 | Hello Tuesday | Krescendo","ispublic":1,"isfriend":0,"isfamily":0}]},"stat":"ok"})
The only problem with your model is that farm is actually an Int. Here's a more complete version of your model accoirding to the docs (https://www.flickr.com/services/api/flickr.photos.search.html):
struct FlickrImageResult: Codable {
let photos : FlickrPagedImageResult?
let stat: String
}
struct FlickrPagedImageResult: Codable {
let photo : [FlickrURLs]
let page: Int
let pages: Int
let perpage: Int
let total: String
}
struct FlickrURLs: Codable {
let id : String
let owner: String
let secret: String
let server: String
let farm: Int
// let title: String
// If needed, camel-case and use CodingKeys enum
//let ispublic: Int
//let isfriend: Int
//let isfamily: Int
}
let jsonData = """
{
"photos": {
"page": 1,
"pages": 13651,
"perpage": 25,
"total": "341263",
"photo": [
{
"id": "36499638920",
"owner": "55126206#N07",
"secret": "7e82dee0ba",
"server": "4346",
"farm": 5,
"title": "[BREATHE]-Ririko,Sayaka&Chiyoko",
"ispublic": 1,
"isfriend": 0,
"isfamily": 0
},
{
"id": "36724435032",
"owner": "92807782#N04",
"secret": "6d830d4a75",
"server": "4354",
"farm": 5,
"title": "Serendipity Designs # SWANK August 2017",
"ispublic": 1,
"isfriend": 0,
"isfamily": 0
},
{
"id": "36087089863",
"owner": "152685136#N08",
"secret": "a4a3f2fe0a",
"server": "4365",
"farm": 5,
"title": "Hello Kitty Scooter",
"ispublic": 1,
"isfriend": 0,
"isfamily": 0
},
{
"id": "36086949593",
"owner": "151818203#N02",
"secret": "fc1207d373",
"server": "4334",
"farm": 5,
"title": "Hip, Hip! It's Chip!",
"ispublic": 1,
"isfriend": 0,
"isfamily": 0
},
{
"id": "36498504410",
"owner": "148300038#N02",
"secret": "5c7f6ff3e1",
"server": "4391",
"farm": 5,
"title": "Hello Kotti",
"ispublic": 1,
"isfriend": 0,
"isfamily": 0
}
]
},
"stat": "ok"
}
""".data(using: .utf8)!
let flickrPhotos = try! JSONDecoder().decode(FlickrImageResult.self, from: jsonData)
print(flickrPhotos)
P.S: The message error processing json data: The data couldn’t be read because it isn’t in the correct format. is the localized error message, use print(error) instead of print(errorlocalizedDescription) to obtain all the error data available (in your case, it will print that there's an issue when trying to decode the farm key).