Issue Parsing / Decoding JSON from API Endpoint Into Struct Object - json

I am writing a Swift 5.x app using Alamofire 5 to get a list of files from an API I wrote. The API returns the file list as a JSON Data object. I want to then get this data into a struct I created. I am not able to get this working. Here is an example JSON string that my server sends over when you hit the API endpoint:
[{
"ID": "0d37ee7a-39bf-4eca-b3ec-b3fe840500b5",
"Name": "File01.iso",
"Size": 6148
}, {
"ID": "656e0396-257d-4604-a85c-bdd0593290cd",
"Name": "File02.iso",
"Size": 224917843
}, {
"ID": "275fdf66-3584-4899-8fac-ee387afc2044",
"Name": "File04.iso",
"Size": 5549504
}, {
"ID": "1c73f857-56b5-475b-afe4-955c9d2d87fe",
"Name": "File05.iso",
"Size": 15476866871
}, {
"ID": "bfebbca2-49de-43d7-b5d0-3461b4793b62",
"Name": "File06.iso",
"Size": 37254264
}]
I created the following Data Model in swift to hold this:
struct Files: Decodable {
let root: [File]
}
struct File: Decodable, Identifiable {
var id: UUID
var name: String
var size: Int
}
enum CodingKeys: String, CodingKey {
case id = "ID"
case name = "Name"
case size = "Size"
}
Then I used Alamofire 5.x to call the API endpoint and attempt to decode the JSON and place it into the object in question:
func getPackageFilesJSON() {
AF.request("http://localhost:8080/api/v1/list/pkgs").responseDecodable(of: Files.self) { response in
guard let serverFiles = response.value else {
print("Error Decoding JSON")
return
}
let self.serverFilesList = serverFiles
}
}
This fails. If I debugPrint the response I get this for the result:
[Result]: failure(Alamofire.AFError.responseSerializationFailed(reason: Alamofire.AFError.ResponseSerializationFailureReason.decodingFailed(error: Swift.DecodingError.typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil)))))
I have never been great at creating these data models and decoding JSON into them. I am sure I am missing something silly. I am hopeful that someone more knowledgable than me, or a second set of eyes can help me get this working.
Thanks,
Ed

