HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Turning JSON objects into custom NSObjects

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
nsobjectsobjectsintocustomturningjson

Problem

Was advised to post this question here after posting to Stack Overflow. I'm pre-populating a UITableView app with data using a local JSON file. The JSON itself is very simple:

[
    {"Name":"Tennis Ball Launchers",
    "Type":"Forces"},

    {"Name":"Magnetic Hot Plates",
    "Type":"Magnetism"},

    {"Name":"Giant Lever",
    "Type":"Forces"},

    {"Name":"Music Wall",
    "Type":"Sound"},

    {"Name":"Water Bearing",
    "Type":"Forces"},

    {"Name":"Chaos Pendulums",
    "Type":"Chaos"}
]


The JSON objects will have more properties further down the line, this is just for starters (there will be around 200 entries by the end, with more properties). I don't imagine the structure getting any more complex, however. The user will not be adding or deleting entries in the app - the JSON is read only. I want to map these JSON objects into an array of custom NSObjects. Here is the code I am currently using in the initialisation of my data model:

```
// JSON reading into array
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"json"];

NSError *error;

NSString *fileContents = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error];

if(error) {
NSLog(@"Error reading file: %@", error.localizedDescription);
}

// Get JSON objects into initial array
NSArray rawExhibits = (NSArray )[NSJSONSerialization JSONObjectWithData:[fileContents dataUsingEncoding:NSUTF8StringEncoding] options:0 error:NULL];

NSMutableArray *myItems = [[NSMutableArray alloc] init];

for (NSDictionary *object in rawExhibits) {
NSString *objectName = object[@"Name"];
NSString *objectType = object[@"Type"];

// Create new exhibit object with data procured from JSON object
W5BExhibit *exhibit = [[W5BExhibit alloc] initWithExhibitName:objectName exhibitType:objectType];

[myItems addObject:exhibit];
}

self.allExhibits = [[NSArray alloc] initWit

Solution

The correct way to check for success or failure of Cocoa (Touch) methods is
documented in
"Handling Error Objects Returned From Methods" in the "Error Handling Programming Guide":


Important: Success or failure is indicated by the return value of the
method. Although Cocoa methods that indirectly return error objects in
the Cocoa error domain are guaranteed to return such objects if the
method indicates failure by directly returning nil or NO, you should
always check that the return value is nil or NO before attempting to
do anything with the NSError object.

Which means that the result of

NSString *fileContents = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error];


should be checked with

if (fileContents == nil) { // Not: if (error) { ...
    NSLog(@"Error reading file: %@", error.localizedDescription);
}


And you should also use the error parameter and check the result of

NSArray *rawExhibits = (NSArray *)[NSJSONSerialization JSONObjectWithData:[fileContents dataUsingEncoding:NSUTF8StringEncoding] options:0 error:NULL];


The explicit cast to (NSArray *) is not necessary because the method
returns id and that can be assigned to any Objective-C object pointer.

Actually your method to read the JSON data is too complicated: The file
contents is first read into a string (using some encoding) and then converted to back to data (using the same encoding).
This can be simplified by reading the file into data in the first step:

NSData *jsonData = [NSData dataWithContentsOfFile:filePath];


You also have to decide what to do if reading the JSON data fails.
Just logging the error is not sufficient because then the program will
crash later.

The parameter naming in your init method

-initWithExhibitName:exhibitType:


seems a bit too verbose to me. It is clear from the class name W5BExhibit
that the object is some "exhibit", so I would shorten that too

-initWithName:type:


Adding new properties to your object class and the JSON file currently
requires a new init method with more arguments.
This can be greatly simplified using "Key-Value Coding" if you use the exact property names of your custom class as keys in the dictionary.

So assuming that W5BExhibit is defined as

@interface W5BExhibit : NSObject
@property(copy, nonatomic) NSString *name;
@property(copy, nonatomic) NSString *type;
@end


the JSON data should look like

[
  {"name":"Tennis Ball Launchers",
  "type":"Forces"},

  {"name":"Magnetic Hot Plates",
  "type":"Magnetism"},

  ...
]


Then you can create an object from a dictionary with

-(instancetype)initWithDictionary:(NSDictionary *)dict
{
    self = [super init];
    if (self) {
        [self setValuesForKeysWithDictionary:dict];
    }
    return self;
}


self.allExhibits = [[NSArray alloc] initWithArray:myItems];


can be simplified to

self.allExhibits = [myItems copy];


The complete code then looks like this (where I have renamed some variables,
this might be a matter of personal taste):

// Read JSON data into array
NSError *error;
NSString *jsonPath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"json"];
NSData *jsonData = [NSData dataWithContentsOfFile:jsonPath];
if (jsonData == nil) {
    // handle error ...
}
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
if (jsonArray == nil) {
    // handle error ...
}

// Create custom objects from JSON array
NSMutableArray *items = [[NSMutableArray alloc] init];
for (NSDictionary *dict in jsonArray) {
    W5BExhibit *exhibit = [[W5BExhibit alloc] initWithDictionary:dict];
    [items addObject:exhibit];
}
self.exhibits = [items copy];

Code Snippets

NSString *fileContents = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error];
if (fileContents == nil) { // Not: if (error) { ...
    NSLog(@"Error reading file: %@", error.localizedDescription);
}
NSArray *rawExhibits = (NSArray *)[NSJSONSerialization JSONObjectWithData:[fileContents dataUsingEncoding:NSUTF8StringEncoding] options:0 error:NULL];
NSData *jsonData = [NSData dataWithContentsOfFile:filePath];
-initWithExhibitName:exhibitType:

Context

StackExchange Code Review Q#66366, answer score: 9

Revisions (0)

No revisions yet.