Slide 1

Slide 1 text

{ "JSON, Swift and Type Safety" : "It's a wrap" } @gylphi @sketchytech [Anthony Levings, @sketchyTech] Presented at SwiftSummit.com, 21 March 2015

Slide 2

Slide 2 text

JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to read and write. It is easy for machines to parse and generate. It is based on a subset of the JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999. JSON is a text format that is completely language independent but uses conventions that are familiar to programmers of the C-family of languages, including C, C++, C#, Java, JavaScript, Perl, Python, and many others. These properties make JSON an ideal data-interchange language.

Slide 3

Slide 3 text

NSData if let NSData?

Slide 4

Slide 4 text

AnyObject? NSJSONSerialization NSData

Slide 5

Slide 5 text

NSArray AnyObject? NSDictionary

Slide 6

Slide 6 text

AnyObject AnyObject AnyObject NSArray

Slide 7

Slide 7 text

NSArray NSDictionary NSString AnyObject NSNumber NSNull

Slide 8

Slide 8 text

Safety Last “Smash and Grab”

Slide 9

Slide 9 text

var error:NSError? if let jsonObject = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &error) as? [String: AnyObject] { let count = jsonObject["resultCount"] as? Int // casting value to Int }

Slide 10

Slide 10 text

var error:NSError? if let jsonObject = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &error) as? [String: AnyObject] { var jDict = jsonObject jDict["resultCount"] = "string" // change of type can happen easily let jsonData = NSJSONSerialization.dataWithJSONObject(jDict, options: nil, error: nil) }

Slide 11

Slide 11 text

Simple Safety “Dream Data”

Slide 12

Slide 12 text

if the JSON you are receiving looks like this {"key1":"value1","key2":"value2","key3":"value3"} or this [1,2,3,4,5,6,6,7,8,9,10]

Slide 13

Slide 13 text

then you can simply write if let dict = jsonObject as? Dictionary { } or this if let dict = jsonObject as? [Int] { } to achieve type safety, but this will rarely the case in the real world and so rather than keep dreaming, we have…

Slide 14

Slide 14 text

Safely Wrapped enum with associated values

Slide 15

Slide 15 text

enum Value { // enum cases case StringType(String) case NumberType(NSNumber) case NullType(NSNull) // collection types case DictionaryType(Dictionary) case ArrayType([Value]) }

Slide 16

Slide 16 text

And we then wrap each value of a received [AnyObject] or [String: AnyObject] as the initializer to our enum* * working code available, ask me after if you’re interested

Slide 17

Slide 17 text

if let num = dictionary["key"]?.number { } RATHER THAN THIS: if let dict = jsonObj as? [String: AnyObject], str = dict[“key”] as? NSNumber { } We can then combine associated values with computed variables to achieve this kind of syntax:

Slide 18

Slide 18 text

Argo  (thoughtbot)   Swiftz  (typelift) json-­‐swift  (David  Owens  II) Three GitHub Swift–JSON libraries that already use associated value enums in their code:

Slide 19

Slide 19 text

Type Safety = Empowerment • restrict  changes  of  type  (e.g.   through  subscripting)   • prevent the return of AnyObject • enable the compiler to better detect errors and assist the programmer • reduction in the amount of code to test types and return values • IT MAKES US THINK ABOUT TREATMENT OF JSON!

Slide 20

Slide 20 text

Potential Problems

Slide 21

Slide 21 text

The larger your model object, the longer the build takes [using Argo]. This is an issue with the Swift compiler having trouble working out all the nested type inference. While Argo works, it can be impracticle for large objects. There is work being done on a separate branch to reduce this time. (Tony DiPasquale, thoughtbot) https://robots.thoughtbot.com/parsing-embedded-json-and-arrays-in-swift Argo

Slide 22

Slide 22 text

Wrapped on Demand A possible solution

Slide 23

Slide 23 text

enum Value { // enum cases case StringType(String) case NumberType(NSNumber) case NullType(NSNull) // collection types case DictionaryType(JSONDictionary) case ArrayType(JSONArray) }

Slide 24

Slide 24 text

if let p = parsedJSON["results"]?.jsonArr, d = p[0]?.jsonDict { d["trackName"]?.str } If we use a struct and an enum together we can use leverage stored values: (1) the getter can wrap individual values on demand (not in advance). (2) changes and additions to stored values become simplified parsedJSON["results"]?[0]?["trackName"] = "Something" And setting: Getting:

Slide 25

Slide 25 text

Using the struct approach we also have easier access to information like which keys have String values, which have Number values, etc. —- Dictionary —- json.keysWithNumberValues json.keysWithStringValues —- Array ——- json.isNumberArray json.isStringArray json.isMixedArray json.removeAllNumbers() json.removeAllStrings() and other benefits of stored properties, which enums don’t enjoy.

Slide 26

Slide 26 text

Bespoke Handling of Data

Slide 27

Slide 27 text

if let url = NSURL(string:"http://itunes.apple.com/search? term=b12&limit=40"), data = NSData(contentsOfURL: url), parsedJSON = JSONParser.parseDictionary(data), iTD = iTunesData(dict: parsedJSON) { let tracks = map(iTD.results, {x in Track(dict:x.jsonDict)}) } Bespoke Handling of Data

Slide 28

Slide 28 text

Round-tripping bespoke data

Slide 29

Slide 29 text

No content