Swift 5 JSON Import with CodingKeys and ENUM values - swift

Reading in from small JSON file (cut/pasted small sample) and bring it into structs. I am using CodingKeys to change the "string key" to match what I want in the struct. I am changing the "whoresponds" key to "respondingPilot". That is all working fine. However, I also want to look at the values on that property and change on-the-fly the value brought in. For example, if I get the string "BOTH", I want to change my data that is stored to "CAPT & F/O". Same if I see "FO", I want that changed to "F/O" as it read in. Sample below reads in fine but will not make the changes. Still learning but must be something simple I am missing. Thanks!
struct CheckListsJSON: Codable {
var name: String
var checklistItems: [ChecklistItemsJSON]
}
struct ChecklistItemsJSON: Codable, Identifiable {
var challenge: String
var respondingPilot: PilotResponding
let id = UUID()
private enum CodingKeys: String, CodingKey {
case challenge
case respondingPilot = "whoresponds"
}
enum PilotResponding: String, Codable {
case CPT = "CAPT"
case FO = "F/O"
case PF = "PF"
case PM = "PM"
case BOTH = "CAPT & F/O"
}
}
let jsonAC = "{\"name\": \"After Start\", \"checklistItems\": [{\"challenge\": \"Generators\", \"whoresponds\": \"CPT\"}, {\"challenge\": \"Isolation Valve\", \"whoresponds\": \"FO\"}}"
let fileData = Data(jsonAC.utf8)
do {
let decodedData = try JSONDecoder().decode(CheckListsJSON.self, from: fileData)
print("decoded:", decodedData)
} catch {
print(error)
}

Decoding a string to an enum works like CodingKeys. The raw value is the string you receive and the case is the case.
Unfortunately the character set of enum cases is restricted. Space characters, / and & are not allowed.
You could write something like this, raw values which match the case can be omitted.
enum PilotResponding: String, Codable {
case CAPT = "CPT"
case F_O = "FO"
case PF, PM
case CAPT_F_O = "BOTH"
}

Related

Cannot iterate through array inside array of dictionaries

I'm having trouble parsing a response into workable objects in swift.
Basically, this is the response I get (simplified data for viewing purposes)
"[{\"CO2\":0,\"Places\":[{\"Name\":\"RT(Esc. Sec.)\",\"Code\":\"ST_RT\",\"Type\":0,\"CoordX\":41.176750183105469,\"CoordY\":-8.5490522384643555,\"Provider\":\"ST\",\"Lines\":null},{\"Name\":\"Esc.Sec RT\",\"Code\":\"ST_RT2\",\"Type\":0,\"CoordX\":41.175251007080078,\"CoordY\":-8.54929256439209,\"Provider\":\"ST\",\"Lines\":null},{\"Name\":\"SM\",\"Code\":\"ST_SM\",\"Type\":0,\"CoordX\":41.173740386962891,\"CoordY\":-8.5474367141723633,\"Provider\":\"ST\",\"Lines\":null}],\"Direction\":\"R\"}]"
After I receive the response I do the following:
let dict = try! JSONSerialization.jsonObject(data: responseData!, options: .allowFragments) as? [[String:Any]] ?? [[String:Any]]()
Which results in the following dictionary (I'm sorry for the picture, but I could not take a print screen. Plus those 44 elements are the total number of Places, but I've simplified the response string as I said above)
My problem is, I cannot access each Place dictionary. I've tried iterating through dict["Places"] but that does not seem to work, which I do not understand, given it is an NSArray.
I may be missing something simple, but I can't seem to figure it out.
Any help is welcome.
Thanks!
You can try
// MARK: - Root
struct Root: Codable {
let co2: Int
let places: [Place]
let direction: String
enum CodingKeys: String, CodingKey {
case co2 = "CO2"
case places = "Places"
case direction = "Direction"
}
}
// MARK: - Place
struct Place: Codable {
let name, code: String
let type: Int
let coordX, coordY: Double
let provider: String
let lines: String?
enum CodingKeys: String, CodingKey {
case name = "Name"
case code = "Code"
case type = "Type"
case coordX = "CoordX"
case coordY = "CoordY"
case provider = "Provider"
case lines = "Lines"
}
}
let res = try JSONDecoder().decode([Root].self,from:data)
print(res.places)

Assign dynamically properties of a Struct in Swift

