I’m trying to parse JSON from a web service to Core Data. I’ve looked at the RSAPI library but I can’t really understand how to use it in my case. What I used until now to get my JSON data was: json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error]; NSData *data = [NSData dataWithContentsOfURL:url]; and then display it as an array in my tableview. Any suggestions on how to save the JSON data into Core Data for my custom entity?
I’m trying to parse JSON from a web service to Core Data. I’ve looked
Share
The Easy/Dangerous Way
Cocoa provides a method that makes this much, much simpler. Thanks to key-value coding there’s a method in NSObject that reduces the above to a single line:
[myObj setValuesForKeysWithDictionary:jsonDict];
This will run through key/value pairs in jsonDict and set the same key/value pairs on myObj. The only caveat is that the key names must match, but that shouldn’t be a problem assuming you’ve named them the same in your data model.
Actually there’s one more caveat, and it’s a biggie. In this method, the keys contained in the dictionary determine what keys are used on the managed object. But what if the dictionary contains a key that’s not part of the entity description? Then your app crashes with an error about how the object is not key-value coding compliant for the key. Using this method here puts your app’s stability at the mercy of the web service you’re using. If you don’t have absolute control over the web service, you’re running serious risks using this method here. We can do better.
Inspecting Managed Objects
Since we’re using Core Data, we can use NSEntityDescription to inspect a managed object’s attributes and turn the logic above around. Instead of using the incoming dictionary to determine what key/value pairs to use, we can use the managed object.
You can look up the entity description for a managed object by asking for its entity. That gives you an NSEntityDescription which you can then ask for all kinds of useful information, like what attributes exist. That leads to a safer approach when converting from JSON:
This code iterates over the entity’s attributes, looks up a key in the dictionary for each of them, and applies that key/value pair to the managed object. That’s still nice and generic, and has the advantage that changes to incoming data won’t crash the app any more. It looks up values in the dictionary only for attributes that actually exist in the entity description. Extra dictionary keys are simply ignored.
The check for nil is there because the code does hit all of the entity’s attributes. If the entity has any attributes that aren’t present in the incoming data (a “favorites” flag, for example) the code would end up setting a nil value for the attribute. That could lead to accidentally wiping out data that you really want to keep.
Handling Broken and Inconsistent JSON
The JSON standard is quite clear about how to distinguish strings from numbers– basically, strings are surrounded by quotes and numbers are not. JSON web services however, are not always good about following this requirement. And even when they are, they are not always consistent from one record to another.
An example, similar to one I encountered recently: Size information for clothing. Sizes are provided by the web service and are typically something like “30-32″, “34-36″, etc. These are correctly quoted as strings in the JSON, and the app saves them as string attributes and displays them to the user as is.
But sometimes the size just has one number, e.g. “8″, “10″, etc. In this case the server drops the quotes, making them numbers. My JSON parser correctly produces an NSNumber. Only I want to save this in my entity’s string attribute! I can use Objective-C introspection to see if I received an NSString or an NSNumber from my JSON parser, but I also need to know what type the managed object expects for the property. I briefly considered breaking my JSON parser so that it would always return NSString, so at least I would know what to expect from it. Fortunately NSEntityDescription came to the rescue again.
Besides asking the entity description what its attribute names are, you can also inquire about the attribute types configured in the Core Data model. This is returned as an NSAttributeType. Using this, we can expand the code above to handle mismatched data types. It’s probably a good idea to abstract the code for easy reuse, too, so I’ll put it in a category on NSManagedObject:
This code inspects the types of both the value found in the dictionary we created from the incoming JSON and the attribute on the managed object, and if there’s a string/number mismatch it modifies the value to make sure it matches what’s expected.
This is getting pretty useful. Not only is it a generic JSON-to-NSManagedObject conversion, it also handles mismatches between numeric and string types without needing any entity-specific information. It could be even better, though.
Handling Dates
JSON does not have a date type, but dates are nevertheless common in JSON. They’re just represented as strings using one date format or another. Only you probably want an NSDate, not a string. NSDateFormatter is really useful here, but wouldn’t it be nice to make the date conversion generic as well, so you don’t need to special-case your date attribute? Can you see where I’m going with this?
Having written the category method above, it’s not much more work to add an optional NSDateFormatter argument, and then to use it whenever an entity’s attribute expects a date. This modified version adds that argument, and an extra case to the “else … if” chain: