Swift compute field in struct on decode - swift

I'm working on an app which does a lot of real time computing based on values loaded in a struct using JSONDecoder(). I need to minimise computations in run time so I'd like to do some calculation up front. For this question I've created a working example with som fictional names for example purpose only
This piece of code loads data from a json. My quest is to use data from one part and inject it in another part based on name similarity of these parts.
struct Structure: Decodable {
static func withJSON(_ fileName: String) -> Structure? {
guard let url = Bundle.main.url(forResource: fileName, withExtension: "json", subdirectory: "Structures") else { return nil }
guard let data = try? Data(contentsOf: url) else { return nil }
do {
let decoded = try JSONDecoder().decode(Structure.self, from: data)
return decoded
}
catch{
print("Unexpected error Structure withJSON: \(error).")
}
return nil
}
private enum Keys: CodingKey {
case id
case rooms
case lists
}
let id: Int
let rooms: [Room]
let lists: [List]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Keys.self)
id = try container.decode(Int.self, forKey: .id)
rooms = try container.decode([Room].self, forKey: .rooms)
lists = try container.decode([List].self, forKey: .lists)
}
}
extension Structure {
struct Room: Decodable {
private enum RoomKeys: CodingKey {
case roomName
case dimension
case doors
}
let roomName: String
let dimension: [Double]
let doors: Int
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RoomKeys.self)
roomName = try container.decode(String.self, forKey: .roomName)
dimension = try container.decode([Double].self, forKey: .dimension)
doors = try container.decode(Int.self, forKey: .doors)
}
}
}
extension Structure {
struct List: Decodable {
private enum RoomKeys: CodingKey {
case roomName
case calculated
}
let roomName: String
var calculated: Double?
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RoomKeys.self)
roomName = try container.decode(String.self, forKey: .roomName)
calculated = 0 // This variable should be filled with the array sum of Structure.Room.dimensions bonus for minus the doors of that (room * 2)
}
}
}
In this over simplified example I'd like to compute Structure.Lists.calculated with the sum of the array which sits in rooms.dimension with both the SAME roomName. See this json example:
{
"id": 1,
"rooms": [
{
"roomName": "living",
"dimension": [6,4,9],
"doors": 2
},
{
"roomName": "hall",
"dimension": [2,4,4],
"doors": 2
},
{
"roomName": "bathroom",
"dimension": [1,1,1],
"doors": 2
}
],
"lists": [
{
"roomName": "living"
},
{
"roomName": "bathroom"
}
]
}
In this example the optional Structure.List.calculated with the roomName "living" should be filled with the sum of dimension [6,4,9] thus 19 (bonus to subtract the doors * 2)
Can this be done on the fly while decoding? Or is this data not available yet since the struct is still loading? Then what approach should I take?

When decoding your "Structure", you can process your raw decoded Lists and store processed versions:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Keys.self)
id = try container.decode(Int.self, forKey: .id)
rooms = try container.decode([Room].self, forKey: .rooms)
let rawLists = try container.decode([List].self, forKey: .lists)
var listsWithSums: [List] = []
for var list in rawLists {
if let room = rooms.first(where: { $0.roomName == list.roomName }) {
list.calculated = room.dimension.reduce(0, +)
- Double(room.doors)
}
listsWithSums.append(list)
}
lists = listsWithSums
}

Related

How to write the codable in generic format [duplicate]

