8. 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
}
9. 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)
}
11. 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]
12. 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…
14. enum Value {
// enum cases
case StringType(String)
case NumberType(NSNumber)
case NullType(NSNull)
// collection types
case DictionaryType(Dictionary<String,Value>)
case ArrayType([Value])
}
15. 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
16. 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:
18. 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!
20. 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
22. enum Value {
// enum cases
case StringType(String)
case NumberType(NSNumber)
case NullType(NSNull)
// collection types
case DictionaryType(JSONDictionary)
case ArrayType(JSONArray)
}
23. 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
24. if let p = parsedJSON["results"]?.jsonArr,
d = p[0]?.jsonDict {
d["trackName"]?.str
}
parsedJSON["results"]?[0]?["trackName"] = "Something"
And setting:
Getting:
25. 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.
27. 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
28. 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? { ... }
}
29. 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
}
}
}
32. 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.
Hinweis der Redaktion
Slides to accompany Box demo
There is nothing to stop us ignoring type safety and working with the JSON objects without a care for type beyond an adherence to the AnyObject protocol.
before you can use a value in any meaningful way it must be cast to a type in Swift
We are flexible to change values paying no regard to type. Here the resultCount is changed from a number to a string.
JSON is characteristically mixed in types and it unless the data was preprepared with strong typing in mind it would be unusual for JSON to be treated in this way
Then you have a very simple situation
You can transform what you have into a Swift Dictionary or Array and be done with it.
JSON is characteristically mixed in types and it unless the data was preprepared with strong typing in mind it would be unusual for JSON to be treated in this way, so how do we co-exist with this while still respecting Swift’s strong typing. The answer is enums with associated values.
This is what the cases look like if we wrap all values and NullType probably doesn’t need a NSNull inside because NSNull is always the same. One their own they can wrap everything and provide us with a type safe way to access values.
enums allow us to escape the world of AnyObject and use a neater looking syntax when paired with computed variables.
enums allow us to escape the world of AnyObject and use a neater looking syntax when paired with computed variables.
Note the omission of SwiftyJSON, which uses enums (without associated values) in unison with a stored AnyObject type property
JSON is characteristically mixed in types and it unless the data was preprepared with strong typing in mind it would be unusual for JSON to be treated in this way
JSON is characteristically mixed in types and it unless the data was preprepared with strong typing in mind it would be unusual for JSON to be treated in this way
This might not be due to the wrapping but other elements of the approach.
JSON is characteristically mixed in types and it unless the data was preprepared with strong typing in mind it would be unusual for JSON to be treated in this way
What if instead of using Dictionary and Array we created unique JSONDictionary and JSONArray types? Would a struct that wrapped on demand, instead of storing wrapped values, save time wrapping and unwrapping? And how would it work? One idea that I’ve worked on is to internally store five dictionaries corresponding to the cases. In the dictionary these are of type [String: String], [String: NSNumber], [String: NSNull], [String: JSONDictionary], [String: JSONArray] and internally the JSONArray type has similar dictionaries but using Int as its key. This approach makes it very easy to add and change values, to return all strings or numbers, to see at a glance whether arrays contain only strings or only numbers, and so on. It is made possible by the ability of structs to store property values.
While we can access all values from the parsed JSON, we can also build bespoke ways of handling the parsed JSON making it easier to manipulate and we could drill down even further if we wished.
Remember to have MAMP on for demo, Dropbox/SwiftSummit/SwiftSummit_JSON_Presentation_Struct.Playground
Document/Developer/Swift/JSONParser