Build URL from NSDictionary - ios

In my application I need to build an url like :
https://www.thefootballapi/football/league1/player/stats
In order to be able to build the url, I need to access the objects in an NSDictionary, since NSDictionary is an unordered data set, I need to sort the objects alphabetically in order to build the correct url:
NSDictionary
{
category = "football";
league = " League1 " ;
section = player;
"sub_category" = "stats";
}
I have tried doing this by writing this block of code:
Accessing the objects:
NSArray *keyyy0= [self.redirect allKeys];
id aaKey0 = [keyyy0 objectAtIndex:0];
id aanObject0 = [self.redirect objectForKey:aaKey0];
NSArray *keys = [self.redirect allKeys];
id aKey = [keys objectAtIndex:1];
id anObject = [self.redirect objectForKey:aKey];
NSArray *keyyy = [self.redirect allKeys];
id aaKey = [keyyy objectAtIndex:2];
id aanObject = [self.redirect objectForKey:aaKey];
and building the full url like this :
NSString *fullurl = [NSString stringWithFormat:#"%#%#%#%#", newurl,anObject,aanObject,aanObject3 ];
This method works fine for now, however I was wondering if this is the correct way of doing this ? is there a better way of implementing this ?
For example as it's mentioned here : Joe's answer ,NSURLQueryItem is used to access objects from dictionaries and build queries from it, however when I used NSURLQueryItem the full url was built with ? and = signs.
Are there any other methods that can be used to just get all of the objects in an NSDictionary ?

When accessing values from an NSDictionary there's no guarantee what type it will be. With full type-checking, a safer and more readable way of creating your URL might be something like:
NSDictionary *redirect = #{#"category" : #"football",
#"league" : #" League1 ",
#"section" : #"player",
#"sub_category" : #"stats"};
id category = redirect[#"category"];
id league = redirect[#"league"];
id section = redirect[#"section"];
id subCategory = redirect[#"sub_category"];
if ([category isKindOfClass:[NSString class]] &&
[league isKindOfClass:[NSString class]] &&
[section isKindOfClass:[NSString class]] &&
[subCategory isKindOfClass:[NSString class]])
{
NSString *urlString = [NSString stringWithFormat:#"https://www.thefootballapi/%#/%#/%#/%#",
[((NSString*)category).lowercaseString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]],
[((NSString*)league).lowercaseString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]],
[((NSString*)section).lowercaseString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]],
[((NSString*)subCategory).lowercaseString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
NSLog(#"%#", urlString); // https://www.thefootballapi/football/league1/player/stats
}
This also ensures the URL is generated as you wanted (lowercase "league1" without leading/trailing whitespace) given your input JSON.

Try this code.
//Your Dictionary
NSMutableDictionary *dict = [NSMutableDictionary new];
[dict setValue:#"football" forKey:#"category"];
[dict setValue:#"League1" forKey:#"league"];
[dict setValue:#"player" forKey:#"section"];
[dict setValue:#"stats" forKey:#"sub_category"];
// Get desired URL like this
NSArray *arr = [[dict allValues] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
NSString *strURL = [NSString stringWithFormat:#"https://www.thefootballapi/%#/%#/%#/%#", [arr objectAtIndex:0], [arr objectAtIndex:1], [arr objectAtIndex:2], [arr objectAtIndex:3]];
NSLog(#"%#", strURL);
It will return ULR same as you want : https://www.thefootballapi/football/League1/player/stats

Related

Save json object in sqlite like text and retransform to dictionary on read

I want to save a Json Object in a field (text) sqlite and then read it again with a select and retransform to NSDictionary or NSMutableArray to parse the key/values
This is how i save actually in the sqlite DB
As you see, is a song object from the iTunes api. I want to read that object and parse it.
This is how i make the select query and save it in a NSDictionary while i filling and NSMutableArary
globals.arrayMyPlaylists = [[NSMutableArray alloc] init];
// Form the query.
NSString *query = #"select * from myplaylists";
// Get the results.
NSArray *listas = [[NSArray alloc] initWithArray:[self.dbManager loadDataFromDB:query]];
for (int i = 0; i < listas.count; i++) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
NSLog(#"lista %d %# %#", i, listas[i][0], listas[i][1]);
[dictionary setObject:listas[i][0] forKey:#"id"];
[dictionary setObject:listas[i][1] forKey:#"nombre"];
NSString *query2 = [NSString stringWithFormat:#"select cancion from canciones where idplaylist = %#", listas[i][0]];
NSArray *canciones = [[NSArray alloc] initWithArray:[self.dbManager loadDataFromDB:query2]];
[dictionary setObject:canciones forKey:#"canciones"];
[globals.arrayMyPlaylists addObject:dictionary];
dictionary = nil;
}
When i try to read it in the cellForRowAtIndexPath method
NSArray *canciones = [[NSArray alloc] initWithArray:[[globals.arrayMyPlaylists objectAtIndex:indexPath.row] valueForKey:#"canciones"]];
and try to get the value for the key artworkUrl100
[cell.tapa1 sd_setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#", [[canciones objectAtIndex:i] valueForKey:#"artworkUrl100"]]] placeholderImage:nil];
i get the error
Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<__NSCFString 0x7ff575810600> valueForUndefinedKey:]: this class is not key value coding-compliant for the key artworkUrl100.'
i understand i'm messing up in some way with dictionarys/nsmutablearrays. I need some help. Thanks!
EDIT: this is how i save the data in the DB
NSString *query = [NSString stringWithFormat:#"insert into canciones (idplaylist, cancion) values ('%#', '%#')", [[globals.arrayMyPlaylists objectAtIndex:indexPath.row] valueForKey:#"id"], self.elementoSeleccionado];
[self.dbManager executeQuery:query];
self.elementoSeleccionado is the NSMutableArray with the "cancion" object and it's saved like it's shows the first image.
EDIT 2: this is what i get trying schmidt9's answer
EDIT 3: OK, i have the json string escaped. How i have to parse now?
You should parse your output first with NSJSONSerialization:
NSString *query2 = [NSString stringWithFormat:#"select cancion from canciones where idplaylist = %#", listas[i][0]];
NSArray *canciones = [[NSArray alloc] initWithArray:[self.dbManager loadDataFromDB:query2]];
// I suppose your array canciones should contain only one song object ...
NSError *error = nil;
id object = [NSJSONSerialization JSONObjectWithData:canciones[0] options:0 error:&error];
if(error) {
// handle error ...
}
if([object isKindOfClass:[NSDictionary class]])
{
NSDictionary *result = object;
[dictionary setObject:result forKey:#"canciones"];
}
Edit
Saving to database
2 options:
- if you get a prepared json string, save it directly to DB, but before you should escape quotes. See eg. here
- if you have NSDictionary:
- (NSString*)JSONStringWithDictionary:(NSDictionary*)dictionary prettyPrinted:(BOOL)prettyPrinted
{
NSJSONWritingOptions options = (prettyPrinted) ? NSJSONWritingPrettyPrinted : 0;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dictionary options:options error:nil];
return [[NSString alloc] initWithBytes:[jsonData bytes] length:[jsonData length] encoding:NSUTF8StringEncoding];
}
After that you should escape the string too.

How to detect if a dictionary is empty or null

I am receiving a JSON string that I need to iterate to retrieve some objects values.
This is the structure
-meta
-objects
|_cabdriver
|_employee
|client
There are objects under the objects tree and there are also child nodes, like cabdriver and client. The child node cabdriver has also another child node called employee.
This is the way I am iterating it:
NSArray *messageArray = [json objectForKey:#"objects"];
historialServicios = [[NSMutableArray alloc]init];
// Parse and loop through the JSON
for (dictionary in messageArray) {
//datos de nivel objects
NSString * date = [dictionary objectForKey:#"date"];
NSString * origin = [dictionary objectForKey:#"origin"];
NSString * destiny = [dictionary objectForKey:#"destiny"];
NSString * rate = [dictionary objectForKey:#"service_rate"];
NSString * state = [dictionary objectForKey:#"state"];
NSString * time_service = [dictionary objectForKey:#"time_service"];
NSString * id_service = [dictionary objectForKey:#"id"];
//datos de nivel cliente
NSDictionary *level2Dict = [dictionary objectForKey:#"client"];
NSString *client_id = [level2Dict objectForKey:#"id"];
//datos de nivel cabdriver
NSDictionary *cabdriverLevelDict=[dictionary objectForKey:#"cabdriver"];
//datos de nivel employee
NSDictionary *employeeLevelDict = [cabdriverLevelDict objectForKey:#"employee"];
//datos del employee
NSString *driverName = [employeeLevelDict objectForKey:#"name"];
NSString *driverLastname = [employeeLevelDict objectForKey:#"lastname"];
NSString *driverPhone = [employeeLevelDict objectForKey:#"phone"];
NSString *driverId = [employeeLevelDict objectForKey:#"id"];
[historialServicios addObject:#{
#"time_service": time_service,
#"id_service": id_service,
#"rate": rate,
#"destiny": destiny,
#"state": state,
#"origin": origin,
#"client_id":client_id,
#"date": date,
#"driverName":driverName,
#"driverLastname": driverLastname,
#"driverPhone": driverPhone,
#"driverId": driverId
}];
NSLog(#"DESPUES DE ANADIR OBJETOS");
NSLog(#"OBJETO ANADIDO==>TIME SERVICE = %#, ID SERVICE=%#, SERVICE RATE=%#,SERVICE DATE=%#,DESTINY=%#, STATE =%#,CLIENT ID=%#, ORIGIN=%#,DRIVER NAME=%#, DRIVER LASTNAME=%#,DRIVER PHONE=%#, DRIVER ID=%#",time_service,id_service,rate,date,destiny,state,client_id,origin,driverName,driverLastname,driverPhone,driverId);
//insertamos objetos en diccionario historialServicios
}
Everything works fine if the object has all nodes but some times, the node cabdriver is empty and doesn't have the employee child node. If it is the case I get an exception is thrown and the app crashes.
How can I determined if the node employee doesn't exist and avoid to get the exception?
Thank you.
You could declare a category to deal with the [NSNull null] values that are injected into your json.
#interface NSDictionary (NilNull)
- (id)optionalObjectForKey:(id)key;
- (id)optionalObjectForKey:(id)key defaultValue:(id)defaultValue;
#end
#implementation NSDictionary (NilNull)
- (id)optionalObjectForKey:(id)key {
return [self optionalObjectForKey:key defaultValue:nil];
]
- (id)optionalObjectForKey:(id)key defaultValue:(id)defaultValue {
id obj = [self objectForKey:key];
return (obj == [NSNull null] || !obj) ? defaultValue : obj;
}
#end
Then use that instead:
NSDictionary *cabdriverLevelDict = [dictionary optionalObjectForKey:#"cabdriver"];
NSDictionary *employeeLevelDict = [cabdriverLevelDict optionalObjectForKey:#"employee"];
You haven't posted the contents of your exception, but from the looks of it, it's probably related to trying to add nil values to your new dictionary.
Then use a default value of [NSNull null] for all your data lookups that produce objects with which you will construct your final dictionary. The full lookup source will now be like this:
NSString * date = [dictionary optionalObjectForKey:#"date" defaultValue:[NSNull null]];
NSString * origin = [dictionary optionalObjectForKey:#"origin" defaultValue:[NSNull null]];
NSString * destiny = [dictionary optionalObjectForKey:#"destiny" defaultValue:[NSNull null]];
NSString * rate = [dictionary optionalObjectForKey:#"service_rate" defaultValue:[NSNull null]];
NSString * state = [dictionary optionalObjectForKey:#"state" defaultValue:[NSNull null]];
NSString * time_service = [dictionary optionalObjectForKey:#"time_service" defaultValue:[NSNull null]];
NSString * id_service = [dictionary optionalObjectForKey:#"id" defaultValue:[NSNull null]];
//datos de nivel cliente
NSDictionary *level2Dict = [dictionary optionalObjectForKey:#"client" defaultValue:[NSDictionary dictionary]];
NSString *client_id = [level2Dict optionalObjectForKey:#"id" defaultValue:[NSNull null]];
//datos de nivel cabdriver
NSDictionary *cabdriverLevelDict=[dictionary optionalObjectForKey:#"cabdriver" defaultValue:[NSDictionary dictionary]];
//datos de nivel employee
NSDictionary *employeeLevelDict = [cabdriverLevelDict optionalObjectForKey:#"employee" defaultValue:[NSDictionary dictionary]];
//datos del employee
NSString *driverName = [employeeLevelDict optionalObjectForKey:#"name" defaultValue:[NSNull null]];
NSString *driverLastname = [employeeLevelDict optionalObjectForKey:#"lastname" defaultValue:[NSNull null]];
NSString *driverPhone = [employeeLevelDict optionalObjectForKey:#"phone" defaultValue:[NSNull null]];
NSString *driverId = [employeeLevelDict optionalObjectForKey:#"id" defaultValue:[NSNull null]];
Try this here:
if( cabdriverLevelDict.allkeys.count ){
// Do something with the dict
} else {
// dict is empty
}
Basically, you need to check every single result that you get. If you don't do that, your app is open to attacks, and one attack might allow a hacker into the user's device and cause unlimited damage. Where you expect a dictionary, you might get nil, you might get a null, you might get a number, or a string, just anything. It's quite simple.
NSDictionary* dict = ...;
if (! [dict isKindOfClass:[NSDictionary class]]) dict = nil;
In Objective-C, nil objects are quite safe. You can use objectForKey [#"employee"], for example, and all that will happen is that you get nil as the result. And you could have received nil anyway.
There is no point checking for [NSNull null] only, because any other result that the server gave you will crash your app just the same. Just check for what you actually expect. Throwing away incorrect data is fine, after all the JSON deserialiser will throw away everything if just a single byte of data is wrong.
Sometimes you need to do a bit more care because servers misbehave and you have to cope with it. For example, a server supposed to return an array of dictionaries might give you just a dictionary if there is only one, so you would check for example
NSArray* arrayOfDicts = ...;
if ([arrayOfDicts isKindOfClass:[NSDictionary class]] arrayOfDicts = #[arrayOfDicts];
else if (! [arrayOfDicts isKindOfClass:[NSArray class]] arrayOfDicts = nil;
As others have pointed out, if any of the objects passed into the dictionary are nil, that will throw an exception that crashes your app. By doing the following:
[historialServicios addObject:#{
#"time_service": time_service,
#"id_service": id_service,
#"rate": rate,
#"destiny": destiny,
#"state": state,
#"origin": origin,
#"client_id":client_id,
#"date": date,
#"driverName":driverName,
#"driverLastname": driverLastname,
#"driverPhone": driverPhone,
#"driverId": driverId
}];
You're depending that all these objects (eg time_service, id_service, etc) are not nil. As you've pointed out, they can be nil, so you need to have a means of checking for each object you do. I would recommend using an NSMutableDictionary, making a category method that only adds the key/value pair if they are both not nil:
#implementation NSMutableDictionary (Util)
-(void)setObjectOrRemoveIfNil:(id)anObject forKey:(id<NSCopying>)aKey
{
if (anObject == nil)
{
[self removeObjectForKey:aKey];
}
else
{
[self setObject:anObject forKey:aKey];
}
}
#end
And then put together your dictionary like so:
NSMutableDictionary* values = [NSMutableDictionary dictionary];
[values setObjectOrRemoveIfNil:time_service forKey:#"time_service"];
[values setObjectOrRemoveIfNil:id_service forKey:#"id_service"];
//Keep going with the rest of your values.
Finally we use that dictionary like you did already:
[historialServicios addObject:values];
check the count for the dictionary
if ([cabdriverLevelDict count] == 0) {
NSLog("empty");
}
else{
// Do your stuff !!
}
if (![cabdriverLevelDict isKindOfClass:[NSNull class]] ){
//do something
}
try this
You can try
NSDictionary *employeeLevelDict = [cabdriverLevelDict objectForKey:#"employee"];
if (employeeLevelDict.count != 0)
{
// do something if dict is not empty
}
else
{
}];

Put multiple arrays in Dictionary

I am parsing a CSV file multiple times with for loop, here I need to store these arrays one by one dictionary. There are very less questions in stack about adding NSArray to NSDictionary. I am parsing CSV with below code but I strucked at storing in NSDictionary, The program is terminating and showing warning at assigning string to dictionary
for (i=0; i<=57; i++) {
NSString *keysString = [csvArray objectAtIndex:i];
NSArray *keysArray = [keysString componentsSeparatedByString:#","];
NSLog(#"Serail No %d %#",i,keysArray);
NSString *string = [NSString stringWithFormat:#"%d", i];
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObjects: keysArray forKeys: string];
}
NSMutableDictionary *dict = [[NSMutableDictionary alloc]init];
for (i=0; i<=57; i++) {
NSString *keysString = [csvArray objectAtIndex:i];
NSArray *keysArray = [keysString componentsSeparatedByString:#","];
NSString *key = [NSString stringWithFormat:#"serial%d",i];
[dict setObject:keysArray forKey:key];
}
To get back data from dictionary,
NSArray *array = [dict valueForKey:#"serial24"];//to get array 24.
If I understand you correctly, you want to add the arrays to a dictionary, with the key being the string value of integer i ? What you need to do is allocate the dictionary outside your loop -
NSMutableDictionary *dict=[NSMutableDictionary new];
for (i=0; i<=57; i++) {
NSString *keysString = [csvArray objectAtIndex:i];
NSArray *keysArray = [keysString componentsSeparatedByString:#","];
NSLog(#"Serial No %d %#",i,keysArray);
NSString *string = [NSString stringWithFormat:#"%d", i];
dict[string]=keysArray;
}
I am not sure why you would want to do this, because this is basically an array. You could simply do -
NSMutableArray *outputArray=[NSMutableArray new];
for (NSString *keysString in csvArray) {
NSArray *keysArray = [keysString componentsSeparatedByString:#","];
[outputArray addObject:keysArray];
}

uilabel text with new line only when required

this is what i am using:
it works if address, city, zip.....length >0.(these field may grow in future)
self.addressInfoLbl.text = [NSString stringWithFormat:#"%#\n%#\n%#\n%#\n%#", address, city, zip, state, country];(numberofline == 0)
but if any of them length =0 then i got unnecessary new line.
i am working on manually preparing(appending \n).if there are more and more fields then doing it manuallt is really hard.
Is there any other proper way.Am i doing it right.
Thanks
Try following code. It creates array of your strings, removes empty strings and then concatenates them with componentsJoinedByString :
NSArray *strings = #[address, city, zip, state, country];
strings = [strings filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"length > 0"]];
NSString *resultString = [strings componentsJoinedByString:#"\n"];
You can join an array of objects into a string with a separator:
NSArray *props = [NSArray arrayWithObjects: address, city, state, nil];
NSString *joinedString = [props componentsJoinedByString:#"\n"];
and you will get:
"6th avenue\nAtlanta\nGeorgia"
If you don't know the amount of properties, use NSMutableArray instead of NSArray and add your properties at runtime.
Try this once,
NSMutableString *joinedString=[NSMutableString string];
NSArray *arr = [NSArray arrayWithObjects: address, city, state, nil];
for(NSString *str in arr)
{
if([str length]>0) [joinedString appendFormat:#"\n%#", str];
}
NSString *resultString=[joinedString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSLog(#"%#", resultString);
Lbl.numberOfLines=0;
Lbl.lineBreakMode=NSLineBreakByCharWrapping;
try this code, it not optimal but it can resolve youy issue
NSArray *arr = [NSArray arrayWithObjects: #"address", #"", #"state", nil];
NSString *addressInfo = #"";
for (NSString *str in arr) {
if (str.length > 0) {
addressInfo = [addressInfo stringByAppendingString:[NSString stringWithFormat:#"\n%#", str]];
}
}
if (addressInfo && ![#"" isEqualToString:addressInfo])
addressInfo = [addressInfo substringFromIndex:1];
NSLog(#"address Info = %#", addressInfo);

Accessing NSMutableDictionary inside NSMutableArray

I have an NSMutableArray of NSMutableDictionary (it's a plist). Each dictionary in the array has 2 keys: 'id' and 'lu'.
How can I find the index in NSMutableArray of NSMutableDictionary where, for example, id = '47653'?
I tried to do it but it's not working:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
NSString *path = [basePath stringByAppendingPathComponent:#"id.plist"];
NSMutableArray *mut_array = [[NSArray arrayWithContentsOfFile:path] mutableCopy];
NSMutableDictionary *mut_dico = [[NSMutableDictionary alloc] init];
NSString *egalIDPlistData = [contentDictio objectForKey:#"id"];
for(mut_dico in mut_array) {
if([[mut_dico objectForKey:#"id"] isEqualToString:#"47653"]) {
NSLog(#"Test");
}
}
NSMutableArray *mut_array = [[NSArray arrayWithContentsOfFile:path] mutableCopy]; will create a mutable array of immutable dictionaries, not mutable dictionaries. mutableCopy is not a deep copy.
Regardless of mutability, the following will do what you want:
NSUInteger indexOfDictionary = [[array valueForKey:#"id"] indexOfObject:#"49711"];
if(indexOfDictionary == NSNotFound)
{
NSLog(#"no matching objects found!");
return;
}
NSDictionary *fullDictionary = [array objectAtIndex:indexOfObject];
The valueForKey: uses the fact that a key-value coding call on an array returns an array of the results of that call on each object in the array, in the same order, and the fact that NSDictionary will respond similarly in this situation to both valueForKey: and objectForKey:
As for the issue of whether what you're really dealing with is strings or numbers, try using just #49711 in place of #"49711" and if that works instead you've got numbers.
In your log id is 49711 & in your if condition it is 47653. Your condition will not satisfy until there is dictionary in your array with id = 47653
The objects in the loaded plist are not mutable.
Try:
for(NSDictionary * dict in mut_array)
{
if([[dict objectForKey:#"id"] isEqualToString:#"49711"]) {
NSLog(#"Test");
}
}

Resources