Swift 4 Using Generics as Return Value - swift

I have a protocol like so:
protocol ModelProtocol{
func parse<T.Model>(data: Data) -> Array<T>? {}
}
The return is an array of option values. The method takes in data, parses it and returns the array of parsed objects from API data.
The type of data that the API returns is called MyData that has an array as the value of the dictionary.
I parse the JSON like so
func parse<T>(data: Data) -> Array<T>? {
do {
let newJSONDecoder = JSONDecoder()
let menu = try newJSONDecoder.decode(MyData.self, from:data)
let dataArray = menu.data //array
let modelArray = Array<T>()
for object in dataArray {
modelArray.append(object)
}
return modelArray
}
catch {
print("error while parsing:\(error.localizedDescription)")
return nil
}
}
I get error on the line where I append into the array to be returned.
Cannot invoke 'append' with an argument list of type '(MyData.Drinks)'
Ultimately I want the returned array to have objects of the type that is in the array MyData.data- in this case, the type is Drinks. But more broadly, I want the method to return any type that is in any JSON payload. The goal is to create a method that can take in any data and return any object as parsed object of type X in the array.
How do I do this?

First of all the code does not compile:
Protocol methods must not have bodies
so you have to remove the braces:
protocol ModelProtocol{
func parse<T : Decodable>(data: Data) -> Array<T>?
}
To solve your problem create MyData also as generic
struct MyData<T : Decodable> : Decodable {
let data : [T]?
}
and declare parse
func parse<T : Decodable>(data: Data) -> Array<T>? {
do {
let newJSONDecoder = JSONDecoder()
let menu = try newJSONDecoder.decode(MyData<T>.self, from:data)
return menu.data
}
catch {
print("error while parsing: ", error)
return nil
}
}
print always the entire error to get detailed information about the decoding error. localizedDescription is too broad.
If data is expected to be non-optional make parse throw and hand over the decoding error
protocol ModelProtocol{
func parse<T : Decodable>(data: Data) throws -> Array<T>
}
struct MyData<T : Decodable> : Decodable {
let data : [T]
}
func parse<T : Decodable>(data: Data) throws -> Array<T> {
let newJSONDecoder = JSONDecoder()
let menu = try newJSONDecoder.decode(MyData<T>.self, from:data)
return menu.data
}

Related

Use of flatMap on a generic Publisher results in a compile error

I'm writing a transform function that would take network request results and try to parse them automatically using a dict to Model transformer(not Decodable due to several backend reasons).
So the chain should look like this:
func getModel -> Single<Model> {
return networkRequest(requestParameters).parse(modelTranslator)
}
The translator is a generic protocol:
public protocol Translator {
associatedtype Model
func translateFrom(dictionary json: [String: Any]) throws -> Model
}
Single is a wrapper around Deferred and Future:
public typealias Single<T> = Deferred<Future<T, Error>>
The problematic parse extension method here is:
public extension Publisher {
func parse<T: Translator, M>(translator: T) -> Single<M> where T.Model == M {
return self.flatMap { (data: Data) -> Single<M> in
return Deferred {
return Future<M, any Error> { promise in
guard
let json = try? JSONSerialization.jsonObject(with: data, options: []),
let dict = json as? [String : Any]
else {
let error: any Error = TranslatorError.invalidJSONObject
return promise(Result.failure(error))
}
do {
let translatedModel: M = translator.translateFrom(dictionary: dict)
return promise(Result.success(translatedModel))
} catch let error {
return promise(Result.failure(error))
}
}
}
}
}
}
It won't compile. It shows 2 errors on the .flatmap row:
No 'flatMap' candidates produce the expected contextual result type 'Single' (aka 'Deferred<Future<M, any Error>>')
No exact matches in call to instance method 'flatMap'
I believe that it has something to do with a type mismatch?
Could you please help me see the problem?
Thank you in advance!
You are trying too hard. A simple tryMap is all you need to parse your [String: Any] into the appropriate model type. Here is a complete example:
func getFoo(_ requestParameters: RequestParameters) -> AnyPublisher<Foo, Error> {
getModel(requestParameters, modelTranslator: FooTranslator())
}
func getModel<T>(_ requestParameters: RequestParameters, modelTranslator: T) -> AnyPublisher<T.Model, Error> where T: Translator {
networkRequest(requestParameters)
.tryMap { try modelTranslator.translateFrom(dictionary: $0) }
.eraseToAnyPublisher()
}
The above assumes the following declarations:
func networkRequest(_ params: RequestParameters) -> Single<[String: Any]> ...
struct FooTranslator: Translator {
func translateFrom(dictionary json: [String : Any]) throws -> Foo ...
}

