How to sum a particular key in an Array Object Swift 4? - ios

This response from API:
{
"status": 200,
"message": "Success",
"data": {
"result": [
{
"name": "Anything",
"response": [
{
"name": "XYZ",
"prize": "1.86"
},
{
"name": "ABCD",
"prize": "9.86"
}
]
}
],
"overall": "XYZ"
}
}
How, can I sum the prize in the response as I have to show it in the header of Table. I did this.
var prizeArr = [Int]()
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: cellReuseIdentifier) as! OverallHeaderCell
let countArr = winnerArr![section]["response"] as? Array<Dictionary<String,Any>>
prizeArr = (countArr!.compactMap{ $0["prize"] })
print(prizeArr)
header.textL[0].text = winnerArr![section]["name"] as? String
header.textL[3].text = "\(String(describing: countArr!.count))"
return header
}
I am trying to store the prize in the prizeArr and then I will sum it up. But, it prints nil, but when I write it in the terminal it gives the value individually. How, can I sum the prize in this ArrayObject?
Also, when I use prizeArr = [String](), then it works and store the prize in the Array, but for Int why is it not working?

First, as #Benhamine pointed out you should start with a clean architecture and map your JSON into a type safe class.
Step 1: Codable Types
Lets define a structure that represents our JSON so we can better consume it in our App. JSON is something we never want to pass around our App. Lets rather pass around something well-defined and documented so we have no need to do any force unwraps and crash our App.
struct JSONResponse: Codable {
enum CodingKeys: String, CodingKey {
case data
}
let data: Data
}
extension JSONResponse {
struct Data: Codable {
enum CodingKeys: String, CodingKey {
case results = "result"
}
let results: [Result]
}
}
extension JSONResponse.Data {
struct Result: Codable {
let name: String
let winners: [Winner]
enum CodingKeys: String, CodingKey {
case winners = "response"
case name
}
}
}
extension JSONResponse.Data.Result {
struct Winner: Codable {
let name: String
let prize: String
}
}
Step 2: Parsing
Parsing using Codable is super simple. The code below will show how we convert it into JSON and also how one could go about getting the sum of the float values.
do {
let o: JSONResponse = try JSONDecoder().decode(JSONResponse.self, from: jsonData)
let floatValues = o.data.results.flatMap({ $0.winners }).compactMap({ Float($0.prize) })
floatValues.reduce(0, +)
print(floatValues)
} catch let e {
print(e)
}
Step 3: Integrate
We now have the building blocks we need to get this information so let's hook it up to your code by starting with what we want our code to look like.
/// We store our main data type for easy reference
var resultsBySection: [Result]
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: cellReuseIdentifier) as! OverallHeaderCell
let currentResults = resultsBySection[section]
let prizeTotals = currentResults.flatMap({ $0.winners }).compactMap({ Float($0.prize) })
let totalPriceMoney = prizeTotals.reduce(0, +)
header.textL[0].text = currentResults.name
header.textL[3].text = "\(String(describing: totalPriceMoney))"
return header
}
Notice how in the above code I do not do any JSON decoding in cell dequeueing. Ideally that should be done when we retrieve our JSON and convert it to our types.
Step 4: Refactor
An essential piece of any code experience should contain some reflection into the code we have written and consider how it could be refactored.
In our example above we could probably hardcode the totals onto the controller or create a custom data structure that will do this for us when we parse JSON. Do we always want to manually calculate the totals if we always need this total? We could have a custom function to do the calculation or just do it in our JSON decoding logic.
Anyways, the idea is that we should always look at what we are writing and question how it could be improved

Try this Codable solution to reduce the prizes together:
struct Winners: Codable {
let status: Int
let message: String
let data: DataClass
}
struct DataClass: Codable {
let result: [Result]
let overall: String
}
struct Result: Codable {
let name: String
let response: [Response]
}
class Response: Codable {
let name: String
let prize: Double
init(name: String, prize: Double) {
self.name = name
self.prize = prize
}
enum CodingKeys: String, CodingKey {
case name
case prize
}
enum SerializationError: Error {
case missing(String)
case invalid(String, Any)
}
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
let prizeString = try container.decode(String.self, forKey: .prize)
guard let prize = Double(prizeString) else {
throw SerializationError.invalid("prize", prizeString)
}
self.prize = prize
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode("\(prize)", forKey: .prize)
}
}
func testing() {
let winners = try! JSONDecoder().decode(Winners.self, from: jsonData)
let sum = winners.data.result[section].response
.map({ $0.prize })
.reduce(0, +)
print(sum)
}