This question already has answers here:
What Is Preventing My Conversion From String to Int When Decoding Using Swift 4’s Codable?
(2 answers)
Closed 2 years ago.
I have understand how to make the codable wrapper class for services response structure.
But some times in server side the attribute value varies It may Int Or String.
Example
struct ResponseDataModel : Codable{
let data : DataClass?
enum CodingKey: String, CodingKey{
case data = "data"
}
init(from decoder: Decoder) throw {
let values = try decoder.container(keyedBy: CodingKeys.self)
data = try values.decodeIfPresent(DataClass.self, forKey:.data)
}
}
struct DataClass : Codable{
let id : Int
let name : String?
let age : Int?
enum CodingKey: String, CodingKey{
case id = "id"
case name = "name"
case age = "age"
}
init(from decoder: Decoder) throw {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decodeIfPresent(Int.self, forKey:.it)
name = try values.decodeIfPresent(String.self, forKey:.name)
age = try values.decodeIfPresent(Int.self, forKey:.age)
}
}
I would like to use generic way if id int string no matter what its it should bind to my controller with id value data.
let id : <T>
How to write the codable in generic formate.
You can do that using the following model:
struct ResponseDataModel<T: Codable>: Codable{
let data : DataClass<T>?
enum CodingKeys: String, CodingKey{
case data = "data"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
data = try values.decodeIfPresent(DataClass<T>.self, forKey:.data)
}
}
struct DataClass<T: Codable>: Codable {
let id: T?
let name: String?
let age: Int?
enum CodingKeys: String, CodingKey{
case id = "id"
case name = "name"
case age = "age"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decodeIfPresent(T.self, forKey:.id)
name = try values.decodeIfPresent(String.self, forKey:.name)
age = try values.decodeIfPresent(Int.self, forKey:.age)
}
}
However, you should always known the type of the id property when you call decode(_:from:) function of JSONDecoder like this:
let decoder = JSONDecoder()
do {
let decoded = try decoder.decode(ResponseDataModel<Int>.self, from: data)
print(decoded)
} catch {
print(error)
}
Or you can use the following model to always map the id as Int, even if your server sends it as String:
struct ResponseDataModel: Codable{
let data : DataClass?
enum CodingKeys: String, CodingKey{
case data = "data"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
data = try values.decodeIfPresent(DataClass.self, forKey:.data)
}
}
struct DataClass: Codable {
let id: Int?
let name: String?
let age: Int?
enum CodingKeys: String, CodingKey{
case id = "id"
case name = "name"
case age = "age"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
do {
id = try values.decodeIfPresent(Int.self, forKey:.id)
} catch DecodingError.typeMismatch {
if let idString = try values.decodeIfPresent(String.self, forKey:.id) {
id = Int(idString)
} else {
id = nil
}
}
name = try values.decodeIfPresent(String.self, forKey:.name)
age = try values.decodeIfPresent(Int.self, forKey:.age)
}
}
First of all, here are some key points to take case when using Codable for parsing.
There is no need to every time implement enum CodingKeys if the property names and keys have exactly same name.
Also, no need to implement init(from:) if there no specific parsing requirements. Codable will handle all the parsing automatically if the models are written correctly as per the format.
So, with the above 2 improvements your ResponseDataModel looks like,
struct ResponseDataModel : Codable{
let data: DataClass?
}
Now, for DataClass you simply need to add an if-else condition to handle the Int and String cases. Implementing generics is not needed here.
Use String or Int as the type for id. And add the conditions accordingly. In the below code, I'm using id as String.
struct DataClass : Codable {
let id : String //here....
let name : String?
let age : Int?
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decodeIfPresent(String.self, forKey: .name)
age = try values.decodeIfPresent(Int.self, forKey: .age)
if let id = try? values.decode(Int.self, forKey: .id) {
self.id = String(id)
} else {
self.id = try values.decode(String.self, forKey:.id)
}
}
}
As per #Joakim Danielson example provided, you can reach desired result by attempting to decode value for each type.
struct Response: Decodable {
let id: String
let name: String?
let age: Int?
private enum CodingKeys: String, CodingKey {
case data
}
private enum NestedCodingKeys: String, CodingKey {
case id
case name
case age
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let nestedContainer = try container.nestedContainer(
keyedBy: NestedCodingKeys.self,
forKey: .data
)
if let id = try? nestedContainer.decode(Int.self, forKey: .id) {
self.id = String(id)
} else {
id = try nestedContainer.decode(String.self, forKey: .id)
}
name = try nestedContainer.decodeIfPresent(String.self, forKey: .name)
age = try nestedContainer.decodeIfPresent(Int.self, forKey: .age)
}
}
As #gcharita illustrated you can also catch DecodingError, but do-catch statement for decode(_:forKey:) would act only as an early exit, since it throws one of the following errors - typeMismatch, keyNotFound or valueNotFound for that particular key-value pair.

How to save and get data from iOS SQLite database?

I would like to te "label" and "slug" String from the database. They are printed well in init(from decoder: Decoder), but I probably do something wrong with saving them, because anywhere else they're gone.
This is my Decodable Class
import Foundation
import SQLite
final class AdditionalField : Decodable {
var label : String?
var slug : String?
var data: Any?
var type: QuestionType?
var pollQuestion: PollQuestion?
enum CodingKeys: String, CodingKey {
case label = "label"
case slug = "slug"
}
required init(from row: Row) {
label = row[Columns.label]
slug = row[Columns.slug]
if let dataString = data as? String, let data = dataString.data(using: .utf8) {
self.data = type?.create(from: data)
}
}
required init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
label = try values.decodeIfPresent(String.self, forKey: .label)
print(label!)
print("label")
slug = try values.decodeIfPresent(String.self, forKey: .slug)
print(slug!)
print("slug")
}
}
extension AdditionalField: SQLTable {
static var tableName = "AdditionalField"
enum Columns {
static let label = Expression<String>("label")
static let slug = Expression<String>("slug")
}
static func createTable(db: Connection) throws {
try db.run(table.create(ifNotExists: true) { table in
table.column(Columns.label)
table.column(Columns.slug)
})
}
static func insert(elements: [AdditionalField]) throws {
try Database.shared.insert(elements: elements)
}
func insert(db: Connection) throws {
try db.run(AdditionalField.table.insert(or: .replace, [
Columns.label <- label ?? "",
Columns.slug <- slug ?? ""
]))
}
}
I've tried to used them like that. Maybe here is the problem ?
// let field = additionalFields?.filter{$0.label == anyTextField.placeholder}.first
// anyTextField.placeholder = field?.label
I created a library called SundeedQLite
Feel free to try it!