Swift Invalid type in JSON write error using JSONSerialization.data

I have an array with elements of a custom type. Here is the type :
public class RequestElemDataBody: Codable {
public var name: String
public var value: String
public init(name: String, value: String) {
self.name = name
self.value = value
}
}
This is how I declare my array :
var elementsInForm = [RequestElemDataBody]()
I use a function to convert this array to Data then to String :
func json(from object: [Any]) -> String? {
guard let data = try? JSONSerialization.data(withJSONObject: object, options: []) else {
return nil
}
return String(data: data, encoding: String.Encoding.utf8)
}
When executing I get this error message :
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (Data.RequestElemDataBody)'
I don't know what is wrong with my custom type since it is Codable.
How can I parse my array with my function without it throwing an error ?
You should use JSONEncoder when serializing Codable.
The example from the documentation page:
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
let pear = GroceryProduct(name: "Pear", points: 250, description: "A ripe pear.")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try encoder.encode(pear)
print(String(data: data, encoding: .utf8)!)
/* Prints:
{
"name" : "Pear",
"points" : 250,
"description" : "A ripe pear."
}
*/
Problem with your approach is that you try to encode the whole array. But your codable object is not an array at the moment.
Try passing single element to your method and return the string to work with it outside;
func json(from element: RequestElemDataBody) -> String {
...
}
As mentioned here the top level object can be Array or Dictionary and all objects can be instances of NSString, NSNumber, NSArray, NSDictionary, or NSNull.
. In your case they are instances of an custom object.
So you can try converting the objects to dictionary first and then use JSONSerialization
And to convert to dictionary you can create an extension on Encodable
extension Encodable {
var dict : [String: Any]? {
guard let data = try? JSONEncoder().encode(self) else { return nil }
guard let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String:Any] else { return nil }
return json
}
}
and while calling function you can use ArrayName.compactMap({$0.dict})
Also you can use ObjectMapper library.
Reference from: How to convert a Swift object to a dictionary

What is the type of struct.self in swift?