I have this Struct:
struct Alphabet {
let a = "ciao"
let b = "hi"
let c = "hola"
}
let alphabet = Alphabet()
I want the value of each property become the string of the property itself.
Like this:
alphabet.a = "a"
alphabet.b = "b"
alphabet.c = "c"
But I want to be done regardless of the number of properties or their value:
I tried this:
Mirror(reflecting: Alphabet.self).children.forEach { (label, value) in
self.alphabet[keyPath: label] = label!
}
but I know this is not the way KeyPath works...
Probably there are type safety problems going on too.
Any idea?
As far as I know keyPaths aren't the way to go, you need to use CodingKeys
Here's a working sample, creating JSON and then decoding it might not be the perfect so you better change my solution to suit your needs.
struct Alphabet: Codable {
let a: String
let b: String
let c: String
enum CodingKeys: String, CodingKey, CaseIterable
{
case a
case b
case c
}
static func generateJSON() -> String {
var json = "{"
for i in CodingKeys.allCases
{
json += "\"\(i.stringValue)\": \"\(i.stringValue)\","
}
json.removeLast()
json += "}"
return json
}
}
let decoder = JSONDecoder()
let alphabet = try decoder.decode(Alphabet.self, from: Alphabet.generateJSON().data(using: .utf8)!)
print(alphabet.a) //Prints "a"

Decoding numerical snake_case keys with JSONDecoder

I have the following JSON object which is to be converted to an object using JSONDecoder:
{
"first_key": 3,
"image_1000x1000": "location"
}
This maps to the following Swift model:
struct Response: Decodable {
var firstKey: Int
var image1000x1000: String
}
By using JSONDecoder with the .convertFromSnakeCase option, the snake_case keys inside the JSON are transformed to camelCase by using the algorithm defined in the documentation:
This strategy follows these steps to convert JSON keys to camel-case:
Capitalize each word that follows an underscore.
Remove all underscores that aren't at the very start or end of the string.
Combine the words into a single string.
Therefore, in this case:
first_key becomes firstKey (as expected)
image_1000x1000 should become image1000x1000
However when attempting to decode this response, a keyNotFound error for the image1000x1000 key is thrown (see this live example):
let json = "{\"first_key\": 3, \"image_1000x1000\": \"location\"}".data(using: .utf8)!
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let response = try decoder.decode(Response.self, from: json)
print(response)
} catch let e {
print(e)
}
What is incorrect about my camel case conversion of image_1000x1000, and why can’t JSONDecoder find the corresponding key?
You can see what the algorithm is expecting by running the process in reverse; use a JSONEncoder to encoder your data and inspect the output:
struct Response: Codable {
var firstKey: Int
var image1000x1000: String
}
let test = Response(firstKey: 10, image1000x1000: "secondKey" )
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
let data = try encoder.encode(test)
print(String(data: data, encoding: .utf8)!)
This will produce:
{"first_key":10,"image1000x1000":"secondKey"}
So if you have control over the JSON and can live with image1000x1000 as a key, then you're done. If not you'll have to do something like this:
struct Response: Codable {
var firstKey: Int
var image1000x1000: String
private enum CodingKeys: String, CodingKey {
case image1000x1000 = "image_1000x1000"
case firstKey = "first_key"
}
}
Another option is to implement a custom key encoding strategy. It may end up being less code. See KeyEncodingStrategy for more about this.
I am facing the same issue my json keys are iso639_1 and iso639_2. Without adding Coding key it will works.
make your variable optional
Here it's my Decodable model
struct CountryModel: Decodable{
var name: String
var borders: [String]
var region: String
var alpha2Code: String
var alpha3Code: String
var flag: String
var languages: [languages]
}
struct languages: Decodable {
var iso639_1: String?
var iso639_2: String?
var name: String
var nativeName: String
}
When I add optional to both underscore variables iso639_1 and iso639_2. Then it works fine may be due to Null Value!
Here, in your case add optional to your image1000x1000 variable. Like below
struct Response: Decodable {
var firstKey: Int
var image_1000x1000: String?
}
Hope it will works!

unable to convert Enum to string from Json in swift

