Decoding [[String]] using JSONDecoder? - ios

I have been away from Swift for the last 12 months but have just come back to an application that was previously working (Swift 4.0) but now seems to be failing and I am not sure why.
My problem relates to the JSONDecoder and trying to decode an array of arrays of strings.
var tryingToDecode: [[String]]
The JSON that I am using has the following format (See table below) for the actual data please use the following link.
JASON DATA
The code I am using is (See below) self.requestData is the JSON data I am using, which works when decoding all my other data, it just does not work with [[String]]
func TEST_decodeReceivedJSON() {
let decoder = JSONDecoder()
do {
let array = try decoder.decode(DataStruct.self, from: self.requestData)
print(array)
} catch {
print("Error")
}
}
The struct that I am using for the decode is
struct DataStruct: Codable {
var data: [[String]]
}
This is just test code, but when I compile it I always get thrown to the catch error. I have tried searching online but can't find any relevent examples. The strange thing is that prior to Xcode 10 this worked, it was even accepted in the App Store. I have now been informed by a number of users that something is not working and this is indeed the case, it seems to be related to this particular section where the [[String]] is decoded using DataStruct.
Any help or pointers would be very much appreciated.
[EDIT 001] Added a link to the JSON data, the code below shows a minimal example, I am specifically interested in how I should be accessing the [[String]] -- the array of arrays of strings. I am trying to assertain as this was working before is there something wrong with the way I am trying to decode the JSON (maybe a Swift update/change)or is there maybe a problem with JSONDecoder.
[EDIT 002] The solution was [[String?]] and the problem was indeed in the JSON, you just can't see it in the text blizzard of the raw data, if you look at the table view below you can clearly see that Item 10 is "null" and as a consequence the code required an optional String.

