JSONDecoder but sometimes the key is missing, how to replace data - Swift - arrays

I'm trying to decode a JSON result in Swift but sometimes one of the keys is missing in the JSON which is causing my attempt at decoding to crash. I cannot figure out a way to replace a missing key with a value to prevent it from crashing and to keep my data structured and in line with other arrays and their positions.
It works perfectly unless there is a missing key from the API, in which case I'm getting a crash.
Here is my code:
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
struct PortHW: Codable {
let EventType: String
let DateTime: String
let Height: Double
}
func getPortHW(portID: String) -> (eventArray: [String], timeArray: [String], heightArray: [Double]) {
var json: Data? = nil
let semaphore = DispatchSemaphore (value: 0)
var request = URLRequest(url: URL(string: "https://admiraltyapi.azure-api.net/uktidalapi/api/V1/Stations/\(portID)/TidalEvents?duration=7")!,timeoutInterval: Double.infinity)
request.addValue("HIDDEN", forHTTPHeaderField: "Ocp-Apim-Subscription-Key")
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
print(String(describing: error))
semaphore.signal()
return
}
json = data
semaphore.signal()
}
task.resume()
semaphore.wait()
let decoder = JSONDecoder()
let tideResult = try? decoder.decode([PortHW].self, from: json!)
var eventArray: [String] = []
var timeArray: [String] = []
var heightArray: [Double] = []
for item in tideResult! {
eventArray.append(item.EventType)
timeArray.append(item.DateTime)
heightArray.append(item.Height)
}
return (eventArray, timeArray, heightArray)
}
The JSON I'm getting my data from looks like this most of the time:
[
{
"EventType": "HighWater",
"DateTime": "2022-03-15T01:29:00",
"IsApproximateTime": false,
"Height": 4.2845989326570688,
"IsApproximateHeight": false,
"Filtered": false,
"Date": "2022-03-15T00:00:00"
},
But sometimes the key Height is missing, not empty, but missing... I've struggled with finding a way to replace the missing key with a value of 0.0 when it's not there.. I'd really appreciate some guidance.
Thank you.

whichever is missing, you can make it optional
like this:
struct PortHW: Codable {
let EventType: String
let DateTime: String
let Height: Double?
}
extension PortHW {
func getHeight() -> Double {
if let height = self.Height {
return height
}
return 0.0
}
}
or you can just add defaultValue ==> 0.0
for item in tideResult! {
eventArray.append(item.EventType)
timeArray.append(item.DateTime)
heightArray.append(item.Height ?? 0.0)
}

Related

How can I remove whole dictionary in nested array at specific index after filter some specific value

I have API response with nested array . But I can't understand how can I remove whole Dict by filtering the value .
This is the response screenshot
https://imgur.com/XIDyfYX
here is the json Resonse :- https://del.dog/lofavofogo.json
I have tried this but I don't know how to get filter nested value and remove whole dict at specific index
How to remove pairs from dictionary at specific index - Swift?
I want to remove the dict where section name are "NA"
Here is the code :-
Model Class For API Response :-
class filterclass: NSObject {
var classesID : String?
var classname : String?
var section = [filterSections]()
init(json: [String: Any]) {
if let classname = json["class"] as? String {
self.classname = classname
}
if let classesID = json["classesID"] as? String {
self.classesID = classesID
}
print("classname",classname)
if let evUserGoing = json["classsection"] as? [[String: Any]] {
if self.section.count > 0
{
self.section.removeAll()
}
for evUser in evUserGoing {
// print("evUser",evUser)
let userGoing = filterSections(json: evUser)
self.section.append(userGoing)
}
for sec in section {
let section = sec.secctionname
let setionID = sec.sectionID
}
}
}
}
class filterSections: NSObject {
var sectionID : String?
var secctionname : String?
var isSelctedSection : Bool = false
init(json: [String: Any]) {
if let sectionID = json["sectionID"] as? String {
self.sectionID = sectionID
}
if let secctionname = json["section"] as? String {
self.secctionname = secctionname
}
print("sectioname",secctionname)
}
}
API POST Method TO hit API :-
func getClassSectionAPI() {
if ReusableClass.sharedInstance.isNetworkAvailable() == true
{
ReusableClass.sharedInstance.showActivityIndicator()
let UUid = LoginUserInfo.sharedInstance.uuid!
let dictionary = ["uuid":UUid,"device_id":devicetoken,"school_id":LoginUserInfo.sharedInstance.schoolId!, "user_type":LoginUserInfo.sharedInstance.usertype!]
print(dictionary)
let encoder = JSONEncoder()
if let jsonData = try? encoder.encode(dictionary) {
if let jsonString = String(data: jsonData, encoding: .utf8) {
// print(jsonString)
let cipher:String = CryptoHelper.encrypt(input:jsonString)!;
let NewEncryption = "data=\(cipher)"
print(NewEncryption)
let hmac_md5 = cipher.hmac(algorithm: .sha512, key: kHMACKey)
print("hmac",hmac_md5)
UserDefaults.standard.set(hmac_md5, forKey: Headerkey)
Singleton.sharedInstance.getWebservicesverify(params: NewEncryption, Methodname: KFilterClassSection, data: Stringnil)
{ (result) in
DispatchQueue.main.async {
ReusableClass.sharedInstance.hideActivityIndicator()
}
if result != nil
{
do {
let jsonData = try JSONSerialization.data(withJSONObject: result)
if let json = String(data: jsonData, encoding: .utf8) {
let Dict = function.convertToDictionary(text: json)! as NSDictionary
guard let data = Dict[KData] as? String
else
{
return
}
self.baseDict = data
}
}
catch {
}
guard let output = CryptoHelper.decrypt(input:self.baseDict)
else
{
return
}
print(output)
let mainDict = function.convertToDictionary(text: output)! as NSDictionary
let status = mainDict[KStatus] as! NSInteger
if(status == 1)
{
DispatchQueue.main.async {
print("Main dict",mainDict)
guard let messageArray = mainDict["data"] as? [[String: Any]] else{
return
}
if self.arrayClasSection.count > 0
{
self.arrayClasSection.removeAll()
}
print("Main dict",messageArray)
for arr in messageArray {
let obj = filterclass.init(json: arr)
if let index = self.arryFilterTemperary.index(where: { $0.classname == obj.classname }) {
// let filtered = self.arryFilterTemperary.filter { $0.classname == "NA" }
obj.section = self.arryFilterTemperary[index].section
self.arrayClasSection.append(obj)
for sec in self.arryFilterTemperary[index].section {
let section = sec.sectionID
let sectionName = sec.secctionname
self.NASection = sec.secctionname!
print(self.NASection)
self.selectedNASectionID = sec.sectionID!
// let test = self.arryFilterTemperary[index].section.filter { !$0.value.contains("") }
// print(test)
}
}
else
{
self.arrayClasSection.append(obj)
}
}
ReusableClass.sharedInstance.hideActivityIndicator()
self.tableFilter.reloadData()
}
}
I want to append the data to array but before appending I want to
filter that "NA" value dict from the array
Since this is your first question I go to some greater length in answering it than usual. Playgrounds are an exceptional way to demonstrate your problem, so you should always try to compose your questions in a form of one. I will post my answer directly from the Playground I have done.
With that out of the way lets get to the question. Your main problem seems to be that you tried an ill fated JSONSerialization "shortcutt" route. This looks cheap from the outside, but working with the unavoidable optionality of a [String:Any] comes at a high cost in a language like Swift. The way to go is the brilliant Codable protocol, at least in my opinion. Once you define your data structure properly Xcode has so much more possibilities to guide you through the APIs that writing your filter code becomes a piece of cake.
Enough of the ranting, let's get to the pizza.
import UIKit
let dataStr = """
{
"status":1,
"message":"Class and sections list",
"data":[
{
"classsection":[
{
"section":"A",
"sectionID":"1",
"classesID":"1"
},
{
"section":"B",
"sectionID":"3",
"classesID":"1"
}
],
"class":"First",
"classesID":"1"
},
{
"classsection":[
{
"section":"A",
"sectionID":"2",
"classesID":"2"
},
{
"section":"B",
"sectionID":"7",
"classesID":"2"
}
],
"class":"Second",
"classesID":"2"
},
{
"classsection":[
{
"section":"A",
"sectionID":"20",
"classesID":"15"
}
],
"class":"Third",
"classesID":"15"
},
{
"classsection":[
{
"section":"NA",
"sectionID":"33",
"classesID":"22"
}
],
"class":"Pre Nursery",
"classesID":"22"
},
{
"classsection":[
{
"section":"NA",
"sectionID":"34",
"classesID":"23"
},
{
"section":"A",
"sectionID":"35",
"classesID":"23"
},
{
"section":"B",
"sectionID":"36",
"classesID":"23"
},
{
"section":"C",
"sectionID":"37",
"classesID":"23"
}
],
"class":"Fourth four",
"classesID":"23"
},
{
"classsection":[
{
"section":"NA",
"sectionID":"38",
"classesID":"24"
}
],
"class":"Fifth",
"classesID":"24"
},
{
"classsection":[
{
"section":"NA",
"sectionID":"39",
"classesID":"25"
}
],
"class":"sixth 6th",
"classesID":"25"
}
]
}
"""
struct Section: Codable {
let section, sectionId, classesId: String
enum CodingKeys: String, CodingKey {
case sectionId = "sectionID"
case classesId = "classesID"
case section
}
}
struct Class1: Codable {
let classsection: [Section]
let clazz, classesId: String
private enum CodingKeys: String, CodingKey {
case classsection
case clazz = "class"
case classesId = "classesID"
}
}
struct Response: Codable {
let status: Int
let message: String
let data: [Class1]
func filterSections(notMatching filterVal: String) -> Response {
let filteredData = data.map { (clazz) -> Class1 in
let filteredSections = clazz.classsection.filter { (sect) -> Bool in
sect.section != filterVal
}
return Class1(classsection: filteredSections, clazz: clazz.clazz, classesId: clazz.classesId)
}
return Response(status: status, message: message, data: filteredData)
}
}
let jsonData = dataStr.data(using:.utf8)!
do {
let res = try JSONDecoder().decode(Response.self, from: jsonData)
let filteredResponse = res.filterSections(notMatching: "NA")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
print(String(data:try encoder.encode(filteredResponse), encoding: .utf8)!)
} catch {
print(error)
}
As you can see your data structure is easily defined and the filtering code is really easy to understand once you wrap your head around the lambdas (which you should). The playground will output nicely formatted JSON as an answer, this way it is easy to check that your code does the right thing without all the messy parts of asynchronous communication (which are still nicely done in Swift).
Here's my last tipp of the day: Always try to isolate your problem as much as possible if you post a question on StackOverflow. I think your question carried too much legacy, you should whittle it down for the next one. This will improve your chances for a quick answer.

How to loop through a JSON object in Vapor 1.5 and convert it to [String:Any]?

I am sending a request to my vapor 1.5 server via Alamofire with body of type [String:Any] where Any type is a dictionary of String:String
When the request is received on the server I convert it to JSON type
guard let reqJson = request.json else {return}
How can I loop through this JSON object and convert it to an array of [String:Any]
The body of the request I send from the client app looks like this:
["DHSKL3920JFLSKXFgs":
["title": “some title”,
"body": “some body”,
"DHSKL3920JFLSKXFgs": "DHSKL3920JFLSKXFgs",
"unreadMessagesCount": 3],
"PKF993AVG59gkCM":
["title": “some title”,
"body": “some body”,
"DHSKL39": "DHSKL39",
"unreadMessagesCount": 3]]
You can use swift4 Codable and shrink your code to 4-5 lines. Documentation
If I understood you correctly. Maybe the following will help.
//This method uses high order function map
func convert(json:[String:Any]) -> [[String: Any]] {
let requiredObjects = json.map { $0.value as! [String:Any] } //force unwrapping
return requiredObjects
}
//This method uses simple loop
func convert(json:[String:Any]) -> [[String: Any]] {
var requiredObjects = [[String:Any]]()
for (key, value) in json.enumerated() {
requiredObjects.append([value.key : value.value])
}
return requiredObjects
}
struct DataFromClientSendNotifications {
let title: String
let body: String
let sound: String
let badge: String
let fcmToken: String
let unreadMessagesCount: String
}
guard let reqJson = request.json else {
throw Abort.custom(status: .badRequest, message: message)
}
for obj in reqJson.object! {
print("new obj is \(obj)")
let objKey = obj.key
let objValue = obj.value.object
print("objectValue here is \(objValue)")
let title = objValue?["title"]?.string
let body = objValue?["body"]?.string
let unreadMessagesCount = objValue?["unreadMessagesCount"]?.string
let sound = objValue?["sound"]?.string
let badge = objValue?["badge"]?.string
let fcmToken = objValue?["objValue"]?.string
let itemCompleted = DataFromClientSendNotifications(title: title!, body: body!, sound: sound!, badge: badge!, fcmToken: fcmToken!, unreadMessagesCount: unreadMessagesCount!)
print("itemCompleted is \(itemCompleted)")
//now you can do whatever you want with itemCompleted
}

Swift JSONDecode decoding arrays fails if single element decoding fails

While using Swift4 and Codable protocols I got the following problem - it looks like there is no way to allow JSONDecoder to skip elements in an array.
For example, I have the following JSON:
[
{
"name": "Banana",
"points": 200,
"description": "A banana grown in Ecuador."
},
{
"name": "Orange"
}
]
And a Codable struct:
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
When decoding this json
let decoder = JSONDecoder()
let products = try decoder.decode([GroceryProduct].self, from: json)
Resulting products is empty. Which is to be expected, due to the fact that the second object in JSON has no "points" key, while points is not optional in GroceryProduct struct.
Question is how can I allow JSONDecoder to "skip" invalid object?
One option is to use a wrapper type that attempts to decode a given value; storing nil if unsuccessful:
struct FailableDecodable<Base : Decodable> : Decodable {
let base: Base?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self.base = try? container.decode(Base.self)
}
}
We can then decode an array of these, with your GroceryProduct filling in the Base placeholder:
import Foundation
let json = """
[
{
"name": "Banana",
"points": 200,
"description": "A banana grown in Ecuador."
},
{
"name": "Orange"
}
]
""".data(using: .utf8)!
struct GroceryProduct : Codable {
var name: String
var points: Int
var description: String?
}
let products = try JSONDecoder()
.decode([FailableDecodable<GroceryProduct>].self, from: json)
.compactMap { $0.base } // .flatMap in Swift 4.0
print(products)
// [
// GroceryProduct(
// name: "Banana", points: 200,
// description: Optional("A banana grown in Ecuador.")
// )
// ]
We're then using .compactMap { $0.base } to filter out nil elements (those that threw an error on decoding).
This will create an intermediate array of [FailableDecodable<GroceryProduct>], which shouldn't be an issue; however if you wish to avoid it, you could always create another wrapper type that decodes and unwraps each element from an unkeyed container:
struct FailableCodableArray<Element : Codable> : Codable {
var elements: [Element]
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var elements = [Element]()
if let count = container.count {
elements.reserveCapacity(count)
}
while !container.isAtEnd {
if let element = try container
.decode(FailableDecodable<Element>.self).base {
elements.append(element)
}
}
self.elements = elements
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(elements)
}
}
You would then decode as:
let products = try JSONDecoder()
.decode(FailableCodableArray<GroceryProduct>.self, from: json)
.elements
print(products)
// [
// GroceryProduct(
// name: "Banana", points: 200,
// description: Optional("A banana grown in Ecuador.")
// )
// ]
I would create a new type Throwable, which can wrap any type conforming to Decodable:
enum Throwable<T: Decodable>: Decodable {
case success(T)
case failure(Error)
init(from decoder: Decoder) throws {
do {
let decoded = try T(from: decoder)
self = .success(decoded)
} catch let error {
self = .failure(error)
}
}
}
For decoding an array of GroceryProduct (or any other Collection):
let decoder = JSONDecoder()
let throwables = try decoder.decode([Throwable<GroceryProduct>].self, from: json)
let products = throwables.compactMap { $0.value }
where value is a computed property introduced in an extension on Throwable:
extension Throwable {
var value: T? {
switch self {
case .failure(_):
return nil
case .success(let value):
return value
}
}
}
I would opt for using a enum wrapper type (over a Struct) because it may be useful to keep track of the errors that are thrown as well as their indices.
Swift 5
For Swift 5 Consider using the Result enum e.g.
struct Throwable<T: Decodable>: Decodable {
let result: Result<T, Error>
init(from decoder: Decoder) throws {
result = Result(catching: { try T(from: decoder) })
}
}
To unwrap the decoded value use the get() method on the result property:
let products = throwables.compactMap { try? $0.result.get() }
The problem is that when iterating over a container, the container.currentIndex isn’t incremented so you can try to decode again with a different type.
Because the currentIndex is read only, a solution is to increment it yourself successfully decoding a dummy. I took #Hamish solution, and wrote a wrapper with a custom init.
This problem is a current Swift bug: https://bugs.swift.org/browse/SR-5953
The solution posted here is a workaround in one of the comments.
I like this option because I’m parsing a bunch of models the same way on a network client, and I wanted the solution to be local to one of the objects. That is, I still want the others to be discarded.
I explain better in my github https://github.com/phynet/Lossy-array-decode-swift4
import Foundation
let json = """
[
{
"name": "Banana",
"points": 200,
"description": "A banana grown in Ecuador."
},
{
"name": "Orange"
}
]
""".data(using: .utf8)!
private struct DummyCodable: Codable {}
struct Groceries: Codable
{
var groceries: [GroceryProduct]
init(from decoder: Decoder) throws {
var groceries = [GroceryProduct]()
var container = try decoder.unkeyedContainer()
while !container.isAtEnd {
if let route = try? container.decode(GroceryProduct.self) {
groceries.append(route)
} else {
_ = try? container.decode(DummyCodable.self) // <-- TRICK
}
}
self.groceries = groceries
}
}
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
let products = try JSONDecoder().decode(Groceries.self, from: json)
print(products)
There are two options:
Declare all members of the struct as optional whose keys can be missing
struct GroceryProduct: Codable {
var name: String
var points : Int?
var description: String?
}
Write a custom initializer to assign default values in the nil case.
struct GroceryProduct: Codable {
var name: String
var points : Int
var description: String
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .name)
points = try values.decodeIfPresent(Int.self, forKey: .points) ?? 0
description = try values.decodeIfPresent(String.self, forKey: .description) ?? ""
}
}
A solution made possible by Swift 5.1, using the property wrapper:
#propertyWrapper
struct IgnoreFailure<Value: Decodable>: Decodable {
var wrappedValue: [Value] = []
private struct _None: Decodable {}
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
while !container.isAtEnd {
if let decoded = try? container.decode(Value.self) {
wrappedValue.append(decoded)
}
else {
// item is silently ignored.
try? container.decode(_None.self)
}
}
}
}
And then the usage:
let json = """
{
"products": [
{
"name": "Banana",
"points": 200,
"description": "A banana grown in Ecuador."
},
{
"name": "Orange"
}
]
}
""".data(using: .utf8)!
struct GroceryProduct: Decodable {
var name: String
var points: Int
var description: String?
}
struct ProductResponse: Decodable {
#IgnoreFailure
var products: [GroceryProduct]
}
let response = try! JSONDecoder().decode(ProductResponse.self, from: json)
print(response.products) // Only contains banana.
Note: The property wrapper things will only works if the response can be wrapped in a struct (i.e: not a top level array).
In that case, you can still wrap it manually (with a typealias for better readability):
typealias ArrayIgnoringFailure<Value: Decodable> = IgnoreFailure<Value>
let response = try! JSONDecoder().decode(ArrayIgnoringFailure<GroceryProduct>.self, from: json)
print(response.wrappedValue) // Only contains banana.
Ive put #sophy-swicz solution, with some modifications, into an easy to use extension
fileprivate struct DummyCodable: Codable {}
extension UnkeyedDecodingContainer {
public mutating func decodeArray<T>(_ type: T.Type) throws -> [T] where T : Decodable {
var array = [T]()
while !self.isAtEnd {
do {
let item = try self.decode(T.self)
array.append(item)
} catch let error {
print("error: \(error)")
// hack to increment currentIndex
_ = try self.decode(DummyCodable.self)
}
}
return array
}
}
extension KeyedDecodingContainerProtocol {
public func decodeArray<T>(_ type: T.Type, forKey key: Self.Key) throws -> [T] where T : Decodable {
var unkeyedContainer = try self.nestedUnkeyedContainer(forKey: key)
return try unkeyedContainer.decodeArray(type)
}
}
Just call it like this
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.items = try container.decodeArray(ItemType.self, forKey: . items)
}
For the example above:
let json = """
[
{
"name": "Banana",
"points": 200,
"description": "A banana grown in Ecuador."
},
{
"name": "Orange"
}
]
""".data(using: .utf8)!
struct Groceries: Codable
{
var groceries: [GroceryProduct]
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
groceries = try container.decodeArray(GroceryProduct.self)
}
}
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
let products = try JSONDecoder().decode(Groceries.self, from: json)
print(products)
Instead, You can also do like this:
struct GroceryProduct: Decodable {
var name: String
var points: Int
var description: String?
}'
and then in while getting it:
'let groceryList = try JSONDecoder().decode(Array<GroceryProduct>.self, from: responseData)'
Unfortunately Swift 4 API doesn't have failable initializer for init(from: Decoder).
Only one solution that I see is implementing custom decoding, giving default value for optional fields and possible filter with needed data:
struct GroceryProduct: Codable {
let name: String
let points: Int?
let description: String
private enum CodingKeys: String, CodingKey {
case name, points, description
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
points = try? container.decode(Int.self, forKey: .points)
description = (try? container.decode(String.self, forKey: .description)) ?? "No description"
}
}
// for test
let dict = [["name": "Banana", "points": 100], ["name": "Nut", "description": "Woof"]]
if let data = try? JSONSerialization.data(withJSONObject: dict, options: []) {
let decoder = JSONDecoder()
let result = try? decoder.decode([GroceryProduct].self, from: data)
print("rawResult: \(result)")
let clearedResult = result?.filter { $0.points != nil }
print("clearedResult: \(clearedResult)")
}
I improved on #Hamish's for the case, that you want this behaviour for all arrays:
private struct OptionalContainer<Base: Codable>: Codable {
let base: Base?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
base = try? container.decode(Base.self)
}
}
private struct OptionalArray<Base: Codable>: Codable {
let result: [Base]
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let tmp = try container.decode([OptionalContainer<Base>].self)
result = tmp.compactMap { $0.base }
}
}
extension Array where Element: Codable {
init(from decoder: Decoder) throws {
let optionalArray = try OptionalArray<Element>(from: decoder)
self = optionalArray.result
}
}
Swift 5
Inspired with previous answers I decode inside Result enum extension.
What do you think about it?
extension Result: Decodable where Success: Decodable, Failure == DecodingError {
public init(from decoder: Decoder) throws {
let container: SingleValueDecodingContainer = try decoder.singleValueContainer()
do {
self = .success(try container.decode(Success.self))
} catch {
if let decodingError = error as? DecodingError {
self = .failure(decodingError)
} else {
self = .failure(DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: error.localizedDescription)))
}
}
}
}
Usage
let listResult = try? JSONDecoder().decode([Result<SomeObject, DecodingError>].self, from: ##YOUR DATA##)
let list: [SomeObject] = listResult.compactMap {try? $0.get()}
#Hamish's answer is great. However, you can reduce FailableCodableArray to:
struct FailableCodableArray<Element : Codable> : Codable {
var elements: [Element]
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let elements = try container.decode([FailableDecodable<Element>].self)
self.elements = elements.compactMap { $0.wrapped }
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(elements)
}
}
I had a similar issue recently, but slightly different.
struct Person: Codable {
var name: String
var age: Int
var description: String?
var friendnamesArray:[String]?
}
In this case, if one of the element in friendnamesArray is nil, the whole object is nil while decoding.
And the right way to handle this edge case is to declare the string array[String] as array of optional strings[String?] as below,
struct Person: Codable {
var name: String
var age: Int
var description: String?
var friendnamesArray:[String?]?
}
You made the description optional, you should also make the points field optional if there is a chance it could be nil, such as this:
struct GroceryProduct: Codable {
var name: String
var points: Int?
var description: String?
}
Just make sure you safe-unwrap it however you see fit for it's use. I'm guessing nil points == 0 in the actual use case so an example could be:
let products = try JSONDecoder().decode([GroceryProduct].self, from: json)
for product in products {
let name = product.name
let points = product.points ?? 0
let description = product.description ?? ""
ProductView(name, points, description)
}
or in-line:
let products = try JSONDecoder().decode([GroceryProduct].self, from: json)
for product in products {
ProductView(product.name, product.points ?? 0, product.description ?? "")
}
I come up with this KeyedDecodingContainer.safelyDecodeArray that provides a simple interface:
extension KeyedDecodingContainer {
/// The sole purpose of this `EmptyDecodable` is allowing decoder to skip an element that cannot be decoded.
private struct EmptyDecodable: Decodable {}
/// Return successfully decoded elements even if some of the element fails to decode.
func safelyDecodeArray<T: Decodable>(of type: T.Type, forKey key: KeyedDecodingContainer.Key) -> [T] {
guard var container = try? nestedUnkeyedContainer(forKey: key) else {
return []
}
var elements = [T]()
elements.reserveCapacity(container.count ?? 0)
while !container.isAtEnd {
/*
Note:
When decoding an element fails, the decoder does not move on the next element upon failure, so that we can retry the same element again
by other means. However, this behavior potentially keeps `while !container.isAtEnd` looping forever, and Apple does not offer a `.skipFailable`
decoder option yet. As a result, `catch` needs to manually skip the failed element by decoding it into an `EmptyDecodable` that always succeed.
See the Swift ticket https://bugs.swift.org/browse/SR-5953.
*/
do {
elements.append(try container.decode(T.self))
} catch {
if let decodingError = error as? DecodingError {
Logger.error("\(#function): skipping one element: \(decodingError)")
} else {
Logger.error("\(#function): skipping one element: \(error)")
}
_ = try? container.decode(EmptyDecodable.self) // skip the current element by decoding it into an empty `Decodable`
}
}
return elements
}
}
The potentially infinite loop while !container.isAtEnd is a concern, and it's addressed by using EmptyDecodable.
A much simpler attempt:
Why don't you declare points as optional or make the array contain optional elements
let products = [GroceryProduct?]
Features:
Simple use. One line in Decodable instance: let array: CompactDecodableArray<Int>
Is decoded with standard mapping mechanism: JSONDecoder().decode(Model.self, from: data)
skips incorrect elements (returns array with only successful mapped elements)
Details
Xcode 12.1 (12A7403)
Swift 5.3
Solution
class CompactDecodableArray<Element>: Decodable where Element: Decodable {
private(set) var elements = [Element]()
required init(from decoder: Decoder) throws {
guard var unkeyedContainer = try? decoder.unkeyedContainer() else { return }
while !unkeyedContainer.isAtEnd {
if let value = try? unkeyedContainer.decode(Element.self) {
elements.append(value)
} else {
unkeyedContainer.skip()
}
}
}
}
// https://forums.swift.org/t/pitch-unkeyeddecodingcontainer-movenext-to-skip-items-in-deserialization/22151/17
struct Empty: Decodable { }
extension UnkeyedDecodingContainer {
mutating func skip() { _ = try? decode(Empty.self) }
}
Usage
struct Model2: Decodable {
let num: Int
let str: String
}
struct Model: Decodable {
let num: Int
let str: String
let array1: CompactDecodableArray<Int>
let array2: CompactDecodableArray<Int>?
let array4: CompactDecodableArray<Model2>
}
let dictionary: [String : Any] = ["num": 1, "str": "blablabla",
"array1": [1,2,3],
"array3": [1,nil,3],
"array4": [["num": 1, "str": "a"], ["num": 2]]
]
let data = try! JSONSerialization.data(withJSONObject: dictionary)
let object = try JSONDecoder().decode(Model.self, from: data)
print("1. \(object.array1.elements)")
print("2. \(object.array2?.elements)")
print("3. \(object.array4.elements)")
Console
1. [1, 2, 3]
2. nil
3. [__lldb_expr_25.Model2(num: 1, str: "a")]

