Serializing an object to JSON - iphone

I am new to the JSON format, but it looks ideal for what I am attempting. I need to take a custom NSObject (a recipe) and send it via a URL string in an email. The recipient will then open the link in my app and the URL will be parsed.
My existing implementation manually builds a string from the recipe’s details and decodes it on the other end. I would prefer to use something more standard, like JSON.
So far I have added the following method to my Recipe class:
- (NSData *)jsonRepresentation
{
NSDictionary *dictionary = #{#"name":self.name,
#"instructions":self.instructions};
NSError* error;
return [NSJSONSerialization dataWithJSONObject:dictionary
options:NSJSONWritingPrettyPrinted
error:&error];
}
I can successfully log this NSData object using:
[[NSString alloc]initWithData:recipe.jsonRepresentation
encoding:NSUTF8StringEncoding];
However, I can’t yet add my list of ingredients (an NSArray). I attempted to simply use this:
NSDictionary *dictionary = #{ #"name" : self.name,
#"instructions" : self.instructions,
#"ingredients" : self.orderedIngredients };
but on logging, I receive this error:
Invalid type in JSON write (Ingredient)
As you can tell, I’m pretty new at this.
Am I meant to do something to the ingredients array before I add it to the dictionary?

Try this:
NSDictionary *dictionary = #{ #"name" : self.name,
#"instructions" : self.instructions,
#"ingredients" : [self.orderedIngredients valueForKey:#"name"] };
Assuming that self.orderedIngredients is an array with ingredients objects in it and that ingredients has a property called name in it,
[self.orderedIngredients valueForKey:#"name"]
will return an array of all names.

If your list of ingredients array holds custom class objects, you should create a serializer for that class.

Related

How to convert JSON to Object

I defined some custom classes, such as Teacher, Student...
Now I receive teacher info (JSON string) from remote server.
How can I convert the JSON string to Teacher object.
In Java, it's easy to implement a common method for all class (Teacher, Student...) with reflect.
But in Objective-C on iOS, the best way I can find is to use Entity of Core Data, which has setValue:forKey method. First I convert the JSON string to NSDictionary, the set the key-value pair in the disctionary to the Entry.
Is there any better ways?
(I'm from China, so maybe my English is poor, sorry!)
First, do you use JSON Parser? (if not, i'd recommend using SBJson).
Second, why not create an initWithDictionary init method in your custom class that returns self object?
These are all good frameworks for JSON parsing to dictionaries or other primitives, but if you're looking to avoid doing a lot of repetitive work, check out http://restkit.org . Specifically, check out https://github.com/RestKit/RestKit/blob/master/Docs/Object%20Mapping.md This is the example on Object mapping where you define mapping for your Teacher class and the json is automagically converted to a Teacher object by using KVC. If you use RestKit's network calls, the process is all transparent and simple, but I already had my network calls in place and what I needed was to convert my json response text to a User object (Teacher in your case) and I finally figured out how. If that's what you need, post a comment and I'll share how to do it with RestKit.
Note: I will assume the json is output using the mapped convention {"teacher": { "id" : 45, "name" : "Teacher McTeacher"}}. If it's not this way, but instead like this {"id" : 45, "name" : "Teacher McTeacher"} then don't worry ... object mapping design doc in the link shows you how to do this...a few extra steps, but not too bad.
This is my callback from ASIHTTPRequest
- (void)requestFinished:(ASIHTTPRequest *)request {
id<RKParser> parser = [[RKParserRegistry sharedRegistry] parserForMIMEType:[request.responseHeaders valueForKey:#"Content-Type"]]; // i'm assuming your response Content-Type is application/json
NSError *error;
NSDictionary *parsedData = [parser objectFromString:apiResponse error:&error];
if (parsedData == nil) {
NSLog(#"ERROR parsing api response with RestKit...%#", error);
return;
}
[RKObjectMapping addDefaultDateFormatterForString:#"yyyy-MM-dd'T'HH:mm:ssZ" inTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"UTC"]]; // This is handy in case you return dates with different formats that aren't understood by the date parser
RKObjectMappingProvider *provider = [RKObjectMappingProvider new];
// This is the error mapping provider that RestKit understands natively (I copied this verbatim from the RestKit internals ... so just go with it
// This also shows how to map without blocks
RKObjectMapping* errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]];
[errorMapping mapKeyPath:#"" toAttribute:#"errorMessage"];
[provider setMapping:errorMapping forKeyPath:#"error"];
[provider setMapping:errorMapping forKeyPath:#"errors"];
// This shows you how to map with blocks
RKObjectMapping *teacherMapping = [RKObjectMapping mappingForClass:[Teacher class] block:^(RKObjectMapping *mapping) {
[mapping mapKeyPath:#"id" toAttribute:#"objectId"];
[mapping mapKeyPath:#"name" toAttribute:#"name"];
}];
[provider setMapping:teacherMapping forKeyPath:#"teacher"];
RKObjectMapper *mapper = [RKObjectMapper mapperWithObject:parsedData mappingProvider:provider];
Teacher *teacher = nil;
RKObjectMappingResult *mappingResult = [mapper performMapping];
teacher = [mappingResult asObject];
NSLog(#"Teacher is %# with id %lld and name %#", teacher, teacher.objectId, teacher.name);
}
You can obviously refactor this to make it cleaner, but that now solves all my problems.. no more parsing... just response -> magic -> Object
Specifically, check out https://github.com/fanpyi/jsontooc/blob/master/README.md
This is the example convert JSON data to Objective-C model, use nodejs.

Change the content of a string within an array of strings

This is my code:
NSString *newString = #"new value";
[breakdownCollection objectAtIndex:i] = newString;
breakdownCollection is an NSArray of multiple strings. I need to access a given string contained in the array via index number, and change the string's content to that of the new string. Note that I cannot simply replace the string with the new one, I am only trying to replace its contents.
When I try to do this, however, I get an "lvalue required as left operand of assignment" error.
Any help with this issue would be very much appreciated!
The error you get is because you wrote the assignement instruction incorrectly. That is, you cannot assign newString to [breakdownCollection objectAtIndex:i].
Also, you won't be able to do it this way. Instead, in order to modify string object content, use NSMutableString, which provides methods to do so (NSString are immutable objects).
So, for example you should try :
[[breakdownCollection objectAtIndex:i] setString:newString];
assuming you put NSMutableString into breakdownCollection.
PS : in order to change the object at the index i, you have to use NSMutableArray instead of NSArray, and then call :
[breakdownCollection replaceObjectAtIndex:i withObject:newString];
Good luck !
NSMutableString class reference
NSMutableArray class reference
Use an NSMutableArray instead and then you can use the method -replaceObjectAtIndex: withObject:

PhoneGap.exec() passing objects between JS and Obj-C

The only way I found to passing objects between the JS and Obj-C it's by encoding the JS object by using JSON.stringify() and pass the json string to PhoneGap.exec
PhoneGap.exec('Alarm.update',JSON.stringify(list));
... and rebuild the object in Obj-C:
NSString *jsonStr = [arguments objectAtIndex:0];
jsonStr = [jsonStr stringByReplacingOccurrencesOfString:#"\\\"" withString:#"\""];
jsonStr = [NSString stringWithFormat:#"[%#]",jsonStr];
NSObject *arg = [jsonStr JSONValue];
It's that correct ? there is a better/proper/official way for doing this ?
PhoneGap.exec was designed for simple types. Your way is ok, alternately you can just pass your single object in (would only work for a single object only, see footer about how we marshal the command), and it should be in the options dictionary for the command. Then on the Objective-C side, use key-value coding to automatically populate your custom object with the dictionary.
e.g.
MyCustomObject* blah = [MyCustomObject new];
[blah setValuesForKeysWithDictionary:options];
If you are interested in how PhoneGap.exec works, read on...
* --------- *
For PhoneGap.exec, the javascript arguments are marshaled into a URL.
For the JS command:
PhoneGap.exec('MyPlugin.command', 'foo', 'bar', 'baz', { mykey1: 'myvalue1', mykey2: 'myvalue2' });
The resulting command url is:
gap://MyPlugin.myCommand/foo/bar/baz/?mykey1=myvalue1&mykey2=myvalue2
This will be handled and converted on the Objective-C side. foo, bar, baz are put in the arguments array, and the query parameters are put in the options dictionary. It will look for a class called 'MyPlugin' and will call the selector 'myCommand' with the arguments array and options dictionary as parameters.
For further info, see phonegap.js, look at PhoneGap.run_command
I think this is the best way to do it, if not the only way.
The PhoneGap.exec call just takes a NSDictionary of objects under the covers so I don't see of a better way to handle it.
most methods are structured like
- (void)someMethod:(NSArray*)arguments withDict:(NSDictionary*)options {
}

Retrieve properties of "id" type object iphone

I'm facing some difficulty in retrieving properties of "id" type object. This is how I'm accessing it:
I'm doing following to assign an object to id type object from a generic array containing different types of objects and calling method "savedata" to which I'm passing the object as well as its type:
for(id objInArray in genericArray){
NSString *objType = [objInArray valueForKey:#"type"];
[objInArray retain];
[self saveData:objInArray :objType];
}
In savedata method I'm writing following code to retrieve the properties of id object:
-(void)saveData:(id)object :(NSString *)objectType
{
self.managedObjectContext = appDelegate.managedObjectContext;
if([objectType isEqualToString:#"event"])
{
Event * newEvent = (Event *)[NSEntityDescription
insertNewObjectForEntityForName:#"Event"
inManagedObjectContext:self.managedObjectContext];
[newEvent setEletitle:[NSString stringWithFormat:#"%#", [object valueForKey:#"eletitle"]]];
[self saveAction];
}
But the object "object" containing the values fails to assign them to object newEvent.
I also tried to retrive this value in a string object like this:
NSString *eletit = [object valueForKey:#"eletitle"];
[eletit retain];
But eletit is also invalid at the end of this transaction.
Can anybody please help? This' really urgent.
Thanx in advance.
I don't have you answer unfortunately but I have few comments on your code.
Are you sure it's normal you array contain so generics object? It's strange because all your object contained in your array need to respond to "type" or "eletitle" messages, so I guess objInArray is less generic than just "id".
Second, it's not recommended to have selector like saveData::, in Objective-C it's usual and recommended to name the arguments, it's more understandable.

iPhone: How to write plist array of dictionary object

I'm a young Italian developer for the iPhone. I have a plist file (named "Frase") with this structure:
Root Array
- Item 0 Dictionary
Frase String
Preferito Bool
- Item 1 Dictionary
Frase String
Preferito Bool
- Item 2 Dictionary
Frase String
Preferito Bool
- Item 3 Dictionary
Frase String
Preferito Bool
exc.
An array that contains many elements dictionary, all the same, consisting of "Frase" (string) and "Preferito" (BOOL).
The variable "indirizzo", increase or decrease the click of the button Next or Back. The application interface:
http://img691.imageshack.us/img691/357/schermata20100418a20331.png
When I click on AddPreferito button, the item "Preferito" must be YES. Subsequently, the array must be updated with the new dictionary.The code:
(void)addpreferito:(id)sender {
NSString *plistPath = [[NSBundle mainBundle] pathForResource:#"Frase" ofType:#"plist"];
MSMutableArray *frase = [[NSMutableArray alloc] initWithContentsOfFile:plistPath];
NSMutableDictionary *dictionary = [frase objectAtIndex:indirizzo];
[dictionary setValue: YES forKey:#"Preferito"];
[frase replaceObjectAtIndex:indirizzo withObject:dictionary];
[frase writeToFile:plistPath atomically:YES];
}
Why not work?
Thanks Thanks Thanks!
What Jason said is correct, but it looks like there's another, more serious problem in your code: it appears that you're passing a primitive value (the defined constant YES) as the first argument to -setValue:forKey:, which expects an argument of type id (in other words, an object, not a primitive).
Instead, you can use an instance of NSNumber to wrap the boolean value, and then put it in the array, like so:
[dictionary setValue:[NSNumber numberWithBool:YES] forKey:#"Preferito"];
You are loading your initial plist from inside your application bundle. Unfortunately, you cannot write to this directory. When you need to do is check if your plist already exists in the user documents directory. If it does, load it from there. If not, load your template (original) from inside the application bundle. When you write the file back, you must always write it to the user documents directory.
For more information on managing your application's files, please see the Files and Data Management section of the iPhone Application Programming Guide.