let sum = winnerArr?[section]["response"].reduce(0, { x, y in
x + y["prize"]
}) ?? 0
As an aside, I would recommend parsing the response and turning it into usable objects rather than dealing with the raw response : https://developer.apple.com/swift/blog/?id=37

Thanks to all of your guys, who helped. I am a newbie and not familiar with Codable. Thanks for introducing me to the same. I will try to understand it and use it for future purposes.
So, coming back to the answer:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
self.prizeArr.removeAll()
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: cellReuseIdentifier) as! OverallHeaderCell
let resArr = self.winnerArr![section]["response"]! as? Array<Dictionary<String,Any>>
for prize in resArr!
{
let doubleStr = prize["prize"] as? NSString
self.prizeArr.append((doubleStr?.doubleValue)!)
}
let sumedArr = prizeArr.reduce(0, +)
let countArr = winnerArr![section]["response"] as? Array<Dictionary<String,Any>>
header.textL[0].text = winnerArr![section]["name"] as? String
header.textL[1].text = "$\(sumedArr)"
header.textL[3].text = "\(String(describing: countArr!.count))"
return header
}
Here I am converting each string to a double and appending it to the prizeArr and then, finally, summing the entire array to get the desired result. But, this is not the ideal way to do it. This answer is for all the newbies like me and please learn Codables.

Related

Swift Decodable parse part of JSON

I'm trying to parse a json that looks like this using decodable:
{
"count": 1,
"results": [
{
"title": 1,
"content": "Bla"
} ]
}
My problem is that I don't want to make a class that has a count property just to be able to use the decoder. I want to parse only the results part I don't care about the count.
So my question is, can decodable.decode somehow only parse a part of the result json. I mean a certain key path instead of the whole json ? And I want to do it using Decodable.
In a nutshell I don't want this:
class IncidentWrapper: Codable{
var count: Int
var incident: [Incident]
}
What I would Imagine is to have this:
decodable.decode([Incident].self, from: response.data, forKey: "results")
Thanks
let me see what I can suggest:
struct Result: Codeable {
var id: Int
var message: String
var color: String
var type: String
enum CodingKeys: String, CodingKey {
case results
}
enum NestedResultKeys: String, CodingKey {
case id, message, color, type
}
}
extension Result: Decodable {
init(from decoder: Decoder) throws {
let result = try decoder.container(keyedBy: CodingKeys.self)
let nestedResult = try result.nestedContainer(keyedBy: NestedResultKeys.self, forKey: .result)
id = try nestedResult.decode(Int.self, forKey: .id)
message = try nestedResult.decode(String.self, forKey: .message)
color = try nestedResult.decode(String.self, forKey: .color)
type = try nestedResult.decode(String.self, forKey: .id)
}
}
See this documentation for more insight
https://developer.apple.com/documentation/swift/swift_standard_library/encoding_decoding_and_serialization
Hope it helps your project!
You probably is looking for JSONSerialization class. This is an example how it works:
if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any] {
if let results = json["results"] as? [Incident] {
print(results.count)
}
}
You can define a generic wrapper for once and use everywhere.
It will work as a generic wrapper for results key only.
protocol ResultsDecodable: Decodable{
associatedtype T: Decodable
var results: [T] {get}
}
struct Result<Element: Decodable>: ResultsDecodable{
typealias T = Element
var results: [Element]
}
Extend JSONDecoder to get results output.
extension JSONDecoder {
func resultDecode<T>(_ type: Result<T>.Type, from data: Data) throws -> [T] where T : Decodable{
let model = try! decode(type, from: data)
return model.results
}
}
And use like this
var str = #"{"count": 1,"results": [{"title": 1,"content": "Bla"}, {"title": 2,"content": "Bla"} ]}"#
class Incident: Decodable{
let title: Int
let content: String
}
let indicents = (try! JSONDecoder().resultDecode(Result<Incident>.self, from: str.data(using: .utf8)!))
See how it makes everything more complex. BETTER USE IncidentWrapper!!!
You only need to use the keys you care about. Just leave off the count. Don't make it part of your struct.
You will only get errors if you can't find a key in the json that you are expecting in the struct. You can avoid this too, though, if you make it an optional in the struct.

