How to parse specific Json Array - objective-c

I have a Json array like this:
{"Response":{"Token":"///","Name":"///","Surname":"///","Phone":"///","Street":"///","Interno":"///","PostalCode":"///","City":"///","Province":{"ID":"///","Code":"///","Name":"///"},"Email":"///#gmail.com"},"Error":false,"ErrorDetails":null}
How can I parse the values inside Response and inside Province using objective-c?
I tried with the following code:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// Create an array to store the locations
if(_fproducts == nil)
{
_fproducts = [[NSMutableArray alloc] init];
}
// Parse the JSON that came in
NSError *error;
jsonArray = [NSJSONSerialization JSONObjectWithData:_downloadedData options:NSJSONReadingAllowFragments error:&error];
// Loop through Json objects, create question objects and add them to our questions array
for (int i = 0; i < jsonArray.count; i++)
{
NSDictionary *jsonElement = jsonArray[i];
// Create a new location object and set its props to JsonElement properties
LoginCredentials *newFProduct = [[LoginCredentials alloc] init];
newFProduct.token = jsonElement[#"Id"];
newFProduct.nomeUser = jsonElement[#"nome"];
NSLog(#"TOKEN:%#", newFProduct.token);
NSLog(#"NOME:%#", newFProduct.nomeUser);
// Add this question to the locations array
[_fproducts addObject:newFProduct];
}
// Ready to notify delegate that data is ready and pass back items
if (self.delegate)
{
[self.delegate itemsDownloaded:_fproducts];
}
}
But I can't parse the values inside Response.. Do i need to create an array of Response and then parse it?

Convert all in NSDictionary, not in NSArray, and then:
jsonDictionary = [NSJSONSerialization JSONObjectWithData:_downloadedData options:NSJSONReadingAllowFragments error:&error];
if(!error) {
NSDictionary* response = jsonDictionary[#"response"];
if(response) {
NSDictionary* province = response[#"province"];
NSLog("Province: %#", province);
/* here you can save all your values */
if(province) {
NSString* identificator = province[#"ID"];
NSString* code = province[#"Code"];
NSString* name = province[#"Name"];
}
}
}
An elegant way to do this is creating your custom interface:
Province.h
#interface Province : NSObject
- (id)initWithDictionary:(NSDictionary*)dictionary;
- (nonatomic, strong) NSString* identificator;
- (nonatomic, strong) NSString* code;
- (nonatomic, strong) NSString* name;
#end
Province.m
#implementation Province
- (id)initWithDictionary:(NSDictionary*)dictionary {
self = [super init];
self.identificator = dictionary[#"ID"];
self.code = dictionary[#"Code"];
self.name = dictionary[#"Name"];
return self;
}
#end
So your code becomes:
if(!error) {
NSDictionary* response = jsonDictionary[#"response"];
if(response) {
NSDictionary* provinceDictionary = response[#"province"];
if(province) {
Province* province = [Province initWithDictionary:provinceDictionary];
}
}
}

Related

How to differentiate the returned value of a function using completion block in Objective C?

I have a function that gives 2 different String values that are returned :
-(NSString*)load:(NSDictionary *)dict
{
NSDictionary *dataDict = [self objectForId:#"data" fromDict:dict withDefault:nil];
if (dataDict) {
NSDictionary *success = [self objectForId:#"success" fromDict:dataDict withDefault:nil];
NSString *str = [NSString stringWithFormat:#"%#", success];
if ([str isEqualToString: #"1"])
{
NSDictionary *idDict = [self objectForId:#"id" fromDict:dataDict withDefault:nil];
if (idDict) {
NSString *idString = [NSString stringWithFormat:#"%#", idDict];
return idString;
}
} else {
NSDictionary *messages = [self objectForId:#"messages" fromDict:dataDict withDefault:nil];
if (messages) {
NSDictionary *messageDict = (NSDictionary *)messages;
NSArray *type = messageDict[#"type"];
if (type.count > 0) {
NSString *messageString = type[0][#"message"];
return messageString;
}
}
}
}
return nil;
}
And accessing the stringValue like this :
NSString *string = [className load:dict];
Now I want to write if else statements for "idString" and "messageString" return values. How do I differentiate the 2 return values?
While returning a NSDictionary (see #Yihui Yang solution), or a custom Class (see #Sulthan's solution) for it are valid solutions, it maybe be too much.
You need to remember the keys of the dictionary returned, or maybe creating a custom class just for that is too much.
Here are two other possibilities:
I'll have has sample dict to test:
NSDictionary *dictToTest1 = #{#"id": #"idString",
#"noiseKey": #"noiseValue"
};
NSDictionary *dictToTest2 = #{#"messages": #"messagesString",
#"noiseKey": #"noiseValue"
};
I'll simplify your test to check only if there is a key/value for key id or for messages.
Using Double pointers:
-(void)loadDict:(NSDictionary *)dict withRetKey:(NSString **)key andRetValue:(NSString **)value
{
NSString *retKey = nil;
NSString *retValue = nil;
if (dict[#"id"])
{
retKey = #"id";
retValue = dict[#"id"];
}
else if (dict[#"messages"])
{
retKey = #"messages";
retValue = dict[#"messages"];
}
if (key)
{
*key = retKey;
}
if (value)
{
*value = retValue;
}
}
Sample test:
NSString *key1 = nil;
NSString *value1 = nil;
[self loadDict:dictToTest1 withRetKey:&key1 andRetValue:&value1];
NSLog(#"Key1: %#\t value1: %#", key1, value1);
NSString *key2 = nil;
NSString *value2 = nil;
[self loadDict:dictToTest2 withRetKey:&key2 andRetValue:&value2];
NSLog(#"Key2: %#\t value2: %#", key2, value2);
Output:
$> Key1: id value1: idString
$> Key2: messages value2: messagesString
Where did you see the & for objects ?
Almost all the times in managing a NSError. (linked question)
For primitive? For sample if you want to retrieve the red/blue/green/alpha of a UIColor (linked question)
With blocks:
-(void)blockLoadDict:(NSDictionary *)dict withBlock:(void(^) (NSString *key, NSString *value))block
{
NSString *retKey = #"";
NSString *retValue = #"";
if (dict[#"id"])
{
retKey = #"id";
retValue = dict[#"id"];
}
else if (dict[#"messages"])
{
retKey = #"messages";
retValue = dict[#"messages"];
}
if (block)
{
block(retKey, retValue);
}
}
Sample:
__block NSString *key3 = nil;
__block NSString *value3 = nil;
[self blockLoadDict:dictToTest1 withBlock:^(NSString *key, NSString *value) {
key3 = key;
value3 = value;
}];
NSLog(#"Block Key3: %#\t value3: %#", key3, value3);
__block NSString *key4 = nil;
__block NSString *value4 = nil;
[self blockLoadDict:dictToTest2 withBlock:^(NSString *key, NSString *value) {
key4 = key;
value4 = value;
}];
NSLog(#"Block Key4: %#\t value4: %#", key4, value4);
Output:
$> Block Key3: id value3: idString
$> Block Key4: messages value4: messagesString
What I understand is that you want to know if load method returns an idString or messageString.
So what I recommend is using a tricky method.
Instead of return a string, you can return a dict which is like
return #{
#"type":#"idString",
#"content":idString
}
And using
NSDictionary * returnDict = [className load:dict]
if ([returnDict[#"type"] isEqualToString:#"idString"]) {
//code here
}
else{
//code here
}
Finally, I know this is not a best solution but it will work fine.
I'd make 2 separate methods. First would only return the id string, the second one would return a message.
That way you can make something like this:
NSDictionary *dict = /* some code here */;
NSString *message = nil;
NSString *idString = [foo loadId:dict];
if (idString.length == 0) {
message = [foo loadMessage:dict];
}
Instead of returning a simple string, create an object that will be returned:
#interface Result: NSObject
#property (nonatomic) NSString *id;
#property (nonatomic) NSString *message;
#end
Ideally, you could create -initWithDictionary: initializer that would handle the parsing.
You can use NSException. Instead of returning idString you throw an NSException
#throw [NSException exceptionWithName:idString reason:nil userInfo:nil];
Then you can call your method like this:
#try{
NSString *messageString = [className load:dict];
NSLog(#"Message String: %#", messageString);
}#catch (NSException *e){
NSString * idString = e.name;
NSLog(#"ID String: %#",idString);
}

How to add multiple email attachments using UIActivityItemProvider

I'm working on an iOS app in which I want to add MULTIPLE attachments to an email using UIActivityItemProvider. I want to do it using UIActivityItemProvider because I do not want to incur the overhead of processing the record before I display the UIActivtyViewController to the user. If I pass in one image using an NSData or an NSURL object to the "url" element of the returned object, then the inline image shows fine. If I pass in an array of these objects then nothing shows up. I believe that passing an array will work if I use the ActivityItems parameter when initializing an NSActivityViewController, but again, I do not want to do this because I want to take advantage of the delayed processing available by using the UIActivityItemProvider. Below is my code
#implementation NoteRecordActivityProvider
- (id)initWithPlaceholderItem:(id)placeholderItem
{
//Initializes and returns a provider object with the specified placeholder data
return [super initWithPlaceholderItem:placeholderItem];
}
- (id)item
{
// //Generates and returns the actual data object
NSData *imageFile = [[NSData alloc]init];
NSString *imageFileName;
NSURL *url;
NSString* exportPath;
NSMutableArray* imageArray = [[NSMutableArray alloc]initWithCapacity:0];
NSInteger photoCount = self.noteRecord.photoCount;
for (NSInteger i = 0; i < photoCount; i+=1)
{
//Add File Attachment
PhotoObject *po = (PhotoObject*)[self.noteRecord photoObjects:i];
NSString *photoGUID = [(PhotoObject*)[self.noteRecord photoObjects:i]GUID];
imageFile = ImageDataReturningMethodHere;
imageFileName = [[NSArray arrayWithObjects:#"Image", [NSString stringWithFormat:#"%ld", (long)i], #".png", nil] componentsJoinedByString:#""];
exportPath = [[FileSystemProvider exportPath] stringByAppendingPathComponent:imageFileName];
[imageFile writeToFile:exportPath atomically:YES];
url = [NSURL fileURLWithPath:exportPath];
[imageArray addObject:url];
}
if ([self.activityType isEqualToString:UIActivityTypeMail])
return imageArray;
else
return nil;
}
- (id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController
{
return #{#"body":#"", #"url":[[NSURL alloc]init]};
}
-(NSString *) activityViewController:(UIActivityViewController *)activityViewController subjectForActivityType:(NSString *)activityType {
return [NSString stringWithFormat:#"Attached Record: %#", self.noteRecord.title];
}
#end
I did find the answer to this question. First I created an PhotoAttachmentActivityProvider that had a property for the source document which contains the photo I wanted to attach, and an index to the attachment in that document. I'm pasting my code here which uses a custom document called a NoteRecord:
#interface EMailPhotoAttachmentItemProvider : UIActivityItemProvider
#property (nonatomic, readwrite) NSInteger photoIndex;
#property (nonatomic, strong) NoteRecord* noteRecord;
#end
Then when I am showing the UIActivityViewController I add 1 of these custom UIActivityItemProvider objects for each attachment:
for (NSInteger i = 0; i < self.noteRecord.photoCount; i++)
{
EMailPhotoAttachmentItemProvider* photoProvider = [[EMailPhotoAttachmentItemProvider alloc]initWithPlaceholderItem:#{#"body":textToShare, #"url":url}];
photoProvider.photoIndex = i;
photoProvider.noteRecord = self.noteRecord;
[activityProviders addObject:photoProvider];
}
//Initialize the ActivityViewController
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:activityProviders applicationActivities:applicationActivities];
Then in the custom UIActivityItemProvider I check for whether I'm processing a EMAIL, and then I create a URL for the image using the document and image index provided:
#import "EMailPhotoAttachmentItemProvider.h"
#import "MiscUtilities.h"
#import "FileSystemProvider.h"
#implementation EMailPhotoAttachmentItemProvider
- (id)initWithPlaceholderItem:(id)placeholderItem
{
//Initializes and returns a provider object with the specified placeholder data
return [super initWithPlaceholderItem:placeholderItem];
}
- (id)item
{
if ([self.activityType isEqualToString:UIActivityTypeMail])
{
// Code here gets the image file from the NoteRecord at the PhotoIndex provided to
// the UIActivityItemProvider at the imageIndex, creates a URL for that image and returns it here.
// Your implementation will vary
PhotoObject *po = (PhotoObject*)[self.noteRecord photoObjects:self.photoIndex];
NSString *photoGUID = [(PhotoObject*)[self.noteRecord photoObjects:self.photoIndex]GUID];
NSData *imageFile = [[[MiscUtilities getApplicationDelegate]imageProvider]imageDataWithCaptionFromGUID:photoGUID caption:po.caption maxResolution:600];
NSString *imageFileName = [[NSArray arrayWithObjects:#"Image", [NSString stringWithFormat:#"%ld", (long)self.photoIndex], #".png", nil] componentsJoinedByString:#""];
imageFileName = [[FileSystemProvider exportPath] stringByAppendingPathComponent:imageFileName];
[imageFile writeToFile:imageFileName atomically:YES];
NSURL *url = [NSURL fileURLWithPath:imageFileName];
return url;
}
else
{
return nil;
};
}
- (id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController
{
NSString* defaultImagePath = [[FileSystemProvider imagePath]stringByAppendingPathComponent:#"default.png"];
NSURL *url = [[NSURL alloc]initWithString:defaultImagePath];
return #{#"body":#"", #"url":url};
}
#end

Archiving objects within objects in objective-c

Thankyou for reading,
PS: I am a beginner so I am not too good at this unfortunetaly, but any help would be very appreciated
So basically I want to archive a big array which contains Account objects, which they themselves contain:
1.a username in form of a NSString,
2.an encrypted password array filled with NSNumbers, and
3.a data array filled with service data objects.
The service data objects have the following:
encrypted serviceType (NSArray filled with NSNumbers) (whatever service the username and password is for)
encrypted username (NSArray filled with NSNumbers)
encrypted password (NSArray filled with NSNumbers)
Now weirdly when trying to archive and save this, I get two errors. One time it won't let me add service data objects to the data array in the Account class anymore, with the following error message (or at least they dont show up in the NSTableView I have, however it does say they exsist):
[<ServiceData 0x60000023bfa0> valueForUndefinedKey:]: this class is not key value
coding-compliant for the key service.
and two, when I try to login in the the username and password from the Account class, it retrieves the username and the first couple and last couple NSNumbers of my password correctly, but the rest of the NSNumbers for the password are in the trillions or something, so I'm wondering what is going wrong, any help would be greatly appreciated.
Here is the code for my instance variables, how I used the NSKeyedArchiver and unarchiver, and how I went about saving and loading the files. Again, please help me, I have been stuck on this for a while and this is kind-of my last resort. I have no idea what is happening!
ACCOUNT CLASS:
H file:
#interface Account : NSObject <NSCoding>
{
#private
NSString *username;
NSMutableArray *password;
NSMutableArray *accData;
}
#property NSString *username;
#property NSArray *password;
FULL M file:
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if(self)
{
username = [aDecoder decodeObjectForKey:#"username"];
password = [aDecoder decodeObjectForKey:#"password"];
accData = [aDecoder decodeObjectForKey:#"data"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:username forKey:#"username"];
[aCoder encodeObject:password forKey:#"password"];
[aCoder encodeObject:accData forKey:#"data"];
}
SERVICEDATA CLASS:
H file:
#interface ServiceData : NSObject <NSCoding>
{
#private
NSArray* serviceData;
NSArray* usernameData;
NSArray* passwordData;
}
#property NSArray* serviceData;
#property NSArray* usernameData;
#property NSArray* passwordData;
M file:
#import "Account.h"
#import "Crypt.h"
NSMutableArray *accounts;
NSInteger accountNumber = -1;
#implementation Account
#synthesize username;
#synthesize password;
- (id)initWithUsername:(NSString *)name withPassword:(NSMutableArray *)key
{
self = [super init];
if (self)
{
username = name;
password = key;
accData = [[NSMutableArray alloc]init];
}
return self;
}
/*
setters and getters
*/
-(NSString*)getUsername;
{
return username;
}
-(NSArray*)getPassword;
{
return password;
}
-(void)changePassword:(NSMutableArray*)newPassword;
{
NSInteger sizeOldPass = [password count];
NSInteger sizeNewPass = [newPassword count];
int changeXObjects = (int)(sizeNewPass - sizeOldPass);
int changeSize = abs(changeXObjects);
//adjusts size differences
if (changeXObjects < 0)
{
for(int i = 0; i < changeSize; i++)
{
[password removeLastObject];
}
}
else if (changeXObjects > 0)
{
for(int i = 0; i < changeSize; i++)
{
NSNumber *value = [NSNumber numberWithInt:0];
[password addObject:value];
}
}
//change password
NSInteger sizePass = [password count];
for (int k = 0; k < sizePass; k++)
{
[password replaceObjectAtIndex:k withObject:newPassword[k]];
}
}
-(NSMutableArray*)getAccData;
{
return accData;
}
-(void)setAccData:(NSMutableArray*)input
{
[input setArray: accData];
}
+(NSMutableArray*)getAccounts
{
return accounts;
}
+(NSInteger)getAccountNumber
{
return accountNumber;
}
+(void)setAccounts:(id)accs
{
accounts = accs;
}
+(void)setAccountNumber:(NSInteger)number
{
accountNumber = number;
}
/*
other methods
*/
+(void)addAccount:(id)acc
{
[accounts addObject:acc];
}
+(void)deleteAccount:(NSInteger)index
{
[accounts removeObjectAtIndex:index];
}
-(void)addAccData:(id)input
{
[accData addObject:input];
}
-(void)deleteAccDataAt:(NSInteger)index
{
[accData removeObjectAtIndex:index];
}
+(bool)checkPassword:(NSString*)passwordIn accountNumber:(NSInteger)index
{
NSMutableArray *passwordInputCrypt = [Crypt encrypt:passwordIn];
NSMutableArray *passwordCrypt = [accounts[index] getPassword];
NSInteger lengthPassword = [passwordInputCrypt count];
bool correctPassword = true;
if([passwordCrypt count] == [passwordInputCrypt count])
{
for(int i = 0; i < lengthPassword; i++)
{
if(passwordCrypt[i]!=passwordInputCrypt[i])
correctPassword = false;
}
}
else
{
correctPassword = false;
}
if(correctPassword == true)
{
return true;
}
return false;
}
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if(self)
{
username = [aDecoder decodeObjectForKey:#"username"];
password = [aDecoder decodeObjectForKey:#"password"];
accData = [aDecoder decodeObjectForKey:#"data"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:username forKey:#"username"];
[aCoder encodeObject:password forKey:#"password"];
[aCoder encodeObject:accData forKey:#"data"];
}
#end
LOADING FILE(filePath is given):
NSData *data = [[NSFileManager defaultManager] contentsAtPath:filePath];
if(data != nil)
{
NSArray *arrayFromData = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSMutableArray *initArray = [NSMutableArray arrayWithArray:arrayFromData];
[Account setAccounts:initArray];
}
else
{
NSMutableArray *accountsInit = [[NSMutableArray alloc] init];
[Account setAccounts:accountsInit];
}
SAVING FILE:
NSArray *accounts = [Account getAccounts];
NSString *filePath = [AppController getFilePath];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:accounts];
[data writeToFile:filePath atomically:YES];
A few things:
You should not be archiving password data to disk (even if you are encrypting it). That's what the keychain is for. Have a look at SSKeychain for a good wrapper class.
The Key-value coding error you are getting suggests you are trying to reference your serviceData array as just "service" somewhere. Check your valueForKey and setValueForKey statements.
Can you post the rest of the Account class? That method setAccounts looks like it might be relevant.
Also is there a reason you are using Keyed Archiving instead of Core Data?

Converting NSObject to NSDictionary

Hello I a class of type NSObject:
ProductDetails *details = [[ProductDetails alloc] init];
details.name = #"Soap1";
details.color = #"Red";
details.quantity = 4;
I want to pass the "details" object to a dictionary.
I did,
NSDictionary *dict = [NSDictionary dictionaryWithObject:details forKey:#"details"];
I am passing this dict to another method which performs a check on JSONSerialization:
if(![NSJSONSerialization isValidJSONObject:dict])
And I am getting a crash on this check. Am I doing anything wrong here? I know that the details I am getting is a JSON object and I am assigning it to the properties in my ProductDetails class.
Please help me. I am a noob in Objective-C.
I now tried:
NSError* error;
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:(NSData*)details options:kNilOptions error:&error];
All I need here is an easy way to convert details to NSData.
I noticed that I have an array inside my object may be thats why all the ways I tried is throwing an exception. However since this question is becoming to big, I have started an another question thread for it where I have displayed the data I am getting inside the object - https://stackoverflow.com/questions/19081104/convert-nsobject-to-nsdictionary
This may well be the easiest way to achieve it. Do import #import <objc/runtime.h> in your class file.
#import <objc/runtime.h>
ProductDetails *details = [[ProductDetails alloc] init];
details.name = #"Soap1";
details.color = #"Red";
details.quantity = 4;
NSDictionary *dict = [self dictionaryWithPropertiesOfObject: details];
NSLog(#"%#", dict);
//Add this utility method in your class.
- (NSDictionary *) dictionaryWithPropertiesOfObject:(id)obj
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
unsigned count;
objc_property_t *properties = class_copyPropertyList([obj class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
[dict setObject:[obj valueForKey:key] forKey:key];
}
free(properties);
return [NSDictionary dictionaryWithDictionary:dict];
}
NSDictionary *details = {#"name":product.name,#"color":product.color,#"quantity":#(product.quantity)};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:details
options:NSJSONWritingPrettyPrinted // Pass 0 if you don't care about the readability of the generated string
error:&error];
if (! jsonData) {
NSLog(#"Got an error: %#", error);
} else {
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}
Second part's source: Generate JSON string from NSDictionary in iOS
As mmackh said, you want to define a custom method for your ProductDetails object that will return a simple NSDictionary of values, e.g.:
#implementation ProductDetails
- (id)jsonObject
{
return #{#"name" : self.name,
#"color" : self.color,
#"quantity" : #(self.quantity)};
}
...
Let's assume that we added manufacturer property to our ProductDetails, which referenced a ManufacturerDetails class. We'd just write a jsonObject for that class, too:
#implementation ManufacturerDetails
- (id)jsonObject
{
return #{#"name" : self.name,
#"address1" : self.address1,
#"address2" : self.address2,
#"city" : self.city,
...
#"phone" : self.phone};
}
...
And then change the jsonObject for ProductDetails to employ that, e.g.:
#implementation ProductDetails
- (id)jsonObject
{
return #{#"name" : self.name,
#"color" : self.color,
#"quantity" : #(self.quantity),
#"manufacturer" : [self.manufacturer jsonObject]};
}
...
If you have potentially nested collection objects (arrays and/or dictionaries) with custom objects that you want to encode, you could write a jsonObject method for each of those, too:
#interface NSDictionary (JsonObject)
- (id)jsonObject;
#end
#implementation NSDictionary (JsonObject)
- (id)jsonObject
{
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[self enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if ([obj respondsToSelector:#selector(jsonObject)])
[dictionary setObject:[obj jsonObject] forKey:key];
else
[dictionary setObject:obj forKey:key];
}];
return [NSDictionary dictionaryWithDictionary:dictionary];
}
#end
#interface NSArray (JsonObject)
- (id)jsonObject;
#end
#implementation NSArray (JsonObject)
- (id)jsonObject
{
NSMutableArray *array = [NSMutableArray array];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj respondsToSelector:#selector(jsonObject)])
[array addObject:[obj jsonObject]];
else
[array addObject:obj];
}];
return [NSArray arrayWithArray:array];
}
#end
If you do something like that, you can now convert arrays or dictionaries of your custom objects object into something that can be used for generating JSON:
NSArray *products = #[[[Product alloc] initWithName:#"Prius" color:#"Green" quantity:3],
[[Product alloc] initWithName:#"Accord" color:#"Black" quantity:1],
[[Product alloc] initWithName:#"Civic" color:#"Blue" quantity:2]];
id productsJsonObject = [products jsonObject];
NSError *error = nil;
NSData *data = [NSJSONSerialization dataWithJSONObject:productsJsonObject options:0 error:&error];
If you're simply trying to save these objects in a file, I'd suggest NSKeyedArchiver and NSKeyedUnarchiver. But if you need to generate JSON objects for your own private classes, you can do something like the above might work.
In .h File
#import <Foundation/Foundation.h>
#interface ContactDetail : NSObject
#property (nonatomic) NSString *firstName;
#property (nonatomic) NSString *lastName;
#property (nonatomic) NSString *fullName;
#property (nonatomic) NSMutableArray *mobileNumbers;
#property (nonatomic) NSMutableArray *Emails;
#property (assign) bool Isopen;
#property (assign) bool IsChecked;
-(NSDictionary *)dictionary;
#end
in .m file
#import "ContactDetail.h"
#import <objc/runtime.h>
#implementation ContactDetail
#synthesize firstName;
#synthesize lastName;
#synthesize fullName;
#synthesize mobileNumbers;
#synthesize Emails;
#synthesize IsChecked,Isopen;
//-(NSDictionary *)dictionary {
// return [NSDictionary dictionaryWithObjectsAndKeys:self.fullName,#"fullname",self.mobileNumbers,#"mobileNumbers",self.Emails,#"emails", nil];
//}
- (NSDictionary *)dictionary {
unsigned int count = 0;
NSMutableDictionary *dictionary = [NSMutableDictionary new];
objc_property_t *properties = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
id value = [self valueForKey:key];
if (value == nil) {
// nothing todo
}
else if ([value isKindOfClass:[NSNumber class]]
|| [value isKindOfClass:[NSString class]]
|| [value isKindOfClass:[NSDictionary class]] || [value isKindOfClass:[NSMutableArray class]]) {
// TODO: extend to other types
[dictionary setObject:value forKey:key];
}
else if ([value isKindOfClass:[NSObject class]]) {
[dictionary setObject:[value dictionary] forKey:key];
}
else {
NSLog(#"Invalid type for %# (%#)", NSStringFromClass([self class]), key);
}
}
free(properties);
return dictionary;
}
#end
if any crash ,You check the property (NSMutableArray,NSString,etc ) in else if condition inside of for.
In Your Controller, in any func...
-(void)addItemViewController:(ConatctViewController *)controller didFinishEnteringItem:(NSMutableArray *)SelectedContact
{
NSLog(#"%#",SelectedContact);
NSMutableArray *myData = [[NSMutableArray alloc] init];
for (ContactDetail *cont in SelectedContact) {
[myData addObject:[cont dictionary]];
}
NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:myData options:NSJSONWritingPrettyPrinted error:&error];
if ([jsonData length] > 0 &&
error == nil){
// NSLog(#"Successfully serialized the dictionary into data = %#", jsonData);
NSString *jsonString = [[NSString alloc] initWithData:jsonData
encoding:NSUTF8StringEncoding];
NSLog(#"JSON String = %#", jsonString);
}
else if ([jsonData length] == 0 &&
error == nil){
NSLog(#"No data was returned after serialization.");
}
else if (error != nil){
NSLog(#"An error happened = %#", error);
}
}
Try this:
#import <objc/runtime.h>
+ (NSDictionary *)dictionaryWithPropertiesOfObject:(id)obj {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
unsigned count;
objc_property_t *properties = class_copyPropertyList([obj class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
[dict setObject:[obj valueForKey:key] ? [obj valueForKey:key] : #"" forKey:key];
}
free(properties);
return [NSDictionary dictionaryWithDictionary:dict];
}
The perfect way to do this is by using a library for serialization/deserialization
many libraries are available but one i like is
JagPropertyConverter
https://github.com/jagill/JAGPropertyConverter
it can convert your Custom object into NSDictionary and vice versa
even it support to convert dictionary or array or any custom object within your object (i.e Composition)
JAGPropertyConverter *converter = [[JAGPropertyConverter alloc]init];
converter.classesToConvert = [NSSet setWithObjects:[ProductDetails class], nil];
//For Object to Dictionary
NSDictionary *dictDetail = [converter convertToDictionary:detail];
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:dictDetail options:NSJSONWritingPrettyPrinted error:&error];
You can convert object (say modelObject) to dictionary at runtime with the help of objc/runtime.h class but that has certain limitations and is not recommended.
Considering MVC, mapping logic should be implemented in Model class.
#interface ModelObject : NSObject
#property (nonatomic) NSString *p1;
#property (nonatomic) NSString *p2;
-(NSDictionary *)dictionary;
#end
#import "ModelObject.h"
#implementation ModelObject
-(NSDictionary *)dictionary
{
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setValue:self.p1 forKey:#"p1"];// you can give different key name here if you want
[dict setValue:self.p2 forKey:#"p2" ];
return dict;
}
#end
Uses:
NSDictionary *modelObjDict = [modelObj dictionary];
Try using
NSDictionary *dict = [details valuesForAttributes:#[#"name", #"color"]];
And compare what the dictionary contains. Then try to convert it to JSON. And look at the JSON spec - what data types can go into a JSON encoded file?
You also can use the NSObject+APObjectMapping category which is available on GitHub: https://github.com/aperechnev/APObjectMapping
It's a quit easy. Just describe the mapping rules in your class:
#import <Foundation/Foundation.h>
#import "NSObject+APObjectMapping.h"
#interface MyCustomClass : NSObject
#property (nonatomic, strong) NSNumber * someNumber;
#property (nonatomic, strong) NSString * someString;
#end
#implementation MyCustomClass
+ (NSMutableDictionary *)objectMapping {
NSMutableDictionary * mapping = [super objectMapping];
if (mapping) {
NSDictionary * objectMapping = #{ #"someNumber": #"some_number",
#"someString": #"some_string" };
}
return mapping
}
#end
And then you can easily map your object to dictionary:
MyCustomClass * myObj = [[MyCustomClass alloc] init];
myObj.someNumber = #1;
myObj.someString = #"some string";
NSDictionary * myDict = [myObj mapToDictionary];
Also you can parse your object from dictionary:
NSDictionary * myDict = #{ #"some_number": #123,
#"some_string": #"some string" };
MyCustomClass * myObj = [[MyCustomClass alloc] initWithDictionary:myDict];
Swift
Now the swift is very popular and most of the SDK's are written in Objective C, we need to convert NSObject to NSDictionary, With the Help of #thatzprem Answer, I wrote an extension for Swift which will convert our NSObject into NSDictionary, then we can use that NSDictionary to simple Dictionary or JSON Object or other purpose. I hope so this will help out the Swift User.
extension NSObject {
func convertNSObjectToNSDictionary() -> [AnyHashable : Any]? {
var dict: [AnyHashable : Any] = [:]
var count: UInt32 = 0
let properties = class_copyPropertyList(type(of: self), UnsafeMutablePointer<UInt32>(mutating: &count)) //as? objc_property_t
for i in 0..<Int(count) {
var key: String? = nil
if let property = properties?[i] as? objc_property_t {
key = String(utf8String: property_getName(property))
}
//dict[key] = (obj as? NSObject)?.value(forKey: key ?? "")
dict[key] = (self).value(forKey: key ?? "")
}
free(properties)
return dict
}
}

Objective C assigning a dictionary to a variable and accessing it

I'm sorry to ask this question again, but I'm still stuck.
I have a city object trying to fetch weather from a weather fetcher object
#interface WeatherFetcher : NSObject {
}
#property (nonatomic, strong) NSMutableDictionary *weatherData;
- (void)fetchWeather:(NSString *)cityName;
- (void)handleNetworkErorr:(NSError *)error;
- (void)handleNetworkResponse:(NSData *)myData;
#end
This is were I assign the value to weatherData
#import "WeatherFetcher.h"
#implementation WeatherFetcher
- (void)fetchWeather:(NSString *)cityName
{
NSString *urlString = #"http://api.openweathermap.org/data/2.5/weather?q=";
urlString = [urlString stringByAppendingString:cityName];
urlString = [urlString stringByAppendingString:#",Aus"];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError)
{
[self handleNetworkErorr:connectionError];
}
else
{
[self handleNetworkResponse:data];
}
}];
}
#pragma mark - Private Failure Methods
- (void)handleNetworkErorr:(NSError *)error
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Network Error" message:#"Please try again later" delegate:nil cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
[alert show];
}
#pragma mark - Private Success Methods
- (void)handleNetworkResponse:(NSData *)myData
{
//NSMutableDictionary *data = [NSMutableDictionary dictionary];
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
// now we'll parse our data using NSJSONSerialization
id myJSON = [NSJSONSerialization JSONObjectWithData:myData options:NSJSONReadingMutableContainers error:nil];
// typecast an array and list its contents
NSDictionary *jsonArray = (NSDictionary *)myJSON;
//NSLog([jsonArray description]);
// take a look at all elements in the array
for (id element in jsonArray) {
id key = [element description];
id innerArr = [jsonArray objectForKey:key];
NSDictionary *inner = (NSDictionary *)innerArr;
if ([inner conformsToProtocol:#protocol(NSFastEnumeration)]) {
for(id ele in inner) {
if ([ele conformsToProtocol:#protocol(NSFastEnumeration)]) {
NSDictionary *innerInner = (NSDictionary *)ele;
for(id eleEle in innerInner) {
id innerInnerKey = [eleEle description];
[data setObject:[[inner valueForKey:innerInnerKey] description] forKey:[eleEle description]];
}
}
else {
id innerKey = [ele description];
[data setObject:[[inner valueForKey:innerKey] description] forKey:[ele description]];
}
}
}
else {
[data setObject:[inner description] forKey:[element description]];
}
}
self.weatherData = data;
NSLog([self.weatherData description]) **//there is data**
}
#end
However every time I call this from by city object I get nothing back at all.
#import <Foundation/Foundation.h>
#import "WeatherFetcher.h"
#interface City : NSObject {
}
#property (nonatomic, strong) NSString *cityName;
#property (nonatomic, strong) NSString *stateName;
#property (nonatomic, strong) UIImage *cityPicture;
#property (nonatomic, strong) NSString *weather;
#property (nonatomic, strong) NSMutableDictionary *weatherData;
-(NSString *)getWeather;
#end
UI calls getWeather by a button press to get the string value to be displayed on screen
#implementation City {
}
-(NSString *)getWeather {
//return self.weather;
NSString *info = #"";
WeatherFetcher *weatherFetcher = [[WeatherFetcher alloc] init];
[weatherFetcher fetchWeather:self.cityName];
self.weatherData = [weatherFetcher weatherData];
for (id element in self.weatherData) {
info = [info stringByAppendingString:[element description]];
info = [info stringByAppendingString:#"-->"];
info = [info stringByAppendingString:[self.weatherData valueForKey:[element description]]];
info = [info stringByAppendingString:#"\n"];
}
return info;
}
#end
What am I doing wrong here?
getWeather method in the city class gets called when a button is pressed and I'm trying to display this string in a text area. I don't have much experience with Objective C and this is my first app other than Hello World app.
Thank you!
Your WeatherFetcher is asynchronous (sendAsynchronousRequest:) - it sets a task to obtain the data and then returns (usually) before that data has been obtained. So when you try to access the weatherData immediately after the call to fetchWeather: it is not there yet.
You need to redesign your model to handle asynchronicity - getWeather cannot be synchronous. For example you could make fetchWeather: take a completion block to invoke when the data is available and have getWeather pass in a suitable block.