JSON marshalling and unmarshalling with SBJSON - objective-c

I am developing an application for both Android and iPhone and I am having a problem marshalling and unmarshalling on iPhone. With Android it is easy enough, I am using Jackson JSON parser and there are plenty of tutorials online which made it easy to use.
On the iPhone I am using SBJSON parser, however there seems to be a real lack of information online about how to use it effectively.
Take the following piece of JSON
{
"data":{
"name":[
{
"fName":"John",
"lName":"Doe"
},
{
"fName":"Jane",
"lName":"Doe"
}
]
}
}
If I were using Java and using Jackson JSON parser, this would be easy. I would set up a Class like
public class Parse {
private Data data;
//get set data class
Then in the data class
public class Data {
private List<Name> name;
//get set name list
then in the name class
public class Name {
private String fName;
private String lName;
//get setters here
That way I have the data split up into a set of objects so I can retrieve the data I need, I can update the JSON if required from Java, then write it out again into a new JSON file, nice and simple, i.e
ObjectMapper mapper = new ObjectMapper();
Parse test = mapper.readValue(new File("/Users/adam/Documents/JSON/list.json"),
Parse.class);
System.out.println(test.getData().getName().get(0).getfName());
or I could set it doing
test.getData().getName().get(0).setfName("test");
What I want to know, is how do I do this with Xcode using SBJSON. I know how to do parse the data, and print it out, but I want to be able to print it out into a set of objects, make a change, then write it out again as I can with Jackson JSON parser. What I have done is
NSString *file = [[NSBundle mainBundle] pathForResource:#"data" ofType:#"json"];
NSData *Data = [NSData dataWithContentsOfFile:file];
NSString *jsonString = [[NSString alloc] initWithData:Data encoding:NSUTF8StringEncoding];
NSDictionary *dictionary = [jsonString JSONValue];
NSArray *name = [dictionary valueForKeyPath:#"data.name"];
NSLog(#"%#", name);
This will get the array of names, but I want to be able to access the first name and last name of each objects, and then update it if I require. Then Write it out again.
Would anyone be able to point me in the right direction. Is it possible to do the same sort of thing I did with Jackson JSON with SBJSON?

Found out how to do it.
Make a class Called List
#interface List : NSObject{
NSString *fName;
NSString *lName;
}
#property (nonatomic, retain) NSString *fName;
#property (nonatomic, retain) NSString *lName;
#end
Then implementation synthesise
#synthesize fName, lName;
Then code to create object of List and loop through it
NSMutableArray *array = [[NSMutableArray alloc] init];
List * list;
NSArray * listArray = [MainJSON valueForKeyPath:#"data.name"];
for(NSDictionary * listInfo in listArray) {
list = [[List alloc] init];
[list setFName:[listInfo objectForKey:#"fName"]];
[list setLName:[listInfo objectForKey:#"lName"]];
[array addObject:list];
}
for (int i = 0; i < [array count]; i++) {
List* l = [array objectAtIndex: i];
NSLog(#"%#", [l fName]);
NSLog(#"%#", [l lName]);
}
Hope this helps anyone else who was trying to do the same thing I was.

Have you considered using the NSKeyValueCoding protocol? A lot of the hard work can be done for you by setValuesForKeysWithDictionary.

Related

Model structure for different categories

I'm creating an app where I have different categories, and these categories has got different items that I want to display in a table view. Each item has got a title, description, url, and a image. The category has got a title and all the items that belongs to that category.
Which is the best way to create models for this structure?
I was thinking about a NSObject called Category, with a NSString for the title and a NSMutableArray for the items. And then another NSObject called Item with NSStrings.
I'm going to parse a JSON with all the data. But how can I parse the JSON objects into the right category model array?
Your model looks good enough. But why only NSStrings for Item. According to your description item model class should look like below:
#interface Item: NSObject{
NSString *title;
NSString *description;
NSURL *itemURL;
UIImage *image;
}
You can use NSJSONSerialization for parsing the json. For eg:
If you have a json string like following you can parse it like below:
NSString *jsonString= #"{ \"category1\": [ { \"iTitle\" : \"item1\", \"iDescription\":\"desc1\"},{ \"iTitle\" : \"item2\", \"iDescription\":\"desc2\"}]}";
NSData *data = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *jsonObj = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
If you want to parse and put it in classes you can do the following :
for (NSString *category in jsonObj) {
Category *categoryObj = [[Category alloc] init];
categoryObj.title = category;
NSArray *itemArray = [jsonObj valueForKey:category];
for (NSDictionary *item in itemArray) {
Item *itemObj = [[Item alloc] init];
itemObj.title = [item valueForKey:#"title"];
itemObj.description= [item valueForKey:#"description"];
[categoryObj.items addObject:itemObj];
}
}
To loop through a dictionary (or to access 'category1' in the string
#"{ \"tab1\": [{ \"category1\": [ { \"iTitle\" : \"item1\", \"iDescription\":\"desc1\"},{ \"iTitle\" : \"item2\", \"iDescription\":\"desc2\"}]}}"
you can use a for in loop.
NSDictionary *categoryDict = [[jsonObj valueForKey:"tab1"] objectAtIndex:0];
for (id key in categoryDict){
NSLog(#"Key : %#",key);
NSLog(#"Value: %#",[categorDict valueForKey:key];
}

ObjC: constructing a mutable dictionary in a loop

I feel like I have read many (simple) examples that do exactly what I am trying to do. I just cannot seem to get this to work. I need a second eye on my code, and I don't have anyone around, so pardon me if this seems very simple... The code compiles without a problem. Thank you!
#implementation Engine
- (id) initWithInventory: (NSString *) path {
if (self = [super init]) {
NSString *contents = [NSString stringWithContentsOfFile:#"ingredientList.csv" encoding:NSASCIIStringEncoding error:nil];
NSLog(#"%#",contents); // This yields the contents of the file appropriately
NSArray *lines = [contents componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSRange ingredientRange = {0,96}; // This is done because I want to omit the last element of the array... the 97th is an empty string caused by the end of file newline character. I know it's bad coding...
NSEnumerator *enumerator = [[lines subarrayWithRange:ingredientRange] objectEnumerator];
NSString *curString;
NSArray *ingredientElements;
NSRange activeEffectRange = {1,4}; // Element 0 will be the key, elements 1-4 are the array to be stored.
while (curString = [enumerator nextObject]) {
ingredientElements = [curString componentsSeparatedByString:#","];
Ingredient *theIngredient = [[Ingredient alloc] initWithName:[ingredientElements objectAtIndex:0] andActiveEffects:[ingredientElements subarrayWithRange:activeEffectRange]];
NSLog(#"%#",[theIngredient ingredientName]);
NSLog(#"%#",[theIngredient activeEffects]); //These both print out correctly.
NSString *theName = [theIngredient ingredientName];
[allIngredients setObject:theIngredient forKey:theName];
NSLog(#"%#",[allIngredients objectForKey:[theIngredient ingredientName]]); // ***This yields (null)***
}
}
return self;
}
EDIT: I should add, that allIngredients is an instance variable of the class being initiated, so it is defined properly as an NSMutableDictionary:
#interface Engine : NSObject {
NSMutableDictionary *allIngredients;
}
- (id) initWithInventory: (NSString *) path;
#end
Where are you creating allIngredients? You've declared it, but you haven't allocated it before you use it.
allIngredients = [[NSMutableDictionary alloc] init]

Write a complex array of custom structs to file Objective C

I need to save and load the contents of an array of structs, but I know that Objective C is very particular about which data types you can read/write with.
Here is my struct:
struct SCourse
{
NSMutableArray* holes; // holds integers (pars)
NSString* name;
int size;
BOOL inUse;
};
#interface CoursesManager : NSObject
{
struct SCourse courses[5];
}
What are the data types I'll need to use? Do they each have different methods needed in order to read/write? I'm just looking for a non-complex way to get all the data I need to and from a file. I could do this quite easily in a language I'm more familiar with (C++), but some of the particulars of Objective-c are still lost on me.
EDIT: Solution (thanks for the help, everyone)
-(void)applicationWillResignActive:(UIApplication *)application {
// save the courses
NSMutableArray* totalWriteArray = [[NSMutableArray alloc] initWithCapacity:MAX_COURSES];
for (int i = 0; i < MAX_COURSES; ++i)
{
struct SCourse saveCourse = [coursesManager GetCourseAtIndex:i];
NSNumber* nInUse = [NSNumber numberWithBool:saveCourse.inUse];
NSNumber* nSize = [NSNumber numberWithInt:saveCourse.size];
NSMutableArray* writeArray = [[NSMutableArray alloc] initWithCapacity:4];
[writeArray addObject:nInUse];
[writeArray addObject:nSize];
[writeArray addObject:saveCourse.name];
[writeArray addObject:saveCourse.holes];
[totalWriteArray addObject:writeArray];
}
[totalWriteArray writeToFile:[self saveFilePath] atomically:YES];
}
And for the loading back in...
-(void)loadFile {
NSString *myPath = [self saveFilePath];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:myPath];
if (fileExists) {
NSMutableArray* totalReadArray = [[NSMutableArray alloc] initWithContentsOfFile:[self saveFilePath]];
for (int i = 0; i < MAX_COURSES; ++i)
{
struct SCourse loadCourse = [coursesManager GetCourseAtIndex:i];
NSMutableArray* loadArray = [totalReadArray objectAtIndex:i];
NSNumber* nInUse = [loadArray objectAtIndex:0];
loadCourse.inUse = [nInUse boolValue];
NSNumber* nSize = [loadArray objectAtIndex:1];
loadCourse.size = [nSize integerValue];
NSString* inName = [loadArray objectAtIndex:2];
loadCourse.name = inName;
NSMutableArray* inHoles = [loadArray objectAtIndex:3];
loadCourse.holes = inHoles;
[coursesManager ReplaceCourseAtIndex:i With:loadCourse];
}
}
}
First thing first. You shouldn't use plain old C structures. The ARC memory management will not appreciate.
If you are familiar with C++, you should maybe use a C++ class instead, which will please the compiler and runtime. Depends on what you want to do.
Array. Use either NSArray or std::vector but please, no plain C arrays. Not sure how ARC will handle this but I suppose it will not appreciate much. Objective-C and C++ both provides all the tools you need to handle collections of whatever.
Serialization. You have several possibilities, one of them is NSCoder.
Last word, with the so called modern syntax, converting things into ObjC objects is quite easy.
BOOL b = YES;
int i = 10;
double d = 3.14;
char* s = "Pouf pouf";
You get the ObjC equivalents with the boxin' thingy:
NSNumber* bo = #( b );
NSNumber* io = #( i );
NSNumber* do = #( d );
NSString* so = #( s );
NSArray* ao = #[ #( i ), do ];
NSDictionary* = #{ #"num" : io, #"str" : #( s ) };
To write something in a file, in one gracious step:
[#{ #"bool" : bo, #"array" : #[ #"string", #10, #( 10 + 20 ) ] }
writeToFile: #"path.plist" atomically: YES];
But the question remains, what are you trying to accomplish?
One easy approach is to store these arrays in an NSMutableDictionary object and use the method:
[mutableDict writeToFile:#"path/to/file" atomically:YES];
To store the data and:
NSMutableDictionary *anotherDict = [NSMutableDictionary dictionaryWithContentsOfFile:#"path/to/file"];
To read the contents back in.
Here's what I'd suggest:
Make a custom class with the properties you want (.h file):
#import <Foundation/Foundation.h>
#interface CustomHolder : NSObject {
NSString *last;
NSString *first;
NSString *middle;
}
#property (strong, nonatomic) NSString *last;
#property (strong, nonatomic) NSString *first;
#property (strong, nonatomic) NSString *middle;
#end
And then set the .m file up so that you can encode/decode the object
#import "CustomHolder.h"
#implementation CustomHolder
#synthesize last, first, middle;
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:first forKey:#"first"];
[encoder encodeObject:last forKey:#"last"];
[encoder encodeObject:middle forKey:#"middle"];
}
- (id)initWithCoder:(NSCoder *)decoder
{
if (self = [super init])
{
self.first = [decoder decodeObjectForKey:#"first"];
self.last = [decoder decodeObjectForKey:#"last"];
self.middle = [decoder decodeObjectForKey:#"middle"];
}
return self;
}
#end
Then you can just
[NSKeyedArchiver archiveRootObject:obj toFile:[self saveFilePath]] to save and
[NSKeyedUnarchiver unarchiveObjectWithFile:[self saveFilePath]] to load
That's probably the most similar to using C-structs (especially because ARC doesn't let you use structs).

Trying to write coredata to csv using chcsvparser

I've done the following to put a fetched request into an array of arrays but now i don't know which methods i need to call from chcsvparser to write this into a csv file
NSArray *objectsForExport = [fetchedResultsController fetchedObjects];
NSArray *exportKeys = [NSArray arrayWithObjects:#"best_checkout", #"darts_thrown", #"high_score", #"score_100", #"score_140", #"score_180",#"three_dart_average",nil];
NSMutableArray *csvObjects = [NSMutableArray arrayWithCapacity:[objectsForExport count]];
for (NSManagedObject *object in objectsForExport) {
NSMutableArray *anObjectArray = [NSMutableArray arrayWithCapacity:[exportKeys count]];
for (NSString *key in exportKeys) {
id value = [object valueForKey:key];
if (!value) {
value = #"";
}
[anObjectArray addObject:[value description]];
}
[csvObjects addObject:anObjectArray];
}
As Johann suggests, you should use the writeToCSVFile:atomically: convenience method. However, be aware that using it as you describe in your comment is not correct.
The NSString you pass in should be the filepath you want the data writing to.
This webpage should give you the necessary information and methods when writing CSV files:
https://github.com/davedelong/CHCSVParser#readme
Hope this helps!

Save / Write NSMutableArray of objects to disk?

Initially I thought this was going to work, but now I understand it won't because artistCollection is an NSMutableArray of "Artist" objects.
#interface Artist : NSObject {
NSString *firName;
NSString *surName;
}
My question is what is the best way of recording to disk my NSMutableArray of "Artist" objects so that I can load them the next time I run my application?
artistCollection = [[NSMutableArray alloc] init];
newArtist = [[Artist alloc] init];
[newArtist setFirName:objFirName];
[newArtist setSurName:objSurName];
[artistCollection addObject:newArtist];
NSLog(#"(*) - Save All");
[artistCollection writeToFile:#"/Users/Fgx/Desktop/stuff.txt" atomically:YES];
EDIT
Many thanks, just one final thing I am curious about. If "Artist" contained an extra instance variable of NSMutableArray (softwareOwned) of further objects (Applications) how would I expand the encoding to cover this? Would I add NSCoding to the "Applications" object, then encode that before encoding "Artist" or is there a way to specify this in "Artist"?
#interface Artist : NSObject {
NSString *firName;
NSString *surName;
NSMutableArray *softwareOwned;
}
#interface Application : NSObject {
NSString *appName;
NSString *appVersion;
}
many thanks
gary
writeToFile:atomically: in Cocoa's collection classes only works for property lists, i.e. only for collections that contain standard objects like NSString, NSNumber, other collections, etc.
To elaborate on jdelStrother's answer, you can archive collections using NSKeyedArchiver if all objects the collection contains can archive themselves. To implement this for your custom class, make it conform to the NSCoding protocol:
#interface Artist : NSObject <NSCoding> {
NSString *firName;
NSString *surName;
}
#end
#implementation Artist
static NSString *FirstNameArchiveKey = #"firstName";
static NSString *LastNameArchiveKey = #"lastName";
- (id)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self != nil) {
firName = [[decoder decodeObjectForKey:FirstNameArchiveKey] retain];
surName = [[decoder decodeObjectForKey:LastNameArchiveKey] retain];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:firName forKey:FirstNameArchiveKey];
[encoder encodeObject:surName forKey:LastNameArchiveKey];
}
#end
With this, you can encode the collection:
NSData* artistData = [NSKeyedArchiver archivedDataWithRootObject:artistCollection];
[artistData writeToFile: #"/Users/Fgx/Desktop/stuff" atomically:YES];
Take a look at NSKeyedArchiver. Briefly :
NSData* artistData = [NSKeyedArchiver archivedDataWithRootObject:artistCollection];
[artistData writeToFile: #"/Users/Fgx/Desktop/stuff" atomically:YES];
You'll need to implement encodeWithCoder: on your Artist class - see Apple's docs
Unarchiving (see NSKeyedUnarchiver) is left as an exercise for the reader :)