How to store multiple back dates in a Swift Struct?

I would like to store the previous 4 days closing event in an individual struct so that i can make reference to them later on in the program. How would you go about storing the the closing event for each 4 days after sorting them from the JSON API.
The code below has sorted the previous 4 days but i am unable to figure how to store each day to use them separately
class DailyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let jsonUrlString = "https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=MSFT&apikey=demo"
let urlObj = URL(string: jsonUrlString)
URLSession.shared.dataTask(with: urlObj!) {(data, response, error) in
guard let data = data else { return }
do {
let forex = try JSONDecoder().decode(Root.self, from: data)
let sortedKeys = forex.timeSeriesDaily.keys.sorted(by: >)
let requestedKeys = sortedKeys.prefix(4)
var requestedPrices = [String:Forex]()
requestedKeys.forEach{ requestedPrices[$0] = forex.timeSeriesDaily[$0] }
print(requestedPrices)
print()
} catch {
print(error)
}
}.resume()
}
struct Root: Codable {
let metaData: [String: String]
let timeSeriesDaily: [String:Forex]
enum CodingKeys: String, CodingKey {
case timeSeriesDaily = "Time Series (Daily)"
case metaData = "Meta Data"
}
}
struct Forex: Codable {
let open, high, low, close: String
enum CodingKeys: String, CodingKey {
case open = "1. open"
case high = "2. high"
case low = "3. low"
case close = "4. close"
}
}
}
One way is to create a struct with four properties for this and add a specific init that takes an array
struct LastFour {
var close1: String
var close2: String
var close3: String
var close4: String
init?(_ closes: [String]) {
guard closes.count >= 4 else {
return nil
}
close1 = closes[0]
close2 = closes[1]
close3 = closes[2]
close4 = closes[3]
}
}
and then use map when initialising the struct from the dictionary
let lastFour = LastFour(requestedPrices.values.map {$0.close})
Note that the init is optional and returns nil in case the array is to short, another option could be to throw an error for instance.
Maybe a more flexible solution would be to use an array internally in the struct and then access the data via a method or perhaps computed properties
struct LastFour {
private var closeEvents: [String]
func close(at index: Int) -> String {
}
}
This would of course require similar code for init and checking the correct size but it would be easier to change if more or less elements are needed
My suggestion is to create another struct with the date and the close price
struct CloseData {
let date, price : String
}
and populate it
do {
let forex = try JSONDecoder().decode(Root.self, from: data)
let sortedKeys = forex.timeSeriesDaily.keys.sorted(by: >)
let requestedKeys = sortedKeys.prefix(4)
let requestedPrices = requestedKeys.map{ CloseData(date: $0, price: forex.timeSeriesDaily[$0]!.close) }
The result is an array of CloseData items

how to add Json value into model Array to display into tableview in swift

I'm using the tableview to display the Two Json value but the problem is I cant add value into model struct to displaying into tableview using two Api's. i want to show percentage value in one of the cell label and
here is my json
[
{
"Percentage": 99.792098999,
}
]
my second json value
{
"Categories": [
"Developer",
"ios "
],
"Tags": [
{
"Value": "kishore",
"Key": "Name"
},
{
"Value": "2",
"Key": "office"
},
]
}
and i need show the Categories value in Categories label in tableview
value and key on tableview
here is my Struct
struct info: Decodable {
let Categories: String?
let Tags: String?
let Value: String?
let Key: String?
var Name: String?
let percentage: Double?
}
here its my code
var List = [info]()
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers)
print(json as Any)
guard let jsonArray = json as? [[String: Any]] else {
return
}
print(jsonArray)
for dic in jsonArray{
guard let per = dic["percentage"] as? Double else { return }
print(per)
}
and second json
if let array = json["Tags"] as? [[String: String]] {
for dict in array {
let key = dict["Key"]
let value = dict["Value"]
switch key {
case "office":
case "Name":
default:
break;
}
}
here is my cell for row indexpath
cell.Categories.text = list[indexpath.row].percentage
cell.Name.text = list[indexpath.row].name
cell.office.text = list[indexpath.row].office
Please use Swift 4 Codable protocol to decode the value from JSON.
//1.0 Create your structures and make it conform to Codable Protocol
struct Tags: Codable{
var Key: String
var Value: String
}
struct Sample: Codable{
var Categories: [String]
var Tags: [Tags]
}
In your method, perform below steps:
//2.0 Get your json data from your API. In example below, i am reading from a JSON file named "Sample.json"
if let path = Bundle.main.path(forResource: "Sample", ofType: "json") {
do {
let jsonData = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
do {
//3.0 use JSONDecoder's decode method to decode JSON to your model.
let sample = try JSONDecoder().decode(Sample.self, from: jsonData)
//4.0 User the "sample" model to do your stuff. Example, printing some values
print("Sample.Category = \(sample.Categories)")
print("Sample.Name = \(sample.Tags[0].Value)")
print("Sample.Office = \(sample.Tags[1].Value)")
} catch let error {
print("Error = \(error)")
}
} catch {
// handle error
}
}
I prefer to use Codable all the time with JSON even for simpler types so for percentage I would do
struct ItemElement: Decodable {
let percentage: Double
enum CodingKeys: String, CodingKey {
case percentage = "Percentage"
}
}
and we need to keep these values in a separate array, declared as a class property
let percentageList: [Double]()
and json encoding would then be
let decoder = JSONDecoder()
do {
let result = try decoder.decode([ItemElement].self, from: data)
percentageList = result.map { item.percentage }
} catch {
print(error)
}
Similar for the second part
struct Item: Decodable {
let categories: [String]
let tags: [Tag]
enum CodingKeys: String, CodingKey {
case categories = "Categories"
case tags = "Tags"
}
}
struct Tag: Decodable {
let value, key: String
enum CodingKeys: String, CodingKey {
case value = "Value"
case key = "Key"
}
}
use a dictionary for the result, again as a class property
var values = [String: String]()
and the decoding
let decoder = JSONDecoder()
do {
let result = try decoder.decode(Item.self, from: data)
for item in result.tags {
values[item.key] = values.item.value
}
} catch {
print(error)
}
and then in the cell for row code
cell.Categories.text = percentageList[indexpath.row].percentage
cell.Name.text = values["name"]
cell.office.text = values["office"]
Note that this last code looks very strange since you don't have an array of name/office values judging by your json. Maybe you have simplified it some way but the code above is the best I can do with the information given even if it possibly wrong

Confirm enum with Decodable protocol [duplicate]

enum PostType: Decodable {
init(from decoder: Decoder) throws {
// What do i put here?
}
case Image
enum CodingKeys: String, CodingKey {
case image
}
}
What do i put to complete this?
Also, lets say i changed the case to this:
case image(value: Int)
How do I make this conform to Decodable?
Here is my full code (which does not work)
let jsonData = """
{
"count": 4
}
""".data(using: .utf8)!
do {
let decoder = JSONDecoder()
let response = try decoder.decode(PostType.self, from: jsonData)
print(response)
} catch {
print(error)
}
}
}
enum PostType: Int, Codable {
case count = 4
}
Also, how will it handle an enum like this?
enum PostType: Decodable {
case count(number: Int)
}
It's pretty easy, just use String or Int raw values which are implicitly assigned.
enum PostType: Int, Codable {
case image, blob
}
image is encoded to 0 and blob to 1
Or
enum PostType: String, Codable {
case image, blob
}
image is encoded to "image" and blob to "blob"
This is a simple example how to use it:
enum PostType : Int, Codable {
case count = 4
}
struct Post : Codable {
var type : PostType
}
let jsonString = "{\"type\": 4}"
let jsonData = Data(jsonString.utf8)
do {
let decoded = try JSONDecoder().decode(Post.self, from: jsonData)
print("decoded:", decoded.type)
} catch {
print(error)
}
Update
In iOS 13.3+ and macOS 15.1+ it's allowed to en-/decode fragments – single JSON values which are not wrapped in a collection type
let jsonString = "4"
let jsonData = Data(jsonString.utf8)
do {
let decoded = try JSONDecoder().decode(PostType.self, from: jsonData)
print("decoded:", decoded) // -> decoded: count
} catch {
print(error)
}
In Swift 5.5+ it's even possible to en-/decode enums with associated values without any extra code. The values are mapped to a dictionary and a parameter label must be specified for each associated value
enum Rotation: Codable {
case zAxis(angle: Double, speed: Int)
}
let jsonString = #"{"zAxis":{"angle":90,"speed":5}}"#
let jsonData = Data(jsonString.utf8)
do {
let decoded = try JSONDecoder().decode(Rotation.self, from: jsonData)
print("decoded:", decoded)
} catch {
print(error)
}
How to make enums with associated types conform to Codable
This answer is similar to #Howard Lovatt's but avoids creating a PostTypeCodableForm struct and instead uses the KeyedEncodingContainer type provided by Apple as a property on Encoder and Decoder, which reduces boilerplate.
enum PostType: Codable {
case count(number: Int)
case title(String)
}
extension PostType {
private enum CodingKeys: String, CodingKey {
case count
case title
}
enum PostTypeCodingError: Error {
case decoding(String)
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
if let value = try? values.decode(Int.self, forKey: .count) {
self = .count(number: value)
return
}
if let value = try? values.decode(String.self, forKey: .title) {
self = .title(value)
return
}
throw PostTypeCodingError.decoding("Whoops! \(dump(values))")
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .count(let number):
try container.encode(number, forKey: .count)
case .title(let value):
try container.encode(value, forKey: .title)
}
}
}
This code works for me on Xcode 9b3.
import Foundation // Needed for JSONEncoder/JSONDecoder
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let decoder = JSONDecoder()
let count = PostType.count(number: 42)
let countData = try encoder.encode(count)
let countJSON = String.init(data: countData, encoding: .utf8)!
print(countJSON)
// {
// "count" : 42
// }
let decodedCount = try decoder.decode(PostType.self, from: countData)
let title = PostType.title("Hello, World!")
let titleData = try encoder.encode(title)
let titleJSON = String.init(data: titleData, encoding: .utf8)!
print(titleJSON)
// {
// "title": "Hello, World!"
// }
let decodedTitle = try decoder.decode(PostType.self, from: titleData)
Swift would throw a .dataCorrupted error if it encounters unknown enum value. If your data is coming from a server, it can send you an unknown enum value at any time (bug server side, new type added in an API version and you want the previous versions of your app to handle the case gracefully, etc), you'd better be prepared, and code "defensive style" to safely decode your enums.
Here is an example on how to do it, with or without associated value
enum MediaType: Decodable {
case audio
case multipleChoice
case other
// case other(String) -> we could also parametrise the enum like that
init(from decoder: Decoder) throws {
let label = try decoder.singleValueContainer().decode(String.self)
switch label {
case "AUDIO": self = .audio
case "MULTIPLE_CHOICES": self = .multipleChoice
default: self = .other
// default: self = .other(label)
}
}
}
And how to use it in a enclosing struct:
struct Question {
[...]
let type: MediaType
enum CodingKeys: String, CodingKey {
[...]
case type = "type"
}
extension Question: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
[...]
type = try container.decode(MediaType.self, forKey: .type)
}
}
To extend on #Toka's answer, you may too add a raw representable value to the enum, and use the default optional constructor to build the enum without a switch:
enum MediaType: String, Decodable {
case audio = "AUDIO"
case multipleChoice = "MULTIPLE_CHOICES"
case other
init(from decoder: Decoder) throws {
let label = try decoder.singleValueContainer().decode(String.self)
self = MediaType(rawValue: label) ?? .other
}
}
It may be extended using a custom protocol that allows to refactor the constructor:
protocol EnumDecodable: RawRepresentable, Decodable {
static var defaultDecoderValue: Self { get }
}
extension EnumDecodable where RawValue: Decodable {
init(from decoder: Decoder) throws {
let value = try decoder.singleValueContainer().decode(RawValue.self)
self = Self(rawValue: value) ?? Self.defaultDecoderValue
}
}
enum MediaType: String, EnumDecodable {
static let defaultDecoderValue: MediaType = .other
case audio = "AUDIO"
case multipleChoices = "MULTIPLE_CHOICES"
case other
}
It can also be easily extended for throwing an error if an invalid enum value was specified, rather than defaulting on a value. Gist with this change is available here: https://gist.github.com/stephanecopin/4283175fabf6f0cdaf87fef2a00c8128.
The code was compiled and tested using Swift 4.1/Xcode 9.3.
A variant of #proxpero's response that is terser would be to formulate the decoder as:
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
guard let key = values.allKeys.first else { throw err("No valid keys in: \(values)") }
func dec<T: Decodable>() throws -> T { return try values.decode(T.self, forKey: key) }
switch key {
case .count: self = try .count(dec())
case .title: self = try .title(dec())
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .count(let x): try container.encode(x, forKey: .count)
case .title(let x): try container.encode(x, forKey: .title)
}
}
This permits the compiler to exhaustively verify the cases, and also doesn't suppress the error message for the case where the encoded value doesn't match the key's expected value.
Actually the answers above are really great, but they are missing some details for what many people need in a continuously developed client/server project. We develop an app while our backend continually evolves over time, which means some enum cases will change that evolution. So we need an enum decoding strategy that is able to decode arrays of enums that contain unknown cases. Otherwise decoding the object that contains the array simply fails.
What I did is quite simple:
enum Direction: String, Decodable {
case north, south, east, west
}
struct DirectionList {
let directions: [Direction]
}
extension DirectionList: Decodable {
public init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var directions: [Direction] = []
while !container.isAtEnd {
// Here we just decode the string from the JSON which always works as long as the array element is a string
let rawValue = try container.decode(String.self)
guard let direction = Direction(rawValue: rawValue) else {
// Unknown enum value found - ignore, print error to console or log error to analytics service so you'll always know that there are apps out which cannot decode enum cases!
continue
}
// Add all known enum cases to the list of directions
directions.append(direction)
}
self.directions = directions
}
}
Bonus: Hide implementation > Make it a Collection
To hide implementation detail is always a good idea. For this you'll need just a little bit more code. The trick is to conform DirectionsList to Collection and make your internal list array private:
struct DirectionList {
typealias ArrayType = [Direction]
private let directions: ArrayType
}
extension DirectionList: Collection {
typealias Index = ArrayType.Index
typealias Element = ArrayType.Element
// The upper and lower bounds of the collection, used in iterations
var startIndex: Index { return directions.startIndex }
var endIndex: Index { return directions.endIndex }
// Required subscript, based on a dictionary index
subscript(index: Index) -> Element {
get { return directions[index] }
}
// Method that returns the next index when iterating
func index(after i: Index) -> Index {
return directions.index(after: i)
}
}
You can read more about conforming to custom collections in this blog post by John Sundell: https://medium.com/#johnsundell/creating-custom-collections-in-swift-a344e25d0bb0
You can do what you want, but it is a bit involved :(
import Foundation
enum PostType: Codable {
case count(number: Int)
case comment(text: String)
init(from decoder: Decoder) throws {
self = try PostTypeCodableForm(from: decoder).enumForm()
}
func encode(to encoder: Encoder) throws {
try PostTypeCodableForm(self).encode(to: encoder)
}
}
struct PostTypeCodableForm: Codable {
// All fields must be optional!
var countNumber: Int?
var commentText: String?
init(_ enumForm: PostType) {
switch enumForm {
case .count(let number):
countNumber = number
case .comment(let text):
commentText = text
}
}
func enumForm() throws -> PostType {
if let number = countNumber {
guard commentText == nil else {
throw DecodeError.moreThanOneEnumCase
}
return .count(number: number)
}
if let text = commentText {
guard countNumber == nil else {
throw DecodeError.moreThanOneEnumCase
}
return .comment(text: text)
}
throw DecodeError.noRecognizedContent
}
enum DecodeError: Error {
case noRecognizedContent
case moreThanOneEnumCase
}
}
let test = PostType.count(number: 3)
let data = try JSONEncoder().encode(test)
let string = String(data: data, encoding: .utf8)!
print(string) // {"countNumber":3}
let result = try JSONDecoder().decode(PostType.self, from: data)
print(result) // count(3)
Features
Simple use. One line in Decodable instance: line eg let enum: DecodableEnum<AnyEnum>
Is decoded with standard mapping mechanism: JSONDecoder().decode(Model.self, from: data)
covered case of receiving unknown data (for example, mapping a Decodable object will not fail if you receive unexpected data)
handle/deliver mapping or decoding errors
Details
Xcode 12.0.1 (12A7300)
Swift 5.3
Solution
import Foundation
enum DecodableEnum<Enum: RawRepresentable> where Enum.RawValue == String {
case value(Enum)
case error(DecodingError)
var value: Enum? {
switch self {
case .value(let value): return value
case .error: return nil
}
}
var error: DecodingError? {
switch self {
case .value: return nil
case .error(let error): return error
}
}
enum DecodingError: Error {
case notDefined(rawValue: String)
case decoding(error: Error)
}
}
extension DecodableEnum: Decodable {
init(from decoder: Decoder) throws {
do {
let rawValue = try decoder.singleValueContainer().decode(String.self)
guard let layout = Enum(rawValue: rawValue) else {
self = .error(.notDefined(rawValue: rawValue))
return
}
self = .value(layout)
} catch let err {
self = .error(.decoding(error: err))
}
}
}
Usage sample
enum SimpleEnum: String, Codable {
case a, b, c, d
}
struct Model: Decodable {
let num: Int
let str: String
let enum1: DecodableEnum<SimpleEnum>
let enum2: DecodableEnum<SimpleEnum>
let enum3: DecodableEnum<SimpleEnum>
let enum4: DecodableEnum<SimpleEnum>?
}
let dictionary: [String : Any] = ["num": 1, "str": "blablabla", "enum1": "b", "enum2": "_", "enum3": 1]
let data = try! JSONSerialization.data(withJSONObject: dictionary)
let object = try JSONDecoder().decode(Model.self, from: data)
print("1. \(object.enum1.value)")
print("2. \(object.enum2.error)")
print("3. \(object.enum3.error)")
print("4. \(object.enum4)")
A lot of good approaches here, but I have not seen one discussing enums with more than one value, although it can be deduced from examples - maybe someone can find a use for this one:
import Foundation
enum Tup {
case frist(String, next: Int)
case second(Int, former: String)
enum TupType: String, Codable {
case first
case second
}
enum CodingKeys: String, CodingKey {
case type
case first
case firstNext
case second
case secondFormer
}
}
extension Tup: Codable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let type = try values.decode(TupType.self, forKey: .type)
switch type {
case .first:
let str = try values.decode(String.self, forKey: .first)
let next = try values.decode(Int.self, forKey: .firstNext)
self = .frist(str, next: next)
case .second:
let int = try values.decode(Int.self, forKey: .second)
let former = try values.decode(String.self, forKey: .secondFormer)
self = .second(int, former: former)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .frist(let str, next: let next):
try container.encode(TupType.first, forKey: .type)
try container.encode(str, forKey: .first)
try container.encode(next, forKey: .firstNext)
case .second(let int, former: let former):
try container.encode(TupType.second, forKey: .type)
try container.encode(int, forKey: .second)
try container.encode(former, forKey: .secondFormer)
}
}
}
let example1 = Tup.frist("123", next: 90)
do {
let encoded = try JSONEncoder().encode(example1)
print(encoded)
let decoded = try JSONDecoder().decode(Tup.self, from: encoded)
print("decoded 1 = \(decoded)")
}
catch {
print("errpr = \(error.localizedDescription)")
}
let example2 = Tup.second(10, former: "dantheman")
do {
let encoded = try JSONEncoder().encode(example2)
print(encoded)
let decoded = try JSONDecoder().decode(Tup.self, from: encoded)
print("decoded 2 = \(decoded)")
}
catch {
print("errpr = \(error.localizedDescription)")
}
Here is a simple example of how to make an enum decodable in Swift.
Sample JSON:
[
{
"title": "1904",
"artist": "The Tallest Man on Earth",
"year": "2012",
"type": "hindi"
},
{
"title": "#40",
"artist": "Dave Matthews",
"year": "1999",
"type": "english"
},
{
"title": "40oz to Freedom",
"artist": "Sublime",
"year": "1996",
"type": "english"
},
{
"title": "#41",
"artist": "Dave Matthews",
"year": "1996",
"type": "punjabi"
}
]
Model struct:
struct Song: Codable {
public enum SongType: String, Codable {
case hindi = "hindi"
case english = "english"
case punjabi = "punjabi"
case tamil = "tamil"
case none = "none"
}
let title: String
let artist: String
let year: String
let type: SongType?
}
Now, you can parse the JSON file and parse the data into an array of songs like below:
func decodeJSON() {
do {
// creating path from main bundle and get data object from the path
if let bundlePath = Bundle.main.path(forResource: "sample", ofType: "json"),
let jsonData = try String(contentsOfFile: bundlePath).data(using: .utf8) {
// decoding an array of songs
let songs = try JSONDecoder().decode([Song].self, from: jsonData)
// printing the type of song
songs.forEach { song in
print("Song type: \(song.type?.rawValue ?? "")")
}
}
} catch {
print(error)
}
}
Comment below in case of any queries.

