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.
Related
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.
I know it may look like a duplicate for XML Parsing posts but i am really not able to understand the nodes and the delegate methods on how they behave. I have an XML..
<?xml version="1.0" encoding="UTF-8"?>
<ParticipantService>
<Response>
<CourseProperties>
<CourseTitle>AICC_Flash_Workshop_PPT_to_web_examples</CourseTitle>
<CourseCode>123456</CourseCode>
<Availability>Open</Availability>
<Status>In Progress</Status>
<ImageLink>HTTP://lmsstaging.2xprime.com/images/inprogress_icon.png</ImageLink>
<CategoryCode>0</CategoryCode>
<CategoryDesc>General</CategoryDesc>
</CourseProperties>
<CourseProperties>
<CourseTitle>Behaviours</CourseTitle>
<CourseCode>OBIUS</CourseCode>
<Availability>Open</Availability>
<Status>In Progress</Status>
<ImageLink>HTTP://lmsstaging.2xprime.com/images/inprogress_icon.png</ImageLink>
<CategoryCode>0</CategoryCode>
<CategoryDesc>General</CategoryDesc>
</CourseProperties>
<CourseProperties>
<CourseTitle>Customer Service Skills (Part - one)</CourseTitle>
<CourseCode>css_1</CourseCode>
<Availability>Open</Availability>
<Status>In Progress</Status>
<ImageLink>HTTP://lmsstaging.2xprime.com/images/inprogress_icon.png</ImageLink>
<CategoryCode>0</CategoryCode>
<CategoryDesc>General</CategoryDesc>
</CourseProperties>
....
My requirement is to store the relevant course details into respective array's. so i declared six nsmutablearray's but getting confused on how to retreive the data from the XMl. I am trying it out this way
in foundCharacters method i am appending the value of string as
videoUrlLink = [NSMutableString stringWithString:string];
and in didEndElement method
if ([elementName isEqualToString:#"CourseTitle"]) {
[courseDetailList addObject:string];
}
but at the end of the XMl i am able to store only one Value in the array. Please let me know if i am going wrong somewhere?
I assume you have a class called Course, and a Course object has properties for title, code, availability and so on.
Make an iVar currentCourse.
Then, in your parser:didStartElement:namespaceURI:qualifiedName:attributes: (note: did start, not end!) method:
if ([elementName isEqualToString:#"CourseProperties"]) {
//create a new course object
currentCourse = [[Course alloc] init];
}
This gives context to all the properties of the course that follow. In the didEndElement: method, you basically do this for all the course properties:
if ([elementName isEqualToString:#"CourseTitle"]) {
[currentCourse setTitle:string];
}
And, last but not least, once the CourseProperties closing tag is found, save the new course somewhere (also in didEndElement:):
if ([elementName isEqualToString:#"CourseProperties"]) {
//create a new course object
[allMyCourses addObject:currentCourse];
currentCourse = nil;
}
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 {
}
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.
The docs say:
you should implement methods of the
form validate:error:, as defined by the NSKeyValueCoding protocol
so lets say I have an attribute which is an int: friendAge
I want to make sure that any friend may not be younger than 30. So how would I make that validation method?
-validateFriendAge:error:
What am I gonna do in there, exactly? And what shall I do with that NSError I get passed? I think it has an dictionary where I can return a humanly readable string in an arbitrary language (i.e. the one that's used currently), so I can output a reasonable error like: "Friend is not old enough"... how to do that?
You can do anything you want in there. You can validate that the age is between ranges or any other logic you want.
Assuming there is an issue, you populate the error and have at least a value for the NSLocaliedDescriptionKey. That error will then be handed back to the UI or whatever this value is getting set from and allow you to handle the validation error. This means if there is other useful information you may need in the calling/setting method, you can add it into the NSError here and receive it.
For example:
-(BOOL)validateFreindAge:(NSNumber*)ageValue error:(NSError **)outError
{
if ([ageValue integerValue] <= 0) {
NSString *errorDesc = #"Age cannot be below zero.";
NSDictionary *dictionary = [NSDictionary dictionaryWithObject:errorDesc forKey:NSLocalizedDescriptionKey];
*error = [NSError errorWithDomain:#"MyDomain" code:1123 userInfo:dictionary];
return NO;
}
return YES;
}