{"json, swift and type safety" : "it's a wrap"}

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

Upload: anthony-levings

Post on 16-Jul-2015

597 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: {"JSON, Swift and Type Safety" : "It's a wrap"}

{"JSON, Swift and Type Safety" : "It's a wrap"}

@gylphi @sketchytech

[Anthony Levings,@sketchyTech]

Presented at SwiftSummit.com, 21 March 2015

Page 2: {"JSON, Swift and Type Safety" : "It's a wrap"}

NSDataNSDataif letNSData?NSData?

Page 3: {"JSON, Swift and Type Safety" : "It's a wrap"}

AnyObject?AnyObject?NSJSONSerializationNSDataNSData

Page 4: {"JSON, Swift and Type Safety" : "It's a wrap"}

NSArrayNSArray

AnyObject?AnyObject?

NSDictionaryNSDictionary

Page 5: {"JSON, Swift and Type Safety" : "It's a wrap"}

AnyObjectAnyObject AnyObjectAnyObject AnyObjectAnyObject

NSArrayNSArray

Page 6: {"JSON, Swift and Type Safety" : "It's a wrap"}

NSArrayNSArrayNSDictionaryNSDictionary

NSStringNSString

AnyObjectAnyObjectNSNumberNSNumber NSNullNSNull

Page 7: {"JSON, Swift and Type Safety" : "It's a wrap"}

Safety Last“Smash and Grab”

Page 8: {"JSON, Swift and Type Safety" : "It's a wrap"}

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

}

Page 9: {"JSON, Swift and Type Safety" : "It's a wrap"}

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)

}

Page 10: {"JSON, Swift and Type Safety" : "It's a wrap"}

Simple Safety“Dream Data”

Page 11: {"JSON, Swift and Type Safety" : "It's a wrap"}

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]

Page 12: {"JSON, Swift and Type Safety" : "It's a wrap"}

then you can simply write

if let dict = jsonObject as? Dictionary<String,String> { }

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…

Page 13: {"JSON, Swift and Type Safety" : "It's a wrap"}

Safely Wrappedenum with associated values

Page 14: {"JSON, Swift and Type Safety" : "It's a wrap"}

enum Value {

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

Page 15: {"JSON, Swift and Type Safety" : "It's a wrap"}

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

Page 16: {"JSON, Swift and Type Safety" : "It's a wrap"}

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:

Page 17: {"JSON, Swift and Type Safety" : "It's a wrap"}

Argo (thoughtbot)

Swiftz (typelift)

json-swift (David Owens II)

Three GitHub Swift–JSON libraries that already use associated value enums in their code:

Page 18: {"JSON, Swift and Type Safety" : "It's a wrap"}

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!

Page 19: {"JSON, Swift and Type Safety" : "It's a wrap"}

Potential Problems

Page 20: {"JSON, Swift and Type Safety" : "It's a wrap"}

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

Page 21: {"JSON, Swift and Type Safety" : "It's a wrap"}

Wrapped on DemandA possible solution

Page 22: {"JSON, Swift and Type Safety" : "It's a wrap"}

enum Value {

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

Page 23: {"JSON, Swift and Type Safety" : "It's a wrap"}

If we use a struct and an enum together we can leverage stored values:

(1) the getter can wrap individual values on demand (not in advance).

(2) changes and additions to stored values become simplified

Page 24: {"JSON, Swift and Type Safety" : "It's a wrap"}

if let p = parsedJSON["results"]?.jsonArr, d = p[0]?.jsonDict { d["trackName"]?.str }

parsedJSON["results"]?[0]?["trackName"] = "Something"

And setting:

Getting:

Page 25: {"JSON, Swift and Type Safety" : "It's a wrap"}

Using the struct approach we also have easier access to information like which keys have String values, which have Number values, etc.

—- Dictionary —-json.keysWithNumberValuesjson.keysWithStringValues

—- Array ——-json.isNumberArrayjson.isStringArrayjson.isMixedArray

json.removeAllNumbers()json.removeAllStrings()

and other benefits of stored properties, which enums don’t enjoy.

Page 26: {"JSON, Swift and Type Safety" : "It's a wrap"}

Bespoke Handling of Data

Page 27: {"JSON, Swift and Type Safety" : "It's a wrap"}

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

Page 28: {"JSON, Swift and Type Safety" : "It's a wrap"}

public struct iTunesData { public var resultCount:Int { return results.count } public var results:JSONArray public init?(dict:JSONDictionary) { ... } public subscript (index:Int) -> JSONDictionary? { ... } public mutating func updateTrackDetails(track:Track) { ... } public func outputJSON() -> NSData? { ... }}

Page 29: {"JSON, Swift and Type Safety" : "It's a wrap"}

public struct Track { public var trackName:String, collectionName:String, trackId:Int public init?(dict:JSONDictionary?) { if let tN = dict?["trackName"]?.str, cN = dict?["collectionName"]?.str, tI = dict?["trackId"]?.num { trackName = tN collectionName = cN trackId = tI.integerValue } else { return nil } }}

Page 30: {"JSON, Swift and Type Safety" : "It's a wrap"}

Round-tripping bespoke data

Page 31: {"JSON, Swift and Type Safety" : "It's a wrap"}

Track info

iTunesData

JSONParser

JSON in

JSON out

Page 32: {"JSON, Swift and Type Safety" : "It's a wrap"}

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.