Trying to decode arrays with Argo - arrays

I am trying to decode data from JSON into a very generic struct using Argo (https://github.com/thoughtbot/Argo):
struct ValueBox<T: Decodable where T == T.DecodedType> {
let value: T
}
extension ValueBox: Decodable {
static func decode(json: JSON) -> Decoded<ValueBox> {
let r = curry(ValueBox.init)
<^> json <| "value"
return r
}
}
extension Array: Decodable {
public typealias DecodedType = Array<Element>
}
extension Array {
public static func decode(json: JSON) -> Decoded<Array<Element>> {
return Decoded<Array>.customError("not implemented")
}
}
This compiles. I know it would not be able to decode ValueBox in case of T being an array. But that is a second problem.
If I now try to use Argo for decoding:
func testExample() {
let jsonDict_Int: [String : AnyObject] = [
"value" : 5
]
let jsonDict_IntArray: [String : AnyObject] = [
"value" : [5]
]
let intBox: Decoded<ValueBox<Int>> = decode(jsonDict_Int)
let intArrayBox: Decoded<ValueBox<Array<Int>>> = decode(jsonDict_IntArray)
}
I get a compiler error that "Array<Int> does not conform to protocol 'Decodable'". But why? I provided the extension to make it conform, or am I missing something obvious?

For the second example, I think you must use <^> json <|| "value" instead of <^> json <| "value"
When you use an Array of value you must use the operator <||
a great link to the detailed Argo's documentation

Related

Swift 3 - Pass Dictionary inside Array from JSON to Another Method

I have the following JSON:
{
"stores":2,
"store_data":[
{
"store_id":1,
"store_name":"Target"
"store_color":"000000"
},
{
"store_id":2,
"store_name":"Walmart"
"store_color":"FFFFFF"
}
]
}
And I am collecting it (within a function) the following way (safeguards removed for simplicity):
let task = URLSession.shared.dataTask(with: baseURL) { (data, response, error) in
if let tmpRawData: NSDictionary = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary {
process(rawData: tmpRawData)
}
}
And sending it to the helper process function defined as:
func process(rawData: NSDictionary) -> Bool {
if let storeDataArray = rawData["store_data"] {
// Here I want to loop through the store_data array
}
}
And I am having some trouble looping through the array in the function above. Initially, I had tried:
for store: Dictionary<String, String> in storeDataArray {
// Do things with store["store_id"] here
}
But I am new to swift and am having trouble deciphering between NSArray, Array, Dictionary, NSDictionary, etc. I'm working in Swift 3. Any help is much appreciated!
First of all, don't annotate types that much. The compiler will tell you if it needs an explicit annotation
Second of all it's convenient to declare a type alias for a JSON dictionary
typealias JSONObject = [String:Any]
This is the task, tmpRawData is a dictionary – represented by {} in the JSON.
let task = URLSession.shared.dataTask(with: baseURL) { (data, response, error) in
if let tmpRawData = try JSONSerialization.jsonObject(with: data, options: []) as! JSONObject {
process(rawData: tmpRawData)
}
}
and the process function, the type alias makes everything more readable.
The value of rawData is an array of dictionaries – represented by [] in the JSON.
func process(rawData: JSONObject) -> Bool {
if let storeDataArray = rawData["store_data"] as? [JSONObject] {
for store in storeDataArray {
let storeID = store["store_id"] as! Int
let storeName = store["store_name"] as! String
let storeColor = store["store_color"] as! String
print(storeID, storeName, storeColor)
}
}
}
I have no idea why all tutorials suggests the mutableContainers option. You don't need it at all in Swift when using native collection types.

Store generic Arrays in NSUserDefaults

I'm trying to store a generic Array in NSUserDefaults but I get the following error: Cannot convert value of type 'Array<T>' to expected argument type 'AnyObject?'.
How can I solve this problem?
public class PropertyStore {
private let userDefaults = NSUserDefaults.standardUserDefaults()
public func loadSet<T>(key: String) -> Set<T>? {
guard let array = userDefaults.objectForKey(key) as? [T] else {
return nil
}
return Set<T>(array)
}
public func saveSet<T>(key: String, value: Set<T>) {
let array = Array(value)
userDefaults.setObject(array, forKey: key) // <- ERROR
}
}
Like #lucasD said, T needs to conform to NSCoding atleast. So the code looks like this.
public func saveSet<T: NSCoding>(key: String, value: Set<T>) {
let array = Array(value)
userDefaults.setObject(array, forKey: key)
}
However, this will not work for many reasons like:
public func loadSet<T: NSCoding>(key: String) -> Set<T>? {
guard let array = userDefaults.objectForKey(key) as? [T] else {
return nil
}
return Set<T>(array)
}
let defaults = PropertyStore()
defaults.saveSet("array", value: [1,2,3])
///defaults.loadSet<Int>("array") ===> Cannot explicitly specialize a function
///defaults.loadSet("array") ===> Cannot infer Type T
//let a: Set<Int>? = defaults.loadSet("array") ==> T cannot be inferred
In the case of loadSet type T cannot be inferred properly because we cannot specify it from outside, as far as i know. I would first try to return NSObject or Set<AnyObject> or Set<NSCoding> and then type cast it explicitly. Let me know if theres a better way though.
You can take a look at this SO post for more information on why a generics parameter cannot be specialised from outside. SO Generics specialization
You should define T as NSCoding conforming class. So when you're going to store the array, you are going to store an array of NSKeyedArchiver.archiveDataWithRootObject() results. Then, to return a Set<T> in the loadSet method, you should unarchive all the objects:
let storedData = NSUserDefaults.standardUserDefaults. ...
return storedData.flatMap { return NSKeyedUnarchiver.unarchiveObjectWithData($0) }

Extend Swift Array to Filter Elements by Type

How can a swift array be extended to access members of a particular type?
This is relevant if an array contains instances of multiple classes which inherit from the same superclass. Ideally it would enforce type checking appropriately.
Some thoughts and things that don't quite work:
Using the filter(_:) method works fine, but does enforce type safety. For example:
protocol MyProtocol { }
struct TypeA: MyProtocol { }
struct TypeB: MyProtocol { }
let myStructs:[MyProtocol] = [ TypeA(), TypeA(), TypeB() ]
let filteredArray = myStructs.filter({ $0 is TypeA })
the filteredArray contains the correct values, but the type remains [MyProtocol] not [TypeA]. I would expect replacing the last with let filteredArray = myStructs.filter({ $0 is TypeA }) as! [TypeA] would resolve that, but the project fails with EXEC_BAD_INSTRUCTION which I do not understand. Perhaps type casting arrays is not possible?
Ideally this behavior could be wrapped up in an array extension. The following doesn't compile:
extension Array {
func objectsOfType<T:Element>(type:T.Type) -> [T] {
return filter { $0 is T } as! [T]
}
}
Here there seem to be at least two problems: the type constraint T:Element doesn't seem to work. I'm not sure what the correct way to add a constraint based on a generic type. My intention here is to say T is a subtype of Element. Additionally there are compile time errors on line 3, but this could just be the same error propagating.
SequenceType has a flatMap() method which acts as an "optional filter":
extension SequenceType {
/// Return an `Array` containing the non-nil results of mapping
/// `transform` over `self`.
///
/// - Complexity: O(*M* + *N*), where *M* is the length of `self`
/// and *N* is the length of the result.
#warn_unused_result
#rethrows public func flatMap<T>(#noescape transform: (Self.Generator.Element) throws -> T?) rethrows -> [T]
}
Combined with matt's suggestion to use as? instead of is you
can use it as
let myStructs:[MyProtocol] = [ TypeA(), TypeA(), TypeB() ]
let filteredArray = myStructs.flatMap { $0 as? TypeA }
Now the type of filteredArray is inferred as [TypeA].
As an extension method it would be
extension Array {
func objectsOfType<T>(type:T.Type) -> [T] {
return flatMap { $0 as? T }
}
}
let filteredArray = myStructs.objectsOfType(TypeA.self)
Note: For Swift >= 4.1, replace flatMap by compactMap.
Instead of testing (with is) how about casting (with as)?
let myStructs:[MyProtocol] = [ TypeA(), TypeA(), TypeB() ]
var filteredArray = [TypeA]()
for case let t as TypeA in myStructs {filteredArray.append(t)}
Casting arrays does not work in Swift. This is because arrays in Swift use generics, just like you can't cast a custom class, where only the type T changes. (class Custom<T>, Custom<Int>() as! Custom<String>).
What you can do is create an extension method to Array, where you define a method like this:
extension Array {
func cast<TOut>() -> [TOut] {
var result: [TOut] = []
for item in self where item is TOut {
result.append(item as! TOut)
}
return result
}
}
I think the canonical FP answer would be to use filter, as you are, in combination with map:
let filteredArray = myStructs.filter({ $0 is TypeA }).map({ $0 as! TypeA })
alternatively, you can use reduce:
let filtered2 = myStructs.reduce([TypeA]()) {
if let item = $1 as? TypeA {
return $0 + [item]
} else {
return $0
}
}
or, somewhat less FP friendly since it mutates an array:
let filtered3 = myStructs.reduce([TypeA]()) { ( var array, value ) in
if let item = value as? TypeA {
array.append(item)
}
return array
}
which can actually be shortened into the once again FP friendly flatMap:
let filtered4 = myStructs.flatMap { $0 as? TypeA }
And put it in an extension as:
extension Array {
func elementsWithType<T>() -> [T] {
return flatMap { $0 as? T }
}
}
let filtered5 : [TypeA] = myStructs.elementsWithType()

indexOf of AnyObject array not working with Strings

So I'm having the following code in a playgroung
var array: [AnyObject] = ["", "2", "3"]
let index = array.indexOf("")
And XCode is marking a compiler error
Cannot convert value of type 'String' to expected argument type '#noescape (AnyObject) throws -> Bool'
So my question is how do I get the indexOf an element in an Array in of AnyObjects?
You can also cast to [String] if you're sure it will cast safely
jvar array: [AnyObject] = ["", "2", "3"]
let index = (array as! [String]).indexOf("")
Try this
var array = ["", "2", "3"]
let index = array.indexOf("")
or you can use the NSArray method:
var array: [AnyObject] = ["", "2", "3"]
let index = (array as NSArray).indexOfObject("")
You should never use AnyObject for a placeholder for any type, use Any instead. Reason: AnyObject only works with classes, Swift uses a lot of structs though (Array, Int, String, etc.). Your code actually uses NSStrings instead of Swifts native String type because AnyObject wants a class (NSString is a class).
In more general cases, collectionType.indexOf will work when the object inside an array conforms to Equatable protocol. Since Swift String has already conformed to Equatable, then casts AnyObject to String will remove the error.
How to use indexOf on collection type custom class ? Swift 2.3
class Student{
let studentId: Int
let name: String
init(studentId: Int, name: String){
self.studentId = studentId
self.name = name
}
}
//notice you should implement this on a global scope
extension Student: Equatable{
}
func ==(lhs: Student, rhs: Student) -> Bool {
return lhs.studentId == rhs.studentId //the indexOf will compare the elements based on this
}
func !=(lhs: Student, rhs: Student) -> Bool {
return !(lhs == rhs)
}
Now you can use it like this
let john = Student(1, "John")
let kate = Student(2, "Kate")
let students: [Student] = [john, kate]
print(students.indexOf(John)) //0
print(students.indexOf(Kate)) //1

conversion of protocol array to any object in swift

I have a function storeInCache that accepts an AnyObject?
When I try to give it an optional array of protocol objects it fails with compilation error "cannot invoke 'storeInCache' with an argument of list of type '([HasImage]?, String)'". Basically it cannot convert the
[HasImage]? to AnyObject? I believe.
I do not understand this, because the array holds objects (that comply with the HasImage protocol yes), its an array so its an AnyObject no?
I tried different ways of casting but none of them worked.
How can this be solved in swift?
Sample code:
protocol HasImage {
var imageUrl : String? {get}
}
class Product : NSObject, HasImage {
var imageUrl : String?
init(imageUrl : String?) {
self.imageUrl = imageUrl
}
}
func storeInCache(obj : AnyObject?, key : String)
{
//if no object supplied nothing to store in cache!
//...
}
func testCaching(addDummyData : Bool) {
var objectsWithImages : [HasImage]?
if addDummyData {
objectsWithImages = [Product(imageUrl: "http://wwww.someserver.com/p1/image.jpg"),Product(imageUrl: "http://wwww.someserver.com/p2/image.jpg")]
}
//fails compilation with => cannot invoke 'storeInCache' with an argument of list of type '([HasImage]?, String)'
storeInCache(objectsWithImages,"somekey")
}
Add #objc to your protocol declaration and it should be fine,
#objc
protocol HasImage {
var imageUrl : String? {get}
}

Resources