神刀安全网

Everything You Need to Know About JSONJoy, SwiftyJSON & OCMapper

About JSON in general

In today’s world, most of the mobile and web applications we write include some background activity with an API, like handling user info, or fetching data. Over 90% of the time (I didn’t look up the exact number, I’m sure you will forgive me) the servers respond with a JSON object containing the information we desire. If you’re unfamiliar with the structure of a JSON object, it’s definitely worth looking into. There are quite a few greatly designed tutorials on the topic, there’s one on W3Schools , for example.

Once we receive the API response, we generally want to pass this information to our business logic, and eventually to our viewcontrollers.

. However, accessing these values from the root object is a pain in the rear. It’s time-consuming, produces messy code, and it’s unsafe. A typo in our code while accessing a key will not produce any errors, since the keys are strings. Without the keen eyes of an experienced developer, who can spot a typo from a mile off, debugging these coding errors takes a lot longer than it should.

So, what if the information received could be put into an object, storing the values with variables (or properties, in Objective C)? That way a misspelled key access will produce a compilation error, because there is no such variable. On top of it all, our code will become more maintainable, easier to write and read, and altogether cleaner.

Luckily, there are several libraries that can help do that. In this article, we’re going to take a look at 3 of these libraries, and discuss some of their pros and cons. These are JSONJoySwiftyJSON, and  OCMapper .

Everything You Need to Know About JSONJoy, SwiftyJSON & OCMapper

In the examples, the following JSON will be used as our API response, a daily menu of a restaurant.

 
{
  "id" : "m3" ,
  "title" : "Wednesday" ,
  "items" : [
    { "name" : "dish1" , "price" : 10 },
    { "name" : "dish2" , "price" : 15 },
    { "name" : "dish3" , "price" : 20 }
   ]

}

JSONJoy

This library is available as a framework too, and can be downloaded from this link , or you can add it using CocoaPods. If you are lazy, you can just add JSONJoy.swift from the sources to your project, it will work all the same.

First of all, we need our classes. If we wish to parse JSON into instances of our classes (struct is also supported ) using JSONJoy, they have to conform to the JSONJoy protocol. This means implementing the decoding initializer (declared in the protocol), that handles our parsing logic.

struct Menu : JSONJoy{
   var id: String
   var title: String
   var items: [MenuItem]   
   init(_ decoder: JSONDecoder) throws {
     id = try decoder[ "id" ].getString()
     title = try decoder[ "title" ].getString();    
     guard let decoderArray = decoder[ "items" ].array else { throw JSONError.WrongType}
     items = [MenuItem]()
     for decoderT in decoderArray{
       items.append( try MenuItem(decoderT))
     }
   }

}   struct MenuItem: JSONJoy{

   var name: String
   var price: Int   
   init(_ decoder: JSONDecoder) throws {
     name = try decoder[ "name" ].getString()
     price = try decoder[ "price" ].getInt()
   }

}

As you can see, our classes (in this case, structs) are fairly simple. MenuItems stores the dishes’ name and price, while the Menu itself has an identifier, a title, and an array, containing the dishes of the day. In this case, to avoid incomplete menus, all of our fields are required, hence the “throw.” Above is an example of how to write the parsing logic for nested classes in containers. Fortunately, JSONJoy supports arrays and dictionaries on multiple levels, so generating even more complex JSONs will be no match for you . If you wish to use optionals, values can be read even more easily from the decoder object , like this:

name = decoder[ "name" ].string

The required initializers take a JSONDecoder object, which takes an NSData. Our JSON is currently in the form of a String, so let’s transform it!

 
let jsonData: NSData = jsonString.dataUsingEncoding(NSUTF8StringEncoding)  
print( "******/n/nMapping JSONJoy style" );
if let data = jsonData{
       let decoder = JSONDecoder(data);
       do {
         let menu = try Menu(decoder);        
         print( "MENU" );
         print( "Identifier: /(menu.id), title: /(menu.title)" );
         for item in menu.items{
           print( "/t/(item.name) - /(item.price)" );
         }
       } catch {
         print( "Unable to parse JSON" );
       }
     }

Running this code should produce the following:

****** Mapping JSONJoy style MENU Identifier: m3, title: Wednesday
   dish1 - 10
   dish2 - 15
   dish3 - 20

Easy as pie. Our data are now in the form of proper objects instead of a dictionary, ready to be passed on. The parsing code is readable, comprehensible, and creating instances from now on is a walk in the park. However, larger classes with more complex structures take somewhat longer to code. Now that we’ve seen an example of a JSON library, let’s move on to the next one!