The issue is not with the JSONDecoder, the issue is with your JSON data.
When I checked your code I'm getting the following error:
valueNotFound(Swift.String, Swift.DecodingError.Context(codingPath:
[CodingKeys(stringValue: "data", intValue: nil), _JSONKey(stringValue:
"Index 11", intValue: 11), _JSONKey(stringValue: "Index 10", intValue:
10)], debugDescription: "Expected String but found null value
instead.", underlyingError: nil))
In your code you are expecting string, but when I checked your JSON data it contains null values and it will obviously break the written code. To fix this issue all you have to do is, change your model definition to accept null/nil values:
struct DataStruct: Codable {
var data: [[String?]]
}

Related

What's the difference between using JSONSerialization and JSONDecoder in swift?

What's the difference between using JSONSerialization and JSONDecoder in swift, while converting JSON to a swift model? It's seems like they are doing the same job. If they do, then when is which to use?
Thank you in advance
Apple has provided JSONDecoder which is a huge relief in swift4 and onwards. We can decode json in just one line. eg
{// sample from quicktype app online
"greeting": "Welcome to quicktype!",
"instructions": [
"Type or paste JSON here",
"Or choose a sample above",
"quicktype will generate code in your",
"chosen language to parse the sample data"
]
}
// MARK: - Welcome
struct Welcome: Codable {
let greeting: String
let instructions: [String]
}
// let welcome = try? newJSONDecoder().decode(Welcome.self, from: jsonData)
Here welcome is the struct which is conforming to codable protocol.
If you want to parse JSON by hand rather than using Codable, iOS has a built-in alternative called JSONSerialization.But i think everybody would like to use JSONDecoder. And also quicktype creates json model classes or struct for u for free.
Check yourself.
Use JSONDecoder !
If I remember well, JSONDecoder appeared in Xcode 9, with Swift4.
It's cleaner and much more efficient to encode and decode JSON.
To do so, your Swift Class has to conform to Decodable Protocol.
JSON Serialization is the Old School. (Like Objective-C)

Determining Swift Types That Can Be Stored in UserDefaults

I am in the beginning stages of developing an open-source utility for storing state in the Bundle UserDefaults.
I'm encountering an issue when I add non-Codable data types to my Dictionary of [String: Any].
I need to be able to vet the data before trying to submit it, because the UserDefaults.set(_:) method won't throw any errors. It just crashes.
So I want to make sure that the Dictionary that I'm submitting is kosher.
I can't just check if it's Codable, because it can sometimes say that it isn't, when the struct is actually good. (It's a Dictionary<String, Any>, and I can cram all kinds of things in there).
I need to validate that the Dictionary can produce a plist. If this were ObjC, I might use one of the NSPropertyListSerialization methods to test the Dictionary, but it appears as if this set of methods is not available to Swift.
According to the UserDefaults docs, there are a specific set of types and classes that are "plist-studly."
I think testing each type in the list is unacceptable. I need to see if I can find a way to test that won't be screwed the first time Apple updates an OS.
Is there a good way to test a Dictionary<String, Any> to see if it will make UserDefaults.set(_:) puke?
The Property List type set of UserDefaults is very limited. The supported types are
NSString → Swift String
NSNumber → Swift Int, Double or Bool
NSDate → Swift Date
NSData → Swift Data
Arrays and dictionaries containing the 4 value types.
Any is not supported unless it represents one of the 4 value or 2 collection types.
Property List compliant collection types can be written to UserDefaults with PropertyListSerialization (even in Swift).
There are two protocols to serialize custom types to Data
Codable can serialize structs and classes.
NSCoding can serialize subclasses of NSObject.
All types in the structs/classes must be encodable and decodable (means conform to the protocol themselves).
The APIs of PropertyListSerialization / PropertyListEncoder/-Decoder and NSKeyed(Un)Archiver provide robust error handling to avoid crashes.
UPDATE[1]: And, just because I like to share, here's the actual completed project (MIT License, as is most of my stuff)
UPDATE: This is the solution I came up with. Even though I greenchecked vadian's excellent answer, I decided to get a bit more picky.
Thanks to matt pointing out that I was looking under the wrong sofa cushions for the keys, I found the Swift variant of NSPropertyListSerialization, and I use that to vet the top level of the tree. I suspect that I'll need to refactor it into a recursive crawler before I'm done, but this works for now.
Here's the code for the _save() method at the time of this writing. It works:
/* ################################################################## */
/**
This is a private method that saves the current contents of the _values Dictionary to persistent storage, keyed by the value of the "key" property.
- throws: An error, if the values are not all codable.
*/
private func _save() throws {
#if DEBUG
print("Saving Prefs: \(String(describing: _values))")
#endif
// What we do here, is "scrub" the values of anything that was added against what is expected.
var temporaryDict: [String: Any] = [:]
keys.forEach {
temporaryDict[$0] = _values[$0]
}
_values = temporaryDict
if PropertyListSerialization.propertyList(_values, isValidFor: .xml) {
UserDefaults.standard.set(_values, forKey: key)
} else {
#if DEBUG
print("Attempt to set non-codable values!")
#endif
// What we do here, is look through our values list, and record the keys of the elements that are not considered Codable. We return those in the error that we throw.
var valueElementList: [String] = []
_values.forEach {
if PropertyListSerialization.propertyList($0.value, isValidFor: .xml) {
#if DEBUG
print("\($0.key) is OK")
#endif
} else {
#if DEBUG
print("\($0.key) is not Codable")
#endif
valueElementList.append($0.key)
}
}
throw PrefsError.valuesNotCodable(invalidElements: valueElementList)
}
}

A NSOperationQueue error I'm trying to figure out from Crashlytics

I got this error on Crashlytics this morning and I can't firgure out what the problem is. It would be awesome to get your opinions about it. I thInk it's most likely a multi threading issue. But I'm not able to pin point exactly what it is.
EDIT: I dug a little deeper and here's the code that's failing:
Also, I've figured out that the error is:
Could not cast value of type '__NSSingleObjectArrayI' (0x1aa60bca0) to 'NSMutableArray' (0x1aa60bd90).
2016-09-22 08:29:34.136764 GrabbnGo[4204:822290] Could not cast value of type '__NSSingleObjectArrayI' (0x1aa60bca0) to 'NSMutableArray' (0x1aa60bd90).
This was working perfectly all this while and it's suddenly causing problems and the app is already on the store :/
json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions()) as? [String: AnyObject]
let str = NSString(data: data!, encoding: NSUTF8StringEncoding)
print(str)
let OrderDictionary = json as NSDictionary
let result = OrderDictionary.objectForKey("result") as! NSMutableArray
OK, this sort of has a unique answer. Basically, you're misusing NSJSONSerialization, and it's a time-bomb bug that eventually bit you.
According to the documentation:
https://developer.apple.com/reference/foundation/jsonserialization
All objects are instances of NSString, NSNumber, NSArray, NSDictionary, or NSNull.
This is a very simple and clear sentence. You should respect it. It says nothing about NSMutableArray, only NSArray. The JSON parser is using whatever compatible (subclass) object for the NSArray that it so chooses. If there's only one item in the array, it appears that the internal type __NSSingleObjectArray is a lot more efficient, probably both in speed and memory.
There is almost certainly a change on the server (or customer behavior) such that result now (often? sometimes? always?) has only 1 item in it, so the JSON parser made a different choice that you're not supposed to care about.
All you need to do is change it to NSArray and construct an NSMutableArray from it if you really need:
let myJSONParsedArray: NSArray = ...
let myMutableArray = NSMutableArray(myJSONParsedArray)
But first learn about why force unwrapping is so dangerous, especially in a network code environment, when you can't trust any data at all, regarding type and value and maliciousness, ever!!