There is no key root in the JSON. The root object is an array
Delete
struct Files: Decodable {
let root: [File]
}
and decode
AF.request("http://localhost:8080/api/v1/list/pkgs").responseDecodable(of: [File].self) { response in ...
and move the CodingKeys enum into the File struct
struct File: Decodable, Identifiable {
var id: UUID
var name: String
var size: Int
enum CodingKeys: String, CodingKey {
case id = "ID"
case name = "Name"
case size = "Size"
}
}

Related

Swift The data couldn’t be read because it isn’t in the correct format

I am new to swift and trying to figure out how to parse JSON to a struct. I am trying to get an image from NASA Mar's Rover Photos.I am trying to follow tutorials online, but can't seem to fix this issue. What am I doing wrong here?
error:
Fatal error: The data couldn’t be read because it isn’t in the correct format.
import Foundation
class API {
class func getImage(_ onSucessus: #escaping ([LatestPhoto]) -> ()){
Constrant.session.dataTask(with: Constrant.request){(data, res, err) in
guard let data = data, err == nil else{
fatalError()
}
do{
let apod = try Constrant.decoder.decode([LatestPhoto].self, from: data)
DispatchQueue.main.async {
onSucessus(apod)
}
}
catch{
fatalError(error.localizedDescription)
}
}.resume()
}
}
Struct
struct LatestPhoto: Identifiable, Codable{
let id = UUID()
let imgSrc: String
let earthDate: String
enum CodingKeys: String, CodingKey {
case imgSrc = "img_src"
case earthDate = "earth_date"
}
}
JSON
{
"latest_photos": [
{
"id": 839114,
"sol": 3127,
"camera": {
"id": 20,
"name": "FHAZ",
"rover_id": 5,
"full_name": "Front Hazard Avoidance Camera"
},
"img_src": "https://mars.nasa.gov/msl-raw-images/proj/msl/redops/ods/surface/sol/03127/opgs/edr/fcam/FRB_675093431EDR_F0880366FHAZ00302M_.JPG",
"earth_date": "2021-05-23",
"rover": {
"id": 5,
"name": "Curiosity",
"landing_date": "2012-08-06",
"launch_date": "2011-11-26",
"status": "active"
}
},
{
"id": 839115,
"sol": 3127,
"camera": {
"id": 20,
"name": "FHAZ",
"rover_id": 5,
"full_name": "Front Hazard Avoidance Camera"
},
"img_src": "https://mars.nasa.gov/msl-raw-images/proj/msl/redops/ods/surface/sol/03127/opgs/edr/fcam/FLB_675093431EDR_F0880366FHAZ00302M_.JPG",
"earth_date": "2021-05-23",
"rover": {
"id": 5,
"name": "Curiosity",
"landing_date": "2012-08-06",
"launch_date": "2011-11-26",
"status": "active"
}
}
]
}
Your JSON format doesn't quite match what you're trying to decode. You need a wrapper for the latest_photos array at the root of your JSON object.
For example:
struct LatestPhotosWrapper: Codable {
let latestPhotos: [LatestPhoto]
enum CodingKeys: String, CodingKey {
case latestPhotos = "latest_photos"
}
}
let apod = try JSONDecoder().decode(LatestPhotosWrapper.self, from: data)
(Rather than providing a CodingKey, you can also look into the built-in systems for converting from snake case: https://developer.apple.com/documentation/foundation/jsondecoder/keydecodingstrategy/convertfromsnakecase)
Also, you may want to print the error and not just the error.localizedDescription -- you can get a better picture of what's going on. For example, with your original code, you get:
Expected to decode Array but found a dictionary instead.
Finally, you might check out app.quicktype.io -- you can paste in your JSON and get correct Swift structs pre-built for you.

Decode JSON with different structures with Swift

I have json data with the same core structure from a nosql database (PK, SK, attributes). The attributes part will be different depending on the value of SK.
Example:
[
{
"PK": "POLL#1544693DF0E88EC-3225-410E-B156-D13781B238F6",
"SK": "#METADATA#1544693DF0E88EC-3225-410E-B156-D13781B238F6",
"attributes": {
"latitude": "53.34589121858683",
"longitude": "-6.272215191675388",
"max_choices": 50,
"number": "1544693",
"poll_open": false,
}
},
{
"PK": "POLL#1544693DF0E88EC-3225-410E-B156-D13781B238F6",
"SK": "CHOICE#00a6ec5c-acc1-40f1-a087-31160d2cfc65",
"attributes": {
"distance": 790.95097525,
"latitude": 53.3416,
"price": "€€",
"categories": [
{
"title": "Ramen",
"alias": "ramen"
}
],
"vote_count": 0,
"longitude": -6.26274
}
}
]
Is it possible to use decode without errors? I've been stuck on this for hours.
I've defined the following:
struct Result: Codable {
var PK: String
var SK: String
var attributes: String
}
But, when I decode, I get the error:
typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "attributes", intValue: nil)], debugDescription: "Expected to decode String but found a dictionary instead.", underlyingError: nil))
I just want to decode 'attributes' as a generic string and parse it later depending on the value of SK when I know how to properly handle it. Why is this so difficult?
Do you need attributes right now? Or are you just looking for pk and sk? If you do not need it just do not include
var attributes: String
in your struct. It will not have a decode error and it will decode the other two, you just will not get the attributes parameter. It is not able to decode the attributes
as a string because it is not. It is really more like a dictionary. Swift does not know how to handle that unless you specifically tell it. That being said you could always do this
struct Result: Codable {
var PK: String
var SK: String
var attributes: Attributes
}
struct Attributes: Codable {
var lattitude: String
var longitude: String
//and whatever else you know you have
}
the key is only adding the values you know will be included in the attributes or else it will give an error
You need to handle this using JSONSerialization instead of Codable. Given you still want to use the same struct you need to change it to
struct Result {
var PK: String
var SK: String
var attributes: [String: Any]
}
and decode the json like this
var result: Result?
do {
if let dictionary = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let pk = dictionary["PK"] as? String,
let sk = dictionary["SK"] as? String,
let attributes = dictionary["attributes"] as? [String: Any] {
result = Result(PK: pk, SK: sk, attributes: attributes)
}
} catch {
print(error)
}
Now you still need to convert the attributes ([String: Any]) property of Result to something more usable but that is beyond this question

How should I make the struct to get this JSON data in SwiftUI?

I am making an HTTP GET request and I want to save the JSON response that looks like this:
{
"code": 200,
"status": "success",
"patients": [
{
"_id": "5e77c7bbc7cbd30024f3eadb",
"name": "Bogdan Patient",
"username": "bogdanp",
"phone": "0732958473"
},
{
"_id": "5e77c982a2736a0024e895fa",
"name": "Robert Patient",
"username": "robertp",
"phone": "0739284756"
}
]
}
And here is my struct:
struct Doctor: Codable, Identifiable {
let id = UUID()
let patients: [Patients]
}
struct Patients: Codable {
let id: String
let name: String
let phone: String
}
As per your model, id is expected in the JSON whereas the keyname in the JSON is _id.
You can use CodingKeys to fix this:
struct Patients: Codable {
let id: String
let name: String
let phone: String
enum CodingKeys: String, CodingKey {
case id = "_id"
case name
case phone
}
}
CodingKeys creates a map between the keynames in your model and the keynames in the JSON response.
There are other reasons to use CodingKeys but for your current purpose this is enough.
Read More: Codable in Swift

Swift how to express JSON in structs