SwiftyJSON

SwiftyJson is really similar to JSONJoy, although its syntax might seem a little bit friendlier to some. The official documentation and download link is available on GitHub .

We’re not going to write parsing code here. After seeing a few examples of how JSON values can be accessed using SwiftyJSON, initializing our classes becomes trivial (and there is no need to conform to protocols, either).

First off, let’s create our object, using the jsonString we fromed earlier:

let jsonData: NSData = jsonString.dataUsingEncoding(NSUTF8StringEncoding)! let json = JSON(data: jsonData)

Simple values can be read as the following (Adding the word “Value” after any type will produce a non-optional value):

let idString = json[ "id" ].stringValue
let optionalIDString = json[ "id" ].string

So far it’s kind of similar to JSONJoy, but here comes the cherry on top. If there are sub-JSON objects (like arrays) inside our root object, we can access values using the path to the element.

let firstItemName = json[ "items" ][ 0 ][ "name" ].stringValue

Naturally, arrays and dictionaries are prone to exceptions (especially to IndexOutOfBounds), but worry not, SwiftyJSON takes care of it. Normally, we have to determine the cause of the problems ourselves, such as checking the array size, existence of a certain key, etc. The library has a built-in error catching mechanism that tells us if anything goes amiss.

if let name = json[ "restaurantName" ].string {
   print( "Name of restaurant: /(name)" )
} else {
   print(json[ "restaurantName" ].error) // "Dictionary["restaurantName"] does not exist"

}  

if let rating = json[ 0 ].string {
   print( "Rating: /(rating)" )
} else {
   print(json[ 0 ])       // "Array[0] failure, It is not an array"
   print(json[ 0 ].error) // "Array[0] failure, It is not an array"

}

Writing initializers for our classes is straight-forward from here.

Comparing the last 2 libraries, it’s pretty clear which one works best. SwiftyJSON is cleaner and safer, while taking roughly the same (actually, slightly less) amount of coding for larger classes. However, it’s strictly for Swift, and even though Swift and Objective-C code can be bridged, JSONJoy is probably a better idea for old projects.

OCMapper

So far we’ve accessed some JSON values on the fly or parsed them into instances of our classes, using some kind of assignment logic. If you have ever parsed multiple large class objects in a relatively complex application, one thing probably bothered you: It takes a hell of a lot of monotonous and redundant coding to write all parsing logic. Assigning each variable one by one is just a nightmare, and sooner or later we all descend into the pits of copy-paste coding, paving the way for developer errors. Assignments should be straight-forward, especially if all our variables have matching names with the JSON keys. But have no fear, your friendly neighbour, OCMapper is here! Get it from GitHub , add the sources to your project, and get ready to rock.

Let’s modify our classes:

class MenuMappable : NSObject{
   var id: String?
   var title: String?
   var items: [MenuItemMappable]?

}

class MenuItemMappable : NSObject{
   var name: String?
   var price: NSNumber?

}

Notice anything? That’s right, there are no init methods, they are inherited from NSObject, which is the required parent class. We don’t need to add any parsing logic inside our classes, OCMapper does everything for us. One small “obstacle”, however, is that this library works with Dictionaries instead of NSData objects. So, first we need to create a native JSON object from our data (which is usually received from the API).

let jsonData: NSData = jsonString.dataUsingEncoding(NSUTF8StringEncoding)!
do {
   let dictionary = try NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.AllowFragments)
   // <insert black magic here>
} catch {
   print( "Serialization error" );

}

Okay, we have our dictionary, this is the part where you’d expect some dark, demonic magic to happen. Guess what? It’s so much simpler than that.

if let menu = ObjectMapper.sharedInstance().objectFromSource(dictionary, toInstanceOfClass: MenuMappable.self) as? MenuMappable{
     print( "MENU" );
     print( "Identifier: /(menu.id!), title: /(menu.title!)" );
     for item in menu.items!{
       print( "/t/(item.name!) - /(item.price!)" );
     }

}

Yes, that’s really it. Almost. All parsing is done automatically by using ObjectMapper’s objectFromSource method while specifying your class name. The library recognizes almost all keys and properties in our class by itself. If we look at the output, we can see that the items are missing, which is troubling at first, but there’s a reason to it.