Swift: decode data into object which is an inherited class comes error

I'm working on decode data into object. The model classes are:
class ZZBaseObj: Codable {
var fid: String
init() {
fid = ""
}
private enum CodingKeys:String, CodingKey {
case id
}
required init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
fid = try values.decode(String.self, forKey: CodingKeys.id)
}
}
class ZZFileObj: ZZBaseObj {
var columns:[String]
override init() {
columns = [String]()
super.init()
}
private enum CodingKeys:String, CodingKey {
case columns
}
required init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
columns = try values.decode([String].self, forKey: CodingKeys.columns)
try super.init(from: decoder)
}
}
The json data looks like:
(
{
columns = (
"first name",
last,
subject,
address,
age
);
id = "1lYc8iIzMdfdfgjGYMKwO4-X3fWe-4GlzfQ-dfs";
},
{
columns = (
"2018/1/30",
100,
"9800"
);
id = "1fdsafdgfQL-fdfdf-fdf";
}
)
When I try to use JSONDecoder() to decode the data into object, there is no error. I get the resultArray, and in the console, I can see the elements. Then it comes to EXC_BAD_ACCESS when comes to following code:
let obj = resultArray[0]
let content = obj.columns //---------error happens here
I understand it is a memory issue, but when I try to set breakpoint here, and in lldb input:
po obj.columns
It prints out the content of the columns.
This confuses me. As the class ZZFileObj is inherited from ZZBaseObj, and I guess there is something missing for inheritance. I try to make a new class which includes fid, and columns, and that comes no error. However, there a common attribute so I need such a base class for the models.
Please help take a look.
This is an issue in Swift 4.1.
https://bugs.swift.org/browse/SR-7090
You can fix for now by removing Codable conformance from base class. And once the issue is fixed in coming version of Xcode/Swift, you can revert back.
Here is how you can achieve this with minimal effort to revert back in future by moving Codable to base class. You can also try in beta version of Xcode 10 where your current code might work as expected.
class ZZBaseObj {
var id: String
init() {
id = ""
}
private enum CodingKeys:String, CodingKey {
case id
}
required init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode(String.self, forKey: CodingKeys.id)
}
}
class ZZFileObj: ZZBaseObj, Codable {
var columns:[String]
override init() {
columns = [String]()
super.init()
}
private enum CodingKeys:String, CodingKey {
case columns
}
required init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
columns = try values.decode([String].self, forKey: CodingKeys.columns)
try super.init(from: decoder)
}
}
let json = """
[
{
"columns":["some text 1", "some text 2"],
"id": "1lYc8iIzMdfdfgjGYMKwO4-X3fWe-4GlzfQ-dfs"
},
{
"columns":["some text 3", "some text 4"],
"id": "1lYc8iIzMdfdfgjGYMKwO4-X3fWe-4GlzfQ-dfs"
}
]
""".data(using: .utf8)!
let decoder = JSONDecoder()
let obj = try! decoder.decode([ZZFileObj].self, from: json)
print(obj.first!.columns)
print(obj.first!.id)
Output:
["some text 1", "some text 2"]
1lYc8iIzMdfdfgjGYMKwO4-X3fWe-4GlzfQ-dfs

How could I silently ignore objects not being decoded in a list using Swift 4's Codable protocol? [duplicate]

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")]

Codable/Decodable should decode Array with Strings

Why is the names Array not decoding?
Prepared for Playground, Simple paste this into your playground
import Foundation
struct Country : Decodable {
enum CodingKeys : String, CodingKey {
case names
}
var names : [String]?
}
extension Country {
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
names = try values.decode([String]?.self, forKey: .names)!
}
}
let json = """
[{
"names":
[
"Andorre",
"Andorra",
"アンドラ"
]
},{
"names":
[
"United Arab Emirates",
"Vereinigte Arabische Emirate",
"Émirats Arabes Unis",
"Emiratos Árabes Unidos",
"アラブ首長国連邦",
"Verenigde Arabische Emiraten"
]
}]
""".data(using: .utf8)!
let decoder = JSONDecoder()
do {
let countries = try decoder.decode([Country].self, from: json)
countries.forEach { print($0) }
} catch {
print("error")
}
You have defined names as an optional property of Country.
If your intention is that this key may not be present in the JSON
then use decodeIfPresent:
extension Country {
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
names = try values.decodeIfPresent([String].self, forKey: .names)
}
}
This method returns nil if the container does not have a value associated with key, or if the value is null.
But actually you can just omit your custom init(from decoder: Decoder)
implementation (and the enum CodingKeys), because that is the default behaviour and will
be synthesized automatically.
Remark: An implicit variable error is defined in any catch clause,
so
} catch {
print(error.localizedDescription)
}
can be more informative than just a print("error") (although not
in this particular case).