I want to express the following JSON and convert to swift structs
1) I get error in the third line full_plan "comma is missing". I don't know why a comma is required? I need help fixing it
2) If that is fixed will the structs shown below is accurate to convert to JSON?
Please note: add_ons may be missing in the JSON for some plans, so second plan shown does not have add_ons.
Basically I am asking help to fix the JSON and the struct for swift.
{
"id": "100",
"plans":
[
"full_plan":
{
"plan":
[
{ "plan_type": "Legacy" },
{ "contract_duration_months": "12" }
],
"add_ons" :
[
{ "parking": "yes"},
{ "washerDryer": "no" }
]
},
"full_plan":
{
"plan":
[
{ "plan_type": "New" },
{ "contract_duration_months": "0" }
]
}
]
}
struct TopPlan : Decodable {
var uniqueId: String?
var Plans: [FullPlan]?
enum CodingKeys : String, CodingKey {
case uniqueId = "id"
case Plans = "plans"
}
}
struct FullPlan: Decodable {
var Plan: PlanJSON?
var freePlan: AddOnsJSON?
enum CodingKeys : String, CodingKey {
case pricedPlan = "plan"
case freePlan = "add_ons"
}
}
struct PlanJSON: Decodable {
var planType: String?
var duration: String?
enum CodingKeys : String, CodingKey {
case planType = "plan_type"
case duration = "contract_duration_months"
}
}
struct AddOnsJSON: Decodable {
var parking: String?
var washerDryer: String?
enum CodingKeys : String, CodingKey {
case parking = "parking"
case washerDryer = "washerDryer"
}
}
Short answer: your current JSON is invalid syntax.
You are using "full_plan" as a key (which would be fine if "plans" was an object) inside an array. Arrays in JavaScript (and thus in JSON) are unkeyed. You should either remove "full_plan" and just use the object that it refers to like "plans": [{}, {}, etc], or if you need to keep the object key wrap the entire item in curly braces such as "plans": [{ "full_plan": {}}, { "full_plan": {}}, etc]
You should build this up from the bottom (and if you post your question in a form that is executable in a Playground you will get answers faster). As it stands your plan JSON is unfortunate. Your struct states that you want a hash and you provide it with an array of hashes (which should be merged to get what you want). Try it like this:
import Cocoa
let jsonData = """
{ "plan_type": "Legacy",
"contract_duration_months": "12"
}
""".data(using: .utf8)!
struct PlanJSON: Decodable {
var planType: String?
var duration: String?
enum CodingKeys : String, CodingKey {
case planType = "plan_type"
case duration = "contract_duration_months"
}
}
do {
let plan = try JSONDecoder().decode(PlanJSON.self, from: jsonData)
print(plan)
} catch {
print(error)
}
That way you will be given enough information to further fix your JSON, but the rest looks ok.

Nested Swift 4 Codable Struct uses CodingKeys irregularly

I have some JSON that I'm trying to decode with a Swift struct that conforms to the Codable protocol. The main struct doesn't seem to want to recognize the codingKey alias for thread_type, but happily consumes the explicitly named thread_id attribute. The struct in question is below:
struct Message: Codable {
var id: String?
var type: String?
var thread_id: String?
var threadType: String?
var sender: User?
var body: String?
var media: String?
var sentAt: Double?
var unread: Bool?
var status: String?
var url: String?
enum codingKeys: String, CodingKey {
case id
case type
case thread_id
case threadType = "thread_type"
case sender
case body
case media
case sentAt = "sent_at"
case unread
case status
case url
}
}
The JSON that I'm trying to parse:
let json =
"""
{
"id": "Jvbl6LY",
"type": "sms",
"thread_id": "60LrVL7",
"thread_type": "578a",
"delay_until": null,
"sender": {
"id": "EVkdNBx",
"type": "user",
"first_name": "Jerry",
"last_name": "Ward",
"mobile_number": "123-456-7890",
"profile_image_url": "",
"is_online": false,
"email": "jerryw+demo#jypsee.com"
},
"body": "Here is a picture of our Coquille Suite. Let me know if you would like a reservation?",
"media": "",
"sent_at": 1509133604000.167,
"unread": false,
"status": "",
"url": "https://connect-staging.jypsee.com/api/sms/threads/60LrVL7/history/Jvbl6LY/"
}
"""
And finally the decoder call itself:
let decoder = JSONDecoder()
let data = json.data(using: .utf8)!
do {
let message = try decoder.decode(Message.self, from: data)
print(message.thread_id)
print(message.threadType)
print(message.sender?.firstName)
print(message.sender?.lastName)
} catch {
print(error)
}
The message.thread_id prints Optional("60LrVL7")\n" which is expected.
The message.threadType prints nil\n, which is not expected. Even more bizarre is the fact that message.sender?.firstName and message.sender?.lastName both print "Optional("Jerry")\n" and "Optional("Ward")\n" respectively. Which means the nested User Codable Struct CodingKey IS working. I'm really at a loss as to why there's such an inconsistency in decoding.
Xcode Playground Gist is available here
Apple doc clearly states in the first sentence on the paragraph below. A special enumeration named 'CodingKeys' is necessary. (I had the similar problem and took me quite some time to find out).
https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types