I get the error:
Cannot initialize Role from invalid String value Mage
when I tried to interpret an array of string as enum type from a JSON file.
struct ChampionsData : Decodable{
let id : String
let key : String
let info : Info
let tags : [Role]
}
enum Role : String, CaseIterable, Decodable{
case Tank = "you believe that last person standing wins"
case Mage = "you like fantacies and tricking people"
case Assasin = "you enjoy living with danger"
case Fighter = "you are the warrior that built this town"
case Support = "you are a reliable teammate that always appears where you are needed "
case Marksman = "you tend to be the focus of the game, or the reason of victory or loss"
enum CodingKeys: String, CodingKey {
case mage = "Mage"
case assassin = "Assassin"
case tank = "Tank"
case fighter = "Fighter"
case support = "Support"
case marksman = "Marksman"
}
}
How can I parse it to a JSON object if I want to interpret tags as an array of Role enum type instead of an array of strings(or get rid of the error)?
Your JSON must be something like this
let jsonString = """
{
"id" :"asda",
"key" : "key asd",
"tags" : [
"Mage",
"Marksman"
]
}
"""
NOTE: I'm ignoring let info : Info here.
And from this string enum should be Mage, Marksman.. so on
But you have added them to be
case Mage = "you like fantacies and tricking people"*
In Enum raw values are implicitly assigned as CodingKeys
Update your code to this
enum Role : String, Decodable {
case tank = "Tank"
case mage = "Mage"
case assasin = "Assassin"
case fighter = "Fighter"
case support = "Support"
case marksman = "Marksman"
var value: String {
switch self {
case .tank:
return "you believe that last person standing wins"
case .mage:
return "you like fantacies and tricking people"
case .assasin:
return "you enjoy living with danger"
case .fighter:
return "you are the warrior that built this town"
case .support:
return "you are a reliable teammate that always appears where you are needed"
case .marksman:
return "you tend to be the focus of the game, or the reason of victory or loss"
}
}
}
Then you can just use the value after decoding like this
let data = jsonString.data(using: .utf8)!
// Initializes a Response object from the JSON data at the top.
let myResponse = try! JSONDecoder().decode(ChampionsData.self, from: data)
print(myResponse.tags.first?.value as Any)
If we used the json mentioned in the start we will get
"you like fantacies and tricking people"

Swift Codable:Nested Dictionary not being read

I am getting some json like so (this is pseudo, not all keys are here):
{
"absolute_magnitude_h" = "23.4";
...
"close_approach_data" = (
{
"close_approach_date" = "1994-09-03";
"epoch_date_close_approach" = 778575600000;
"orbiting_body" = Earth;
}
I have an object with this struct:
struct NEOObj:Codable {
var absoluteMagnitudeH:Float
var designation:String
var isPotentiallyHazardousAsteroid:Bool
var isSentryObject:Bool
var name:String
var nasaJPLURL:String
var neoReferenceID:String
var closeApproachData:[NEOCloseApproachData] = [NEOCloseApproachData]()
private enum CodingKeys: String, CodingKey {
case absoluteMagnitudeH = "absolute_magnitude_h"
case designation = "designation"
case isPotentiallyHazardousAsteroid = "is_potentially_hazardous_asteroid"
case isSentryObject = "is_sentry_object"
case name = "name"
case nasaJPLURL = "nasa_jpl_url"
case neoReferenceID = "neo_reference_id"
}
enum CloseApproachCodingKeys: String, CodingKey {
case closeApproachDate = "close_approach_date"
case epochDateCloseApproach = "epoch_date_close_approach"
case orbitingBody = "orbiting_body"
}
struct NEOCloseApproachData:Codable {
var closeApproachDate:Date
var epochDateCloseApproach:Date
var orbitingBody:String
enum CodingKeys: String, CodingKey {
case closeApproachDate = "close_approach_date"
case epochDateCloseApproach = "epoch_date_close_approach"
case orbitingBody = "orbiting_body"
}
}
and in my file I have this code:
if let arrNEOs = dictJSON["near_earth_objects"] as? Array<Any> {
for thisNEODict in arrNEOs {
do {
let jsonData = try JSONSerialization.data(withJSONObject: thisNEODict, options: .prettyPrinted)
let thisNEOObj = try? JSONDecoder().decode(NEOObj.self, from: jsonData)
print(thisNEOObj!.closeApproachData)
} catch {
}
}
}
But closeApproachData never gets populated. What am I doing wrong?
There are multiple things that would need to be changed for it to work.
closeApproachData should probably be of type [NEOCloseApproachData]. You haven't included CloseApproachCodingKeys in your code but it probably isn't right.
NEOObj.CodingKeys needs to have a case for closeApproachData which would look like
case closeApproachData = "close_approach_data"
NEOCloseApproachData needs a custom decoder to work with the dates for closeApproachDate and epochDateCloseApproach. Since the JSON has different formats for each (String and Int respectively), you can't use on the JSONDecoder.dateDecodingStrategy since it will apply to all dates.
The reason why after decoding you get closeApproachData property empty is the lack of the regarding key for that property into your CodingKeys enum.
Regarding to the Apple doc, it tells next:
Omit properties from the CodingKeys enumeration if they won't be
present when decoding instances, or if certain properties shouldn't be
included in an encoded representation.
That means that generated implementation of decode method for your NEOObj class omit decoding implementation for the closeApproachData property.
Here's a link at the Apple documentation about Encoding and Decoding Custom Types. You can find more details at the "Choose Properties to Encode and Decode Using Coding Keys" paragraph.
Also your custom class NEOCloseApproachData should be conformed to the Coding protocol.