{ "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.
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