I'm receiving a JSON payload of data from a MVC API in my iOS application. NSJSONSerialization then serializes this into an object. This object contains some properties and also a list of data objects. The data objects are of type NSDictionary. I already have the class structure of these objects in ObjC (Im using odata so i want to convert the objects to their OdataObject equivalent).
So I'd like to know how I can cast/convert these NSDictionary objects to their corresponding OdataObject class (or any object really)?
You can't cast an NSDictionary instance to be an OdataObject, you either need to explicitly convert the instance or create the appropriate instance when you deserialise the JSON.
You could look at using setValuesForKeysWithDictionary: to push your dictionary contents into another instance using KVC. Whether this will work in this case depends on the OdataObject definition (from github? Not convinced) and the dictionary contents...
Write a class category for NSDictionary that allows the conversion to the OdataObject class? I'm sorry, I don't completely understand what you're asking but if you need to be able to convert NSDictionary to a custom object, then I recommend Class Categories:
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html
Yes, you can not cast your NSDictionary instance to your custom model object. For that you need to write code of conversion.
1) Create a class which inherits NSObject with required properties.
2) Synthesize all the properties
3) Write one private keyMapping method which returns the dictionary with keys you want in your model object as
-(NSDictionary *)keyMapping {
return [[NSDictionary alloc] initWithObjectsAndKeys:
#"key1", #"key1",
#"key2", #"key2",
#"key3", #"key3",
#"key4", #"key4",
#"key5", #"key5",
nil];
}
4) Write class method which takes NSDictionary instance, as a parameter and returns instance of the same model class with filled values from NSDictionary as (Pass your dictionary to this method)
+(ModelClass *)getModelClassObjectFromDictionary:(NSDictionary *)dictionary {
ModelClass *obj = [[ModelClass alloc] init];
NSDictionary *mapping = [obj jsonMapping];
for (NSString *attribute in [mapping allKeys]){
NSString *classProperty = [mapping objectForKey:attribute];
NSString *attributeValue = [dictionary objectForKey:attribute];
if (attributeValue!=nil&&!([attributeValue isKindOfClass:[NSNull class]])) {
[obj setValue:attributeValue forKeyPath:classProperty];
}
}
return obj;
}
Thats it. Hope this helps you.
Related
I'm getting an NSCFDictionary returned to me and I can't figure out how to use it. I know it's of type NSCFDictionary because I printed the class and it came out as __NCSFDictionary. I can't figure out how to do anything with it.
I'm just trying to hold onto it for now but can't even get that to work:
NSDictionary *dict = [[NSURLCredentialStorage sharedCredentialStorage] allCredentials];
for(NSURLProtectionSpace key in [dict keyEnumerator])
{
NSCFDictionary *value = [dict objectForKey:key];
}
The class reference for allCredentials says its supposed to return a dictionary whose values are also dictionaries. My assignment statement isn't working though. Do I need a cast of some kind?
NSDictionary and the other collection classes are actually class clusters: several concrete subclasses classes masquerading under the interface of a single class: they all provide the same functionality (because they are subclasses of the same class — in NSDictionary's case, this involves the three "primitive methods" -count, -objectForKey:, and -keyEnumerator), but have different internal workings to be efficient in different situations, based on how they're created and what type of data they may be storing.
NSCFDictionary is simply a concrete subclass of NSDictionary. That is, your NSDictionaries may actually be NSCFDictionary instances, but you should treat them as instances of NSDictionary, because that will provide you with the required dictionary-storage functionality.
NSDictionary *value = [dict objectForKey:key];
Now, another reason your code doesn't work: NSURLProtectionSpace is a class, so you should use it as a pointer, like this:
for (NSURLProtectionSpace *key ...
NSCFDictionary is the private subclass of NSDictionary that implements the actual functionality. It's just an NSDictionary. Just about any NSDictionary you use will be an NSCFDictionary under the hood. It doesn't matter to you code. You can type the variable as NSDictionary and use it accordingly.
I have an NSCFDictionary that is actually a NSMutableDictionary object, I can delete items from it. I mention this to further clarify jtbandes' answer: the NSCFDictionary object may be any object that inherits from NSDictionary.
I have a JSON structure like this:
{
id:100
contactInfo: {
name: John Doe
city: New York
}
}
and a corresponding Customer class w/ the following properties including a nested contactInfo class.
Customer.id
Customer.contactInfo.name
Customer.contactInfo.city
Is it possible to convert the JSON data directly to an instance of the Customer class?
I know how to get an NSDictionary object, but that is very cumbersome and verbose to work with and would rather convert/deserialize the data directly to the Customer class.
EDITED w/ Additional Info:
In other languages I have worked with there is built-in support for deserialization of JSON to custom objects. All you have to do is mark the class as "Serializable" and the JSON is deserialized w/o the need to write custom code in each class.
The NSDictionary object can become very cumbersome if the class has nested classes. It would be much easier to use the custom object properties like this
Customer.contactInfo.name
rather than
[(NSDictionary*)[customerDict objectForKey:#"contactInfo"] objectForKey:#"name"]
thus my question about a standard built-in deserializer in Objective-C.
As of iOS5 there if official support for JSON
https://developer.apple.com/library/ios/#documentation/Foundation/Reference/NSJSONSerialization_Class/Reference/Reference.html
I don't know why you think it is cumbersome to work with. A simple way to do it would be to have an initialiser for your customer object that took a dictionary as a parameter.
That way you could get your downloaded data as JSON, deserialise it into a dictionary and then pass it to the initializer to create an object from the dictionary.
Any of the dozens of JSON libraries will parse your JSON string and turn it into an NSDictionary. You're going to have to deal with an intermediate format if you don't want to write a parser yourself. But, once you have the NSDictionary you can use Key Value Coding (KVC) to set the properties on your object:
for (NSString *key in jsonDictionary) {
[customer setValue:[jsonDictionary objectForKey] forKey:key];
}
KVC is built into Cocoa, and defines setValue:forKey: for all objects, so you can set properties by name regardless of how they are defined.
You'll have to do something smarter than the loop above to handle your internal ContactInfo class, but you should be able to figure that out. For safety (since the JSON is probably coming over the network) you might want to make a whitelist of keys and loop over that, and use valueForKeyPath:/setValue:forKeyPath: to handle nested objects.
Consider using RestKit api (http://restkit.org/) and its object mapping system...
Here is some wiki : https://github.com/RestKit/RestKit/wiki/Object-mapping
Write an initializer for your Customer class that takes JSON data as a parameter, something like:
-(id)initWithJSONData:(NSData*)data;
You can implement that method to do what Abizern suggests: read the data into a dictionary, and then pull whatever values you need out of the dictionary.
If you want to get fancy, another way you could go is to adopt NSCoding in your Customer class, and then create a NSCoder subclass that deserializes JSON data. This could work very much like NSKeyedUnarchiver. Doesn't seem worth the trouble unless you're going to take the same approach with a number of your classes, though.
Using this library, you can do the following:
SBJSON *parser = [[SBJSON alloc] init];
NSDictionary *customer = [parser objectWithString:jsonString];
You can then access your properties using:
[customer objectForKey:#"id"];
[[customer objectForKey:#"contactInfo"] objectForKey:#"name"];
[[customer objectForKey:#"contactInfo"] objectForKey:#"city"];
You can then use this code in your Customer class' init function. For example:
- (id)initWithJSON:(NSString *)jsonString {
self = [super init]; // or whatever you need here
if (self) {
SBJSON *parser = [[SBJSON alloc] init];
NSDictionary *customer = [parser objectWithString:jsonString];
self.id = [customer objectForKey:#"id"];
self.contactInfo = [[NSDictionary alloc] initWithObjectsAndKeys:[[customer objectforKey:#"contactInfo"] objectForKey:#"name"], #"name", [[customer objectforKey:#"contactInfo"] objectForKey:#"city"], #"city"]
}
return self;
}
I was busy with this for some hours, but see no head or tail.
How should I create an NS(Mutable)Dictionary category that I can let return the same class as the sending method class.
Sample
+(NSDictionary *)dictionaryWithAdditions:path error:(NSError**)error;
NSDictionary * dict = [NSDictionary dictionaryWithAdditions:path error:&error];
should return an NSDictionary.
AND
+(NSMutableDictionary *)dictionaryWithAdditions:path error:(NSError**)error;
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithAdditions:path error:&error];
should return an NSMutableDictionary.
I dont want to return an id, to avoid any possible warning etc.
Suggestions how I should create this in one category with error management?
You either need to create two different methods (e.g. dictionaryWithAdditions: and mutableDictionaryWithAdditions:) or return id (I would suggest the latter). The type system doesn't allow us to say "NSDictionary if the receiver is statically typed as NSDictionary, NSMutableDictionary if the receiver is statically typed as NSMutableDictionary, and error otherwise".
What warnings would you get when returning id? That's what the standard init method does anyway.
Your method would probably look something like:
+ (id)dictionaryWithAdditions:path error:(NSError**)error;
{
id dictionary = [[[self alloc] init] autorelease];
// ... do something
return dictionary;
}
I have an NSMutableArray, if this is store string, I can read, and write it successfully, using this method.
[array writeToFile:m_sApplicationPlistPath atomically:YES];
NSMutableArray *array = [[NSMutableArray alloc] initWithContentsOfFile:m_sApplicationPlistPath];
but if my Array add something which is not a simple string, for example, I add a special Object to the array, like this:
[array addObject:[[SpecialObject alloc] init]];
I find that I can't read back the special object, how can I solve it, thank you.
Implement the NSCoding protocol. (encodeWithCoder and initWithCoder)
If you still cannot use the method writeToFile:atomically: then you have to serialize the NSArray to get data, and then write it to an file.
NSData *data = NSKeyedArchiver archivedDataWithRootObject:theArray];
I think you can't do that because [NSArray writeToFile:atomically:] method makes use of property lists, which are only available for certain data types (NSString, NSData, NSArray or NSDictionary).
In order for you to write the array to a file you can make SpecialObject comply to NSCoding protocol and then save the array using -[NSKeyedArchiver archiveRootObject:toFile:] using the array as root object.
You can get this to work by using methods defined in the NSKeyValueCoding protocol to convert the instances of your custom class to instances of NSDictionary before you write them. You could do a similar conversion after reading the plist file back in to recreate the objects.
Here's an example that converts an array of instances of a custom Book class to an array of dictionaries, using the dictionaryWithValuesForKeys: method declared in NSKeyValueCoding (and implemented by NSObject, so all objects inherit this behavior):
+ (NSArray *)dictionariesFromBooks:(NSArray *)books
{
NSMutableArray *bookDicts = [NSMutableArray arrayWithCapacity:[books count]];
for (Book *currBook in books)
{
NSDictionary *currDict = [currBook dictionaryWithValuesForKeys:[Book keys]];
[bookDicts addObject:currDict];
}
return bookDicts;
}
Similarly, here's a method that populates instances of Book using the NSKeyValueCoding method setValuesForKeysWithDictionary:
+ (NSArray *)booksFromDictionaries:(NSArray *)bookDicts
{
NSMutableArray *books = [NSMutableArray arrayWithCapacity:[bookDicts count]];
for (NSDictionary *currDict in bookDicts)
{
Book *currBook = [[Book alloc] init];
[currBook setValuesForKeysWithDictionary:currDict];
[books addObject:currBook];
[currBook release];
}
return books;
}
With a little work, this can be made to cascade to nested custom objects. For example, if Book contained a nested Author instance, you could override NSKeyValueCoding methods such as setValue:forKey: and dictionaryWithValuesForKeys: to convert the instance on the fly.
From docs:
If the array’s contents are all property list objects (NSString, NSData, NSArray, or NSDictionary objects), the file written by this method can be used to initialize a new array with the class method arrayWithContentsOfFile: or the instance method initWithContentsOfFile:. This method recursively validates that all the contained objects are property list objects before writing out the file, and returns NO if all the objects are not property list objects, since the resultant file would not be a valid property list.
I have a sudzc service class generated from a WSDL that accepts an ArrayOfInt and ArrayOfString objects as parameters. The service method signature is this:
- (SoapRequest*) Search: (id <SoapDelegate>) handler filters: (NSMutableArray*) displayedAttributes: (NSMutableArray*) displayedAttributes;
My question is, how do I pass values into the parameters that expect NSMutableArrays?
In the above method signature, the "displayedAttributes" parameter is expecting an ArrayOfInt object (which should be populated with several integers in an int tag, e.g., <int>1</int><int>2</int><int>3</int> etc).
However none of these things which I've tried have worked:
Directly passing an NSArray/NSMutableArray of (int) objects
Directly passing an NSArray/NSMutableArray of NSNumber objects
Passing an array of strings containing #"1", #"2", #"3" etc
Passing an array of strings that already contain #"<int>1</int>", #"<int>2</int>", etc
Constructing a CXMLDocument out of a string based on the integers
I'm sure this is somehow explained in the accompanying documentation in the download -- it's just not clear to me at the moment.
#Jon Limjap: Lucky you are!!! it asks you for a type which you have dealt before, I have custom class type that SudzC generated for me (!)... It initializes only when passed CXMLNode, (which need CXMLDocument / CXMLElement).. I have no idea how to deal with such type...
an instance is: filter is a class, I have a class of the filter, but there is no way to initialize it, (except alloc-init and then setting its properties, but its properties are another such custom type.. !!!!)...If you know any "trick" to tell/configure sudzc to allow us to pass objects or fetch objects of cocoa type, do tell me....
I had similar situation of passing array of objects to SOAP request. I managed to get it working by doing following changes.
SOAP array case in not added in
+(NSString *) serialize: (id) object()
{
//look if it is array not implemented
}
so I managed to change in the following method
+ (NSString*) serialize: (id) object withName: (NSString*) nodeName {
if([object respondsToSelector:#selector(serialize:)]) {
if([object isKindOfClass:[SoapArray class]])
return [object serialize:object];
return [object serialize: nodeName];
}
NSString *temp =[NSString stringWithFormat:#"<%#>%#</%#>", nodeName, [Soap serialize: object], nodeName];
NSLog(#"serialise = %#", temp);
return temp;
}
at the time SOAP request,
NSMutableArray arr = [[MYTable_STUB_ARR alloc] init]
MYTABLE_OBJ *obj = [[MYTABLE_OBJ alloc] init];
[arr addObject:obj];
[obj release];
Pass element arr object your SOAP Request ??
This is a bit old, but I hope it will help someone. I implemented the serialize: method on SoapArray this way:
- (NSMutableString *)serialize:(NSString *)name
{
NSMutableString *str = [NSMutableString string];
//[str appendFormat:#"<%#>", name];
for (id content in self)
{
//[str appendString:[Soap serialize:content]];
[str appendString:[Soap serialize:content withName:name]];
}
//[str appendFormat:#"</%#>", name];
return str;
}
As you can see, there are some commented lines. If you uncomment them and comment the currently used one inside the for, you will get a tag named name which will contain objects tagged with the content class name.