OCMapper (sadly) does not have the clairvoyance to recognize every intended class, so we need to tweak this process a little. This is where Mapping Providers come in. With a Mapping Provider, we can map (surprising twist) dictionary keys to our class properties, while also being able to declare which kind of object we want to see as our property.

Here’s an example: Change the array name inside MenuMappable from “items” to “dishes.” Next is to create our own Mapping Provider to do the mapping. Add the following code snippet right before creating the menu object from the dictionary:

let provider = InCodeMappingProvider();
provider.mapFromDictionaryKey( "items" , toPropertyKey: "dishes" , withObjectType: MenuItemMappable.self, forClass: MenuMappable.self);

ObjectMapper.sharedInstance().mappingProvider = provider;

What did we do exactly? We created a mapping configuration for OCMapper, letting it know that the key “items” corresponds with our “dishes” property, while also specifying that the array contains MenuItemMappable objects. The library automatically recognizes arrays and dictionaries. If the names were similar, even the mapping above wouldn’t be necessary, as it recognizes plurals as well. Running the code now will properly list all our dishes.

And that’s not all! Mapping Providers can map dates according to a given date format, transform our data while mapping, and support polymorphic mapping, on any level in the class hierarchy.

Let’s add some more extras to our provider:

let provider = InCodeMappingProvider()
provider.mapFromDictionaryKey( "items" , toPropertyKey: "dishes" , withObjectType: MenuItemMappable.self, forClass: MenuMappable.self)

  // #1

provider.mapFromDictionaryKey( "title" , toPropertyKey: "title" , forClass: MenuMappable.self, withTransformer: { (currentNode, parentNode) -> AnyObject! in
         return (currentNode as! String).uppercaseString // Transform the menu title to uppercase
       })

// #2

provider.mapFromDictionaryKey( "price" , toPropertyKey: "price" , forClass: MenuItemMappable.self, withTransformer: { (currentNode, parentNode) -> AnyObject! in
         let integer : Int = (currentNode as! NSNumber).integerValue
         return NSNumber(integer: integer * 1000 ) // Multiply the price by 1000
       })

ObjectMapper.sharedInstance().mappingProvider = provider

#1: A simple transformation of our data. This feature resembles functional programming a lot, as it allows us to manipulate the data on the fly without storing anything, leaving the data flow unhindered. CurrentNode is always a reference to the value being mapped for the given key, parentNode, one level above, is the object .

#2: Another transformation, but with a slight twist. As you can see, custom mapping is not only available for our main class, but any aggregate class as well. The unwrapping-rewrapping of the NSNumber is required, because OCMapper and NSJSONSerialization don’t support primitive types.

Run the code now to see the result! It should look something like this:

MENU Identifier: m3, title: WEDNESDAY
   dish1 - 10000
   dish2 - 15000
   dish3 - 20000

Unfortunately, this was an awful lot of coding for such small classes (with even smaller transformation needs),  but it pays off several times if you add a few more variables to the picture (larger applications can easily have above 10-15 properties per class). Nevertheless it’s clean and easy to understand, and provides immense opportunity to shape our data.

In conclusion

Just now we saw examples of 3 great JSON libraries. How can we decide which one to use? OCMapper seems to be incredibly fast and adaptive, but it also requires a lot of coding. SwiftyJSON is flexible with the mapping, but large classes are monotonous and redundant. The question can be answered by taking our intentions into account:

Do I want to process large amounts of data with low overhead compared the data received?

– OCMapper

Do I prefer quickly accessing a value of the API response, or don’t need mapping?

– SwiftyJSON

Do I have to update an older Objective-C application to use a better parsing library?

– JSONJoy

All the above libraries are well-built and generally stable. Deciding among them is dependant on our current needs.

Everything You Need to Know About JSONJoy, SwiftyJSON &amp; OCMapper

The icing on the cake

We have put together a neat online tool that generates a parseable class, based on a JSON that you provide. Just specify which library to use, and download the results! It’s not even that expensive (and by that, I mean it’s completely free). Make sure to check it out, it saves a lot of work!

Summary

Processing API responses is one of the fundamental aspects of any data-driven application. I hope this article was helpful in discovering new ways of JSON handling! Don’t be afraid to experiment a little, and you will surely find which one suits you best.

Make sure to visit the libraries’ official websites for detailed documentation and regular updates. Let us know what you think about our JSON tool! Meanwhile, have fun parsing!

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Everything You Need to Know About JSONJoy, SwiftyJSON & OCMapper

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址