InvalidFirebaseData error when saving multiple values in Firebase

The following code once worked fine and I was able to save multiple values to a Firebase record. However this no longer works since upgrading to Swift 3 (Xcode 8). I now get the following error:
*** Terminating app due to uncaught exception 'InvalidFirebaseData', reason: '(setValue:) Cannot store object of type _SwiftValue at mood. Can only store objects of type NSNumber, NSString, NSDictionary, and NSArray.'
The above error always mentions the second value, regardless of what type it is (even if it is one of the supported types like NSString). Here's what I have:
postsRef.childByAutoId().setValue(["postedBy": self.currentUser?.uid, "mood": mood, "status": status, "date": convertedDate])
This still seems to comply with the docs on the Firebase website. What am I doing wrong?
Thanks!
Try :-
let toBePosted = ["postedBy": String(self.currentUser!.uid),
"mood": String(mood), "status": String(status),
"date": String(convertedDate)]
postsRef.childByAutoId().setValue(toBePosted){ (error, ref) -> Void in
}
As for converted date you will have to convert it to NSDate format when you retrieve it.
I just had this issue and after a few hours of toying around I think I've got a solution.
It looks like Firebase is wanting you to be very specific with your data types with Swift 3.
You need to straight up tell Swift what kind of dictionary you're going to have. In this case it's probably
let toBePosted : Dictionary<String, Any> = ["postedBy": String(self.currentUser!.uid),
"mood": String(mood), "status": String(status),
"date": String(convertedDate)]
"Any" is a new thing in Swift 3 I think. AnyObject doesn't work here because Swift redefined what AnyObjects are, and Strings/Ints dont seem to be them anymore.
Finally, it seems like you have to have absolutely no optionals in your dictionary. Maybe an optional is a _SwiftValue? Once I got rid of optionals and garunteed each value was going to be there it started working for me.
This worked for me, let me know if you're still having an issue.

parsing json in swift

I'm trying to read the Linkedin response in swift.
My object is something like this ["positions":["values":["data1","data2","data3"]]]
if let positions: NSDictionary = info["positions"] as NSDictionary!{
if let positionsInfo: [NSDictionary] = positions["values"] as? [NSDictionary]{
for position : NSDictionary! in positionsInfo {
dosomething(position, person:usr)
}
}
}
If I do a StepOver line by line it works correctly. But if I run it i'll get a EXC_BAD_ADDRESS(code=1,address=0x7966b04) I enabled Zombie objects and ran it on Instruments. I'm pretty sure this is the code which is causing the problem. But not sure what is wrong with it.
The moment you used ! you opened yourself up for crashes if there were any problem. You must use as? to make sure that the data is actually what you think it is.
There are many blog posts out there on how to safely parse JSON into Swift data structures. It's now almost a rite of passage for Swift bloggers.
http://robots.thoughtbot.com/efficient-json-in-swift-with-functional-concepts-and-generics
http://chris.eidhof.nl/posts/json-parsing-in-swift.html
https://github.com/owensd/json-swift
https://github.com/lingoer/SwiftyJSON
Of course the many packages: https://github.com/search?q=%5Bswift%5D+json
http://robnapier.net/functional-wish-fulfillment - My own version on top of all the others

Resources