check the json response is array or int or string for a key?

I am having the json response in which "products" key sometime having the int value and some cases it had an array?
How to check whether it is having array or Int?
"products": 25
or
"products": [77,80,81,86]
I am using this
self.productsCount = mResp["products"] as! [Int]
but it is crashed every time when it is not having array.
Now i am not getting how to check this because i have the different option for Int and Array?
Please help me.Thanks
There is no need to fall back to Any here. Even problematic JSON like this can be handled with Codable. You just need to keep trying the different types until one works.
struct Thing: Decodable {
let products: [Int]
enum CodingKeys: String, CodingKey {
case products
}
init(from decoder: Decoder) throws {
// First pull out the "products" key
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
// Then try to decode the value as an array
products = try container.decode([Int].self, forKey: .products)
} catch {
// If that didn't work, try to decode it as a single value
products = [try container.decode(Int.self, forKey: .products)]
}
}
}
let singleJSON = Data("""
{ "products": 25 }
""".utf8)
let listJSON = Data("""
{ "products": [77,80,81,86] }
""".utf8)
let decoder = JSONDecoder()
try! decoder.decode(Thing.self, from: singleJSON).products // [25]
try! decoder.decode(Thing.self, from: listJSON).products // [77, 80, 81, 86]
It crashes because you force unwrap as an Integer Array, even though you just have an integer. The solution is to check for both:
self.productsCount = mResp["products"] as? [Int] ?? mResp["products"] as? Int
Other Solution
if let proCount = mResp["products"] as? [Int] {
self.productsCount = proCount
} else {
self.productsCount = mResp["products"] as? Int
}
This is temporary solution as you want. Check for possible type with "Any" type.
var anyType : Any!
anyType = "123"
anyType = ["Test","Test1"]
anyType = 1
if anyType is Array {
print("is Array")
}else if anyType is String {
print("is String")
}else if anyType is Int {
print("is Int")
}
let dict = [77,80,81,86]//Pass your parameter or parsed json value
if dict is Array<Any> {
print("Yes, it's an Array")
}
else{
print("NO, it's not an Array")
}
let's assume your json name is jsonData
Check for Int and Array Int:
if let intVal = jsonData["products"] as? Int {
print("Products is a Integer: ", intVal)
} else if let jsonArr = jsonData["products"] as? [Int] {
var intVals = [Int]()
for json in jsonArr {
intVals.append(json)
}
print("Json is array of Int: ", intVals)
}
Generic solution would be like this,
let products = mResp["products"] as? Any
if let item = products as? [Int] {
print("array", item)
} else if let item = products as? Int {
print("Integer", item)
}
Use Generics for obtaining a better solution and provide the type at the time of decoding this model.
struct Product<T: Codable>: Codable {
let products: T?
}
And you can use it with nested try catch:
do {
let product = try JSONDecoder().decode(Product<Int>.self, from: data)
print(product)
} catch {
do {
let product = try JSONDecoder().decode(Product<[Int]>.self, from: data)
print(product)
} catch {
print(error)
}
}
Note: This solution assumes there is not more than a few different type-varying properties in the codable struct. If there are multiple type-varying properties I'd recommend use a custom init(decoder:) as provided in the accepted answer which would be much better design instead of having a try-catch tree.

Resources