How can I create an array of URLs with SwiftyJSON and Alamofire? [duplicate]

This question already has answers here:
What does "Fatal error: Unexpectedly found nil while unwrapping an Optional value" mean?
(16 answers)
Closed 6 years ago.
I have the following problem I need to retrieve an array of URL's from a JSON Object in order to download all the pictures of the products from an e-commerce site in my app.
The JSON I get looks like this:
[
{
........
........
.........
........
"images": [
{
"id": 976,
"date_created": "2016-08-10T15:16:49",
"date_modified": "2016-08-10T15:16:49",
"src": "https://i2.wp.com/pixan.wpengine.com/wp-content/uploads/2016/07/canasta-familia.jpg?fit=600%2C600&ssl=1",
"name": "canasta-familia",
"alt": "",
"position": 0
}
],
.......
.......
.......
So far I've been able to get only one string from the array doing this.
Alamofire.request(url, method: .get, parameters: nil, encoding: URLEncoding.default, headers: headers)
.responseJSON { response in
if let jsonValue = response.result.value {
let jsonObject = JSON(jsonValue)
var jsonArray = jsonObject[0]["images"][0]["src"].stringValue
print(jsonArray)
}
}
which gives me this
https://xx.xx.xx/xxxx.xxxxx.xxxx/xx-xxxxx/uploads/2016/07/canasta-familia.jpg?fit=600%2C600&ssl=1
But what I need is to access all the elements inside "images" & "src" not just the first element of the index of both.
How can I do this?
Any ideas?
Step 1:
Create a custom object to represent the pictures. We'll call this "Picture".
struct Picture {
let id:Int
let date_created:String
let date_modified:String
let src:String
let name:String
let alt:String
let position:Int
}
Step 2:
Create an array to hold all of your product pictures. Make sure you pay attention to the scope in which you create it. It should ideally outside of your download function.
var productPictures = [Picture]()
Step 3:
Download your JSON file, make a Picture struct for each image, and add each Picture to your productPictures array.
Alamofire.request(url, method: .get, parameters: nil, encoding: URLEncoding.default, headers: headers)
.responseJSON { response in
switch response.result {
case .success:
self.productPictures.removeAll()
guard let json = response.result.value as? [String:Any] else {
print("couldn't retrieve json as a dictionary")
return
}
guard let images = json["images"] as? [AnyObject] else {
print("there was a problem accessing the images key")
return
}
for image in images {
guard let id = image["id"] as? Int,
let date_created = image["date_created"] as? String,
let date_modified = image["date_modified"] as? String,
let src = image["src"] as? String,
let name = image["name"] as? String,
let alt = image["alt"] as? String,
let position = image["position"] as? Int
else {
print("There was a problem accessing one of the picture variables, or it was missing")
continue
}
let newPicture = Picture(id: id,
date_created: date_created,
date_modified: date_modified,
src: src,
name: name,
alt: alt,
position: position)
self.productPictures.append(newPicture)
}
case .failure(let error):
print("could not download and retrieve product images. An error occurred: \(error)")
return
}
}
Now you have an array full of Picture structs, each containing all of the necessary information pulled from your JSON download.
Note
This doesn't use SwiftyJSON, but it should work and give you the same intended result. I hope this helps!
The following lines of code should work as I've tested myself with an actual dataset.
import Alamofire
import SwiftyJSON
Alamofire.request(url, method: .get, parameters: nil, encoding: URLEncoding.default, headers: headers)
.responseJSON { response in
if let jsonValue = response.result.value {
let jsonObject = JSON(jsonValue)
if let array = jsonObject.array {
for i in 0..<array.count {
if let images = array[i]["images"].array {
for i in 0..<images.count {
let src = images[i]["src"]
print(src) // save src in an array or whatever
}
}
}
}
}

struggling with JSON parsing in swift

I am trying to load data in JSON format from my server into IOS application.
Here is my JSON:
[
{
"BankName": "bank1",
"CurrencyName": "cur1",
"SellRate": "0.65",
"BuyRate": "0.55",
"OfficialRate": "0.6"
},
{
"BankName": "bank1",
"CurrencyName": "cur2",
"SellRate": "1.65",
"BuyRate": "1.55",
"OfficialRate": "1.6"
}
]
There are 2 files in my project:
1:
import Foundation
class Shot {
var bankName: String!
var currencyName: String!
var sellRate: String!
var buyRate: String!
var offRate: String!
init (data: NSDictionary) {
self.bankName = getStringFromJSON(data, key:"BankName")
self.currencyName = getStringFromJSON(data, key:"CurrencyName")
self.sellRate = getStringFromJSON(data, key:"SellRate")
self.buyRate = getStringFromJSON(data, key:"BuyRate")
self.offRate = getStringFromJSON(data, key: "OfficialRate")
}
func getStringFromJSON(data: NSDictionary, key: String) -> String {
if let info = data[key] as? String{
return info
}
return ""
}
}
2:
import Foundation
class JsonTest {
func loadJson(completion: ((AnyObject) -> Void)!) {
var urlString = "http://a.com/g.php"
let session = NSURLSession.sharedSession()
let sourceUrl = NSURL(string: urlString)
var task = session.dataTaskWithURL(sourceUrl!){
(data, response, error) -> Void in
if error != nil {
println(error.localizedDescription)
} else {
var error: NSError?
var jsonData = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: &error) as NSArray
var shots = [Shot]()
println(jsonData)
for shot in jsonData{
let shot = Shot(data: shot as NSDictionary)
shots.append(shot)
}
println(shots) //[jsontest.Shot, jsontest.Shot]
}
}
task.resume()
}
}
I am trying to populate array automatically when my app starts. To do it I have a code in my mainViewController class.
override func viewDidLoad() {
super.viewDidLoad()
let api = JsonTest()
api.loadJson(nil)
}
The problem occurs when I try to print shots variable in the second file.
it returns [jsontest.Shot, jsontest.Shot] when I was expecting the array of dictionaries.
println(jsonData) works fine and shows JSON data from URL.
Can anybody advise what is wrong in my program?
"shots" is an array of instances of Shot, not a dictionary:
class Shot {
var bankName: String!
var currencyName: String!
var sellRate: String!
var buyRate: String!
var offRate: String!
init (data: NSDictionary) {
self.bankName = getStringFromJSON(data, key:"BankName")
self.currencyName = getStringFromJSON(data, key:"CurrencyName")
self.sellRate = getStringFromJSON(data, key:"SellRate")
self.buyRate = getStringFromJSON(data, key:"BuyRate")
self.offRate = getStringFromJSON(data, key: "OfficialRate")
}
func getStringFromJSON(data: NSDictionary, key: String) -> String {
if let info = data[key] as? String{
return info
}
return ""
}
}
var shots = [Shot]()
let urlString = "http://almazini.lt/getrates.php"
let sourceUrl = NSURL(string: urlString)
// Using NSData instead of NSURLSession for experimenting in Playground
let data = NSData(contentsOfURL: sourceUrl!)
var error: NSError?
// As I'm using Swift 1.2 I had to change "as" with "as!"
let jsonData = NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers, error: &error) as! NSArray
for shot in jsonData{
let shot = Shot(data: shot as! NSDictionary)
shots.append(shot)
}
println(shots[0].bankName)
Update for Swift 2
var shots = [Shot]()
let urlString = "http://almazini.lt/getrates.php"
// Using NSData instead of NSURLSession for experimenting in Playground
if let sourceUrl = NSURL(string: urlString) {
NSURLSession.sharedSession().dataTaskWithURL(sourceUrl, completionHandler: { (data, response, error) in
if error == nil {
if let data = data, jsonData = try? NSJSONSerialization.JSONObjectWithData(data, options: []), jsonArray = jsonData as? [NSDictionary] {
for item in jsonArray {
let shot = Shot(data: item)
shots.append(shot)
}
print(shots[0].bankName)
} else {
print("no JSON data")
}
} else {
print(error!.localizedDescription)
}
}).resume()
}
Seems like there are two problems:
You're trying to use println to debug instead of setting a breakpoint and checking your objects values.
You have not created a description or debugDescription property for your object, so println on your object is just using some default implementation.
shots is an array of your custom object, so when you call println, it's using the description for Array, which prints out the objects in the array, comma separated, and within square brackets.
The default description property for classes in Swift just prints the class name.
Ideally, you should just use a break point to check the values of your object to be certain it initialized correctly, but if it's actually important to get them to print right, it's only a matter of implementing the description property:
override var description: String {
get {
// build and return some string that represents your Shot object
}
}

Resources