NSDictionary writeToFile fails while objects are valid, permission is 0k - objective-c

Why NSDictionary cannot be written?? I have checked the content of the dictionary: all the instances are of NSString and NSNumber. I checked permissions: a text file with the same name at the same path is written well. Of course, my dictionary is not empty.
NSString *file = ...
NSDictionary *dict = ...
// check dictionary keys
BOOL wrong = NO;
for (id num in [dict allKeys]) {
if (![num isKindOfClass:[NSNumber class]]) {
wrong = YES;
break;
}
}
if (wrong) {
NSLog(#"First");
}
// check dictionary values
wrong = NO;
for (id num in [dict allValues]) {
if (![num isKindOfClass:[NSString class]]) {
wrong = YES;
break;
}
}
if (wrong) {
NSLog(#"Second");
}
if (![dict writeToFile:file atomically:YES]) {
// 0k, let's try to create a text file
NSLog(#"Names writing error!");
[#"Something here... .. ." writeToFile:file atomically:YES encoding:NSUTF8StringEncoding error:nil];
}
Output: "Names writing error!"
Text file is created successfully.

Writing out a dictionary creates a property list, and according to the documentation all keys in a property list must be strings.
... and although NSDictionary and CFDictionary objects allow their keys to
be objects of any type, if the keys are not string objects, the
collections are not property-list objects.
NSNumber objects as keys are not supported.

As #vadian points out, you cannot write plist with numeric keys. But you can use NSKeyedArchiver:
NSURL *documents = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:false error:nil];
NSURL *fileURL = [documents URLByAppendingPathComponent:#"test.plist"];
// this will not work
NSDictionary *dictionary = #{#1: #"foo", #2: #"bar"};
BOOL success = [dictionary writeToFile:fileURL.path atomically:true];
NSLog(#"plist %#", success ? #"success" : #"failure");
// this will
fileURL = [documents URLByAppendingPathComponent:#"test.bplist"];
success = [NSKeyedArchiver archiveRootObject:dictionary toFile:fileURL.path];
NSLog(#"archive %#", success ? #"success" : #"failure");
And you can read it back with NSKeyedUnarchiver:
// to read it back
NSDictionary *dictionary2 = [NSKeyedUnarchiver unarchiveObjectWithFile:fileURL.path];
NSLog(#"dictionary2 = %#", dictionary2);
Note, you can do this with any class that conforms (and properly implements) NSCoding. Fortunately, NSDictionary conforms already. You have to make sure that any objects inside the dictionary, also conform (both NSString and NSNumber do). If you had a custom object in your dictionary, you'd have to make it properly conform yourself.
This is all described in the Archives and Serializations Programming Guide.

Related

Filtering Parsed JSON in Objective-C

I'm trying to take out the "lasttradeprice" in https://www.allcrypt.com/api.php?method=singlemarketdata&marketid=672 but I can't seem to figure out how to grab the "lasttradeprice" piece.
How would I 'filter' the "price" out? None of the other information is relevant.
Current Code:
NSURL * url=[NSURL URLWithString:#"https://www.allcrypt.com/api.php?method=singlemarketdata&marketid=672"]; // pass your URL Here.
NSData * data=[NSData dataWithContentsOfURL:url];
NSError * error;
NSMutableDictionary * json = [NSJSONSerialization JSONObjectWithData:data options: NSJSONReadingMutableContainers error: &error];
NSLog(#"%#",json);
NSMutableArray * referanceArray=[[NSMutableArray alloc]init];
NSMutableArray * periodArray=[[NSMutableArray alloc]init];
NSArray * responseArr = json[#"lasttradeprice"];
for(NSDictionary * dict in responseArr)
{
[referanceArray addObject:[dict valueForKey:#"lasttradeprice"]];
[periodArray addObject:[dict valueForKey:#"lasttradeprice"]];
}
NSLog(#"%#",referanceArray);
NSLog(#"%#",periodArray);
NOTE: Keep in mind I've never worked with JSON before so please keep your answers dumbed down a tad.
Key value coding provides an easy way to dig through that data. Use the key path for the values you want. For example, it looks like you could get the array of recent trades using the path "return.markets.OMC.recenttrades" like this (assuming your code to get the json dictionary):
NSArray *trades = [json valueForKeyPath:#"return.markets.OMC.recenttrades"];
That's a lot more concise than having to dig down one level at a time.
The value returned for a given key by an array is the array of values returned by the array's members for that key. In other words, you can do this:
NSArray *recentprices = [trades valueForKey:#"price"];
And since that's just the next step in the key path, you can combine the two operations above into one:
NSArray *recentprices = [json valueforKeyPath:#"return.markets.OMC.recenttrades.price"];
The only down side here is that there's no real error checking -- either the data matches your expectations and you get back your array of prices, or it doesn't match at some level and you get nil. That's fine in some cases, not so much in others.
Putting that together with the relevant part of your code, we get:
NSURL *url = [NSURL URLWithString:#"https://www.allcrypt.com/api.php?method=singlemarketdata&marketid=672"];
NSData *data = [NSData dataWithContentsOfURL:url];
NSError *error = nil;
NSMutableDictionary *json = [NSJSONSerialization JSONObjectWithData:data options: NSJSONReadingMutableContainers error:&error];
NSArray *recentprices = [json valueforKeyPath:#"return.markets.OMC.recenttrades.price"];
Update: I just noticed that you want the "lasttradeprice", not the array of prices. Given that, the key path to use is simply #"return.markets.OMC.lasttradeprice", and the value you'll get back will be a string. So replace the last line above with:
NSString *lastTradePrice = [json valueforKeyPath:#"return.markets.OMC.lasttradeprice"];
The value you want is buried a few dictionaries deep. One general idea might be to dig recursively, something like this:
- (BOOL)isCollection:(id)object {
return [object isKindOfClass:[NSArray self]] || [object isKindOfClass:[NSDictionary self]];
}
- (void)valuesForDeepKey:(id)key in:(id)collection results:(NSMutableArray *)results {
if ([collection isKindOfClass:[NSDictionary self]]) {
NSDictionary *dictionary = (NSDictionary *)collection;
if (dictionary[key]) [results addObject:dictionary[key]];
for (id deeperKey in [dictionary allKeys]) {
if ([self isCollection:dictionary[deeperKey]]) {
[self valuesForDeepKey:key in:dictionary[deeperKey] results:results];
}
}
} else if ([collection isKindOfClass:[NSArray self]]) {
NSArray *array = (NSArray *)collection;
for (id object in array) {
if ([self isCollection:object]) {
[self valuesForDeepKey:key in:object results:results];
}
}
}
}
Then call it like this:
NSMutableArray *a = [NSMutableArray array];
[self valuesForDeepKey:#"lasttradeprice" in:json results:a];
NSLog(#"%#", a);

RestKit: mapping BOOL and integer values

I'm evaluating RestKit to use in my project. I've created a simple app that loads some JSON and maps it into Objective-C objects. I'm having a problem correctly mapping a JSON object that has numeric and logical fields. E.g.
{
"integerValue":"5",
"booleanValue":"YES",
}
I want these to map to the following properties in my data object:
#property int integerValue;
#property BOOL booleanValue;
It didn't work out of the box, so I've created a value transformer for that:
[_activityMapping setValueTransformer:[RKBlockValueTransformer valueTransformerWithValidationBlock:^BOOL(__unsafe_unretained Class inputValueClass, __unsafe_unretained Class outputValueClass) {
if([inputValueClass isSubclassOfClass:[NSString class]] && [outputValueClass isSubclassOfClass:[NSNumber class]]) {
return YES;
}
else {
return NO;
}
} transformationBlock:^BOOL(id inputValue, __autoreleasing id *outputValue, __unsafe_unretained Class outputClass, NSError *__autoreleasing *error) {
if([[inputValue class] isSubclassOfClass:[NSString class]] && [outputClass isSubclassOfClass:[NSNumber class]]) {
NSString *inputString = (NSString *)inputValue;
if([inputString isEqualToString:#"YES"] || [inputString isEqualToString:#"NO"]) {
*outputValue = [NSNumber numberWithBool:[inputString boolValue]];
}
else {
*outputValue = [NSNumber numberWithInt:[inputString intValue]];
}
}
else {
*outputValue = [inputValue copy];
}
return YES;
}]];
This code works, but looks ugly. Note how I have to check the input value to see if it's a boolean or an integer. Any suggestions on an elegant solution to this problem?
Please note that I'm using RestKit. I do know about NSJSONSerialization and know how to parse JSON in code. If you suggest a non-RestKit solution, please explain why do you not recommend using RestKit.
The issue is not occurring at the RestKit level but at the JSON level itself.
According to the JSON spec Boolean values should be represented with true/false not YES/NO. If you update your JSON to be semantically correct then RestKit should do the right thing.
Ok. So according to my understanding of your answer, your main problem lies in mapping the data in the JSON object to their very own designated variables.
So, I'd recommend using the conventional NSJSONSerialization approach.
So, first up. You need to store your JSON object in an NSData object. Now, you're most likely downloading the data from a simple URL. So, this is what you'd do :
//This part is just to download the data. If you're using another method - that's fine. Just make sure that the download is in NSData format
NSURL *url = [[NSURL alloc] initWithString : #"YOUR_URL_HERE"];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL : url];
NSData *jsonData = [NSURLConnection sendSynchronousRequest:request
returningResponse:nil
error:nil];
Now, you need to map those to the NSDictionary... Here's how :
//This is the actual NSJSONSerialization part.
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:jsonData
options:NSJSONReadingMutableLeaves
error:nil];
Now, just map the values to your designated properties.
_integerValue = (int)[jsonDict objectForKey:#"integerValue"];
_booleanValue = (BOOL)[jsonDict objectForKey:#"booleanValue"];

Looping through nested NSDictionary

I know there is a somewhat simple solution here, I can't quite wrap my head around it though.
I have a user generated .plist file that can have a (reasonable) infinite amount of nested trees in it. What I'm wanting to do is create a folder structure based on how the plist is nested.
Since I do not know how deep the main dictionary goes I cant figure out how to loop through it and tell it to create a directory then dive into that and create directories within it.
I know how to create folders its iterating through the entire list is what is throwing me off. I'm pretty sure I need a separate recursive method just am not sure where to begin. Key "Children" and "Name" are what I'm using to create the list. Any help would be great.
I'd start with defining a path to the directory where you want to create the directory structure. Then call the function to build the directory structure.
NSString *pathToDirectory = #"./";
[self buildDirectoriesAtPath: pathToDirectory fromDictionary: dictionary];
The function -buildDirectoriesAtPath:fromDictionary: is recursive and would look something like this:
- (void) buildDirectoriesAtPath: (NSString *) filepath fromDictionary: (NSDictionary *) dictionary {
for (NSString *key in dictionary.allKeys) {
if ([key isEqualToString: #"Children"]) {
NSArray *children = [dictionary objectForKey: key];
for (id object in children) {
if ([object isKindOfClass: [NSDictionary class]]) {
NSDictionary *directoryInfo = (NSDictionary *) object;
NSString *directoryName = [directoryInfo objectForKey: #"Name"];
NSString *directoryPath = [NSString stringWithFormat: #"%#%#", filepath, directoryName];
// Create directory
NSLog(#"Creating directory: %#", directoryPath);
// Create subdirectories
NSArray *subdirectories = [directoryInfo objectForKey: #"Children"];
for (id directory in subdirectories) {
if ([directory isKindOfClass: [NSString class]]) {
NSString *subdirectoryName = (NSString *) directory;
NSString *subdirectoryPath = [NSString stringWithFormat: #"%#/%#", directoryPath, subdirectoryName];
// Create directory
NSLog(#"Creating directory: %#", subdirectoryPath);
}
else if ([directory isKindOfClass: [NSDictionary class]]) {
NSDictionary *subdirectory = (NSDictionary *) directory;
NSString *subdirectoryName = [subdirectory objectForKey: #"Name"];
NSString *subdirectoryPath = [NSString stringWithFormat: #"%#/%#/", directoryPath, subdirectoryName];
[self buildDirectoriesAtPath: subdirectoryPath fromDictionary: (NSDictionary *) directory];
}
}
}
else if ([object isKindOfClass: [NSString class]]) {
NSString *directoryName = (NSString *) object;
NSString *directoryPath = [NSString stringWithFormat: #"%#%#", filepath, directoryName];
// Create directory
NSLog(#"Creating directory: %#", directoryPath);
}
}
}
}
}
If there's anything you'd like clarified in this, don't hesitate to ask. :)
May be this function will help you,
void recursiveCall (NSDictionary *dictionary) {
NSArray *_keys = [dictionary allKeys];
for (NSString *_key in _keys) {
id obj = [dictionary valueForKey:_key];
NSLog(#"Create folder of %#",_key);
if ([obj isKindOfClass:[NSArray class]]) {
for (id _obj in obj) {
if ([_obj isKindOfClass:[NSDictionary class]]) {
recursiveCall(_obj);
}
else {
NSLog(#"Create folder of %#",_obj);
}
}
}
}
}

Error: Mutating method sent to immutable object for NSMutableArray from JSON file

This seems to be a fairly common problem, but the solutions that I have looked at do not solve the error. I am trying to read an NSMutableArray from a JSON file. Many of the suggestions I have seen involve using mutableCopy or [NSMutableArray arrayWithArray:] but both of these solutions do not fix the problem when using the call replaceObjectAtIndex:withObject: seen below. Please let me know if you have any advice on how to solve this problem.
EDIT: I would also like to add that the inventory list is an NSMutableArray of NSMutableArray objects.
The exact error reads:
Terminating app due to uncaught exception 'NSInternalInconsistencyException',
reason: '-[__NSCFArray replaceObjectAtIndex:withObject:]:
mutating method sent to immutable object'
I have the property defined as follows at the top of my implementation file:
NSMutableArray *inventoryData;
I am trying to read it from a JSON file as follows:
- (void)readJSON
{
//Code to get dictionary full of saves from JSON file (overworld.json) - includes the file path on the ipad as well as
//the dictionary itself
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *localPath = [[NSString alloc] initWithString:[documentsDirectory stringByAppendingPathComponent:#"savedPaintGameData.json"]];
NSString *filePath = [localPath mutableCopy];
NSError *e = nil;
// Read data from file saved previously - read the raw data from the path, then parse it into a dictionary using JSONObjectWithData
NSData *RawJSON = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:&e];
if (RawJSON == nil) {
[self saveGameInitialize];
} else {
NSMutableDictionary *localDictionary = [[NSMutableDictionary alloc] initWithDictionary:[NSJSONSerialization JSONObjectWithData:RawJSON options:NSJSONReadingAllowFragments error:&e]];
NSMutableDictionary *savedDataDictionary = [localDictionary mutableCopy];
//inventoryData = [[savedDataDictionary objectForKey:#"inventory"] mutableCopy];
inventoryData = [NSMutableArray arrayWithArray:[savedDataDictionary objectForKey:#"inventory"]];
}
}
I am then trying to replace an object at the given index of the NSMutableArray as seen here:
- (void)setInventoryData: (NSString *) colorKey: (int) change
{
// Check if inventory already contains the paint and change the amount
bool foundPaint = false;
int newAmount = 100; // Magic number prevents crashing # removal check
for (int i = 0; i < [inventoryData count]; i++) {
NSMutableArray *object = [inventoryData objectAtIndex:i];
if ([[object objectAtIndex:0] isEqualToString:colorKey]) {
newAmount = [[object objectAtIndex:1] integerValue] + change;
[[inventoryData objectAtIndex:i] replaceObjectAtIndex:1 withObject:[NSNumber numberWithInt:newAmount]];
foundPaint = true;
break;
}
}
if (newAmount == 0) {
[self removeInventoryColor:colorKey];
}
}
The issue appears to be surround the depth at which you are working... the mutable versions of containers you are creating only apply to that "level". You are later indexing into that level (i.e. accessing a container one level deeper) which is still immutable. Try passing the NSJSONReadingMutableContainers option when you first unserialize the JSON:
NSUInteger jsonReadingOptions = NSJSONReadingAllowFragments | NSJSONReadingMutableContainers;
NSMutableDictionary *localDictionary = [[NSMutableDictionary alloc] initWithDictionary:[NSJSONSerialization JSONObjectWithData:RawJSON options:jsonReadinOptions error:&e]];

Read and update a Dictionary type PSMultiValueSpecifier from NSUserDefault

Does anyone know how to easily read a Dictionary type PSMultiValueSpecifier from NSUserDefault into a UIPickViewer control and save changes back to NSUserDefaults using iPhone SDK 3.0? If so, can you post some specific code to efficiently do this?
The following code turns the PSMultiValueSpecifier Values and Titles into a nice dictionary which you can use with your UIPickViewer.
NSString* settingsBundle = [[[NSBundle mainBundle] pathForResource:#"Settings" ofType:#"bundle"] stringByAppendingPathComponent:#"Root.plist"];
NSDictionary* rootPlist = [NSDictionary dictionaryWithContentsOfFile:settingsBundle];
if (rootPlist == nil)
return nil;
NSArray* specifiers = [rootPlist objectForKey:#"PreferenceSpecifiers"];
NSDictionary *multiValueSpecifier = nil;
for (NSDictionary *specifier in specifiers)
{
if ([[specifier objectForKey:#"Key"] isEqualToString:speficierKey] == YES &&
[[specifier objectForKey:#"Type"] isEqualToString:#"PSMultiValueSpecifier"] == YES)
{
multiValueSpecifier = specifier;
break;
}
}
if (multiValueSpecifier == nil)
return nil;
NSArray* titlesArray = [multiValueSpecifier objectForKey:#"Titles"];
NSArray* valuesArray = [multiValueSpecifier objectForKey:#"Values"];
NSDictionary *dictionary = [NSDictionary dictionaryWithObjects:titlesArray
forKeys:valuesArray];
return dictionary;
To save it you need to save the KEY and not the value of the dictionary.
[settings setObject:dictionaryKey forKey:#"mySelection_preference"];