I have a structural variable that extends Codable protocol, and want to memorize its type, so that I can use it next time in JSONDecoder. However, when a private variable is declared, its class needs to be specified, but whatever class I'm trying use, I cannot use the result later in JSONDecoder. So my question is what is the type of myVariable.self?
I have this problem, because I want to specify the class when a view decoder is initialized. During the initialization, I call the following function (this part of the code works well):
func getData<T: Codable>(fromURL: String, onSuccess: #escaping (T) -> Void, onError: #escaping (String) -> Void) {
let url = URL(string: fromURL)!
let task = session.dataTask(with: url) {(data, response, error) in
DispatchQueue.main.async {
if let error = error {
onError(error.localizedDescription)
return
}
guard let data = data, let response = response as? HTTPURLResponse else {
onError("Invalid data or response")
return
}
do {
if response.statusCode == 200 {
let ret = try JSONDecoder().decode(T.self, from: data)
onSuccess(ret)
} else {
let err = try JSONDecoder().decode(APIError.self, from: data)
onError(err.message)
}
} catch {
onError(error.localizedDescription)
}
}
}
task.resume()
}
This part of the code works well. However, if I need to upload more data, I need to know T.self for proper decoder, so I need to keep it. A solution could be to create a private variable:
private var type: ??? // what type should I put here?
and to write in the function's body
type = T.self
But whichever type I try, it doesn't work.
What type should I put there? Or, maybe, there are some other solutions?
You can declare the property
private var type: Codable.Type
or wrap the method in a generic struct or class and declare the property
class CodableWrapper<T : Codable> {
private var type: T.Type
...

How can I type check a generic then cast to it in swift?

I like the ideas presented in this post, about making database-agnostic, protocol-oriented code.
So say I have a protocol such as:
protocol Database {
func loadObjects<T>(matching query: Query) -> [T]
func loadObject<T>(withID id: String) -> T?
func save<T>(_ object: T)
}
where Query is a struct that has filter and sort specifiers.
Then, given a persistence framework, such as Realm or CoreData, I can just support this protocol, as such:
extension NSManagedObjectContext: Database {
...
}
extension Realm: Database {
...
}
extension MockedDatabase: Database {
...
}
extension UITestingDatabase: Database {
...
}
The issue arises when I would want to use CoreData.
If we look at the method:
func loadObjects<T>(matching query: Query) -> [T]
I have no way to 'cast' T to NSManagedObject.
For example, my desired implementation might be something like:
extension NSManagedObjectContext: Database {
func loadObjects<T>(matching query: Query<T>) -> [T] {
// you need a fetch request for these models. This guard statement compiles. How do we make it work with NSFetchRequestResult however?
guard T.self is NSManagedObject.Type else {
return []
}
// This line below Fails compiling. Type 'T' does not conform to protocol 'NSFetchRequestResult'
var request = NSFetchRequest<T>(entityName: String(describing: T))
// then set sortDescriptors and predicate, etc.
var objects: [T] = []
self.performAndWait {
do {
if let results = try self.fetch(request!) as? [T] {
objects = results
}
} catch let error {
print("Error fetching: \(error.localizedDescription)")
}
}
return objects
}
}
So if I can determine if T's Type is a kind of NSManagedObject, then is it possible to instantiate a NSFetchRequest by 'casting' T to something that will work? It would seem I can't cast or force T to be anything.
Database is a technology-agnostic protocol and therefore shouldn't know anything about Core Data. I'd like to do this in the event I need to change my data persistence framework.
How might I accomplish this in Swift? Would I have to add to the Model protocol to return optionals that would be used for the given frameworks I would support? Or make them support NSFetchRequestResult? I would rather that only the implementation of the protocol need to care about the details of the persistence framework.
Rather than checking the type at runtime constrain the type at compile time, for example
extension NSManagedObjectContext: Database {
func loadObjects<T: NSManagedObject>(matching query: Query<T>) -> [T] {
let request = NSFetchRequest<T>(entityName: String(describing: T.self))
var objects = [T]()
self.performAndWait {
do {
objects = try self.fetch(request) // a generic fetch request returns an array of the generic type or throws an error
} catch let error {
print("Error fetching: \(error.localizedDescription)")
}
}
return objects
}
}
It looks like I can answer my own question. One can further constrain generic types by overloading them, so that the calling code can remain the same, but what gets called is dependent on the type you pass into it.
This code below demonstrates this in a playground:
public protocol Action {
func doSomething<T>(to object: T)
}
public class MyActor {
}
extension MyActor: Action {
// works for any type
public func doSomething<T>(to object: T) {
print("was generic")
}
// but if you constrain the type and your object fits that constraint...
// this code is called (same method signature)
public func doSomething<T: NSObject>(to object: T) {
print("was an object")
}
}
class MyObject: NSObject {
var name: String = "Object"
}
struct MyStruct {
var name: String = "Struct"
}
let actor = MyActor()
let object = MyObject()
let value = MyStruct()
actor.doSomething(to: value) // prints 'was generic'
actor.doSomething(to: object) // prints 'was an object'
So in the original example, I would then support Database for CoreData with:
extension NSManagedObjectContext: Database {
func loadObjects<T>(matching query: Query<T>) -> [T] {
return [] // return an empty array because we only support NSManagedObject types
}
func loadObjects<T: NSManagedObject>(matching query: Query<T>) -> [T] {
let request = NSFetchRequest<T>(entityName: String(describing: T.self))
var objects = [T]()
self.performAndWait {
do {
objects = try self.fetch(request) // a generic fetch request returns an array of the generic type or throws an error
} catch let error {
print("Error fetching: \(error.localizedDescription)")
}
}
return objects
}
}

Is it possible to abort a map function on a Swift collection?

We have a case where we're being handed an object of type Array<Any> which we need to convert to an Array<Codable>. If any of the items in the original array don't adhere to Codable, then we want the entire thing to abort and return nil.
Or current approach is to manually loop over everything, testing along the way, like so...
func makeCodable(sourceArray:Array<Any>) -> Array<Codable>?{
var codableArray = Array<Codable>()
for item in sourceArray{
guard let codableItem = item as? Codable else {
return nil
}
codableArray.append(codableItem)
}
return codableArray
}
However, I'm wondering if there's an easier way to do this with the map command, but it would require it to short-circuit if any of the elements can't be mapped. That's what I'm not sure or not is possible.
For instance, this pseudo-code...
func makeCodable(sourceArray:Array<Any>) -> Array<Codable>?{
return sourceArray.map({ $0 as? Codable});
}
Is this possible, or is our original way the correct/only way?
Here's one solution using map and throws.
func makeCodable(sourceArray: [Any]) -> [Codable]? {
enum CodableError: Error {
case notCodable
}
let res: [Codable]? = try? sourceArray.map {
guard let codable = $0 as? Codable else {
throw CodableError.notCodable
}
return codable
}
return res
}
let res = makeCodable2(sourceArray: [5, 6.5, "Hi", UIView()])
print(res) // nil
Here's a variation that makes makeCodable throw and return a non-optional array:
enum CodableError: Error {
case notCodable
}
func makeCodable(sourceArray: [Any]) throws -> [Codable] {
let res: [Codable] = try sourceArray.map {
guard let cod = $0 as? Codable else {
throw CodableError.notCodable
}
return cod
}
return res
}
do {
let res = try makeCodable(sourceArray: [5, 6.5, "Hi"])
print(res) // prints array
let bad = try makeCodable(sourceArray: [5, 6.5, "Hi", UIView()])
print(bad)
} catch {
print(error) // goes here on 2nd call
}
As #rmaddy shows, you can utilise the fact that map(_:) can accept a throwing closure, and will stop mapping upon an error being thrown, propagating the error thrown back to the caller (which you can then absorb with try?).
One slight variation on this would be to define your own throwing cast(_:to:) function to call in the transformation closure:
struct TypeMismatchError : Error {
var expected: Any.Type
var actual: Any.Type
}
func cast<T, U>(_ x: T, to _: U.Type) throws -> U {
guard let casted = x as? U else {
throw TypeMismatchError(expected: U.self, actual: type(of: x))
}
return casted
}
func makeCodable(sourceArray: [Any]) -> [Codable]? {
return try? sourceArray.map { try cast($0, to: Codable.self) }
}
Although we're completely ignoring the error thrown in this case, I have found it occasionally useful in other cases to have a throwing casting function about (you could of course also propagate the error by making makeCodable a throwing function and using try).
However, that all being said, note that your resulting [Codable]? really isn't too useful in its current form. You can't decode stuff from it because you don't have any concrete types to hand, and you can't directly encode it as protocols don't conform to themselves (i.e Codable doesn't conform to Encodable, so you can't just hand off a Codable or [Codable] to JSONEncoder).
If you actually wanted to do some encoding with your [Codable], you'd need to wrap each of the elements in a Encodable conforming wrapper, for example:
struct AnyEncodable : Encodable {
var base: Encodable
init(_ base: Encodable) {
self.base = base
}
func encode(to encoder: Encoder) throws {
try base.encode(to: encoder)
}
}
func makeEncodable(sourceArray: [Any]) -> [AnyEncodable]? {
return try? sourceArray.map {
AnyEncodable(try cast($0, to: Encodable.self))
}
}
Now [AnyEncodable] is something you can pass off to, for example, JSONEncoder.