This article is translated from this article and all the code in this article is on GitHub.
In this article, I will outline a parsing library that uses Swift to process JSON. A JSON example is as follows:
- var json: [String:anyobject] = [
- " stat": "OK",
- "Blogs": [
- "blog": [
- [
- "id":
- " name": "Bloxus test",
- "Needspassword": true,
- "url": "http://remote.bloxus.com/"
- ],
- [
- "id":
- " name": "Manila Test",
- "Needspassword": false,
- "url": "http://flickrtest1.userland.com/"
- ]
- ]
- ]
- ]
The most challenging part is how to convert this data into an array of Swift structures as follows:
- struct Blog {
- Let Id:int
- Let name:string
- Let Needspassword:bool
- Let Url:nsurl
- }
We first look at the final analytic function, which contains two algorithms: >>= and <*>. These two operators may look strange, but parsing the entire JSON structure is as simple as that. The other sections of this article will explain these library codes. The following parsing code works: If the JSON is illegal (for example, name does not exist or ID is not integer) The final result will be nil. We don't need reflection (reflection) and KVO, just a few functions and some clever combinations:
- Func Parseblog (blog:anyobject)-blog? {
- return asdict (blog) >>= {
- Mkblog <*> Int ($,"id")
- <*> string ($,"name")
- <*> bool ($,"Needspassword")
- <*> (String ($, "url") >>= tourl)
- }
- }
- Let parsed: [Blog]? = Dictionary (JSON, "blogs") >>= {
- Array ($, "blog") >>= {
- Join ($0.map (Parseblog))
- }
- }
What exactly did the above code do? Let's take a closer look at these most important functions. First look at the dictionary function, which takes a String-to-Anyobject dictionary and returns another dictionary with the specified key:
- Dictionary (input: [String:anyobject], key:string), [String:anyobject]? {
- return Input[key] >>= {$ as? [String:anyobject]}
- }
For example, in the previous JSON example, we expected key = "blogs" to contain a dictionary. If the dictionary exists, the above function returns the dictionary, otherwise nil is returned. We can write the same method for Array, String, and Integer (just life, the full code, see Github):
- Func Array (input: [String:anyobject], key:string), [Anyobject]?
- Func string (input: [String:anyobject], key:string), string?
- Func Int (input: [Nsobject:anyobject], key:string), int?
Now, let's look at the full structure of the JSON example. It is a dictionary in itself and contains a second dictionary with key "blogs". The dictionary contains an Array with a key of "blog". We can use the following code to express the above structure:
- If Let blogsdict = Dictionary (parsedjson, "blogs") {
- if let Blogsarray = Array (blogsdict, "blog") {
- //Do something with the blogs array
- }
- }
I can implement a >>= operation instead, accept a optional parameter, and use a function on it when the parameter is not nil. The operator uses the flatten function, and the flatten function expands the nested optional:
- Operator infix >>= {}
- @infix func >>= <u,t> (optional:t, F:t-u?) u? {
- return Flatten (Optional.map (f))
- }
- Func flatten (x:a??), A? {
- if let y = x { return y}
- return Nil
- }
Another frequent use is the <*> operator. For example, the following code is used to parse a single blog:
- Mkblog <*> Int (dict,"id")
- <*> string (dict,"name")
- <*> bool (dict,"Needspassword")
- <*> (String (dict, "url") >>= tourl)
This function works when all optional parameters are Non-nil, and the code above translates to:
- Mkblog (int (dict,"id"), String (Dict,"name"), BOOL (dict,"Needspassword"), (String (dict, "url") >>= tourl))
So let's take a look at the definition of operator <*>. It takes two optional parameters, and the left argument is a function. If none of the two parameters is nil, the Left function argument is used for the right argument:
- operator infix <*> { associativity left precedence 150 }
- func <*><a, b> (f: (A -> B)?, x: a?) -> b? {
- if let f1 = f {
- if let x1 = x {
- return f1 (x1)
- }
- }
- return nil
- }
Now you might want to know what Mkblog is doing. It is a curried function used to wrap our initialization function. First, we have a function (Int,string,bool,nsurl) –> the type of the Blog. The curry function then converts its type to a Blog, Nsurl, a String, such as Int:
- Let Mkblog = Curry {ID, name, needspassword, url in
- Blog (Id:id, Name:name, Needspassword:needspassword, Url:url)
- }
We will use Mkblog and <*>, let's take a look at the first line:
- Mkblog:int, Nsurl, String, Bool,
- Int (dict, "id"): int?
- Let Step1 = mkblog <*> int (dict,"id")
As you can see, using <*> to connect them together, a new type will be returned: (Blog, Nsurl, String---)? , and then combine with the string function:
- Let Step2 = Step1 <*> string (dict,"name")
We get: (Blog, Nsurl, Bool)? , has been such a combination, and finally will be the type of Blog? The value.
Hopefully you can now see how the entire code works together. By creating some auxiliary functions and operators, we can make it very easy to parse the strongly typed JSON data. If you don't use the optional type, then we will be using a completely different type and contain some error messages, but this will be the topic of another blog.
Parsing JSON with Swift