modifying json data in a local file using SBjson - objective-c

I have recently started Application development on MAC OS 10.6, I am trying to modify a "key/value" pair in a local JSON file on my MAC machine using SBJSON. I have successfully read the value of a key, but I am not able to get that how to modify the value of a key and synchronize this to the JSON file. Lets suppose, I have a following JSON Data int o a local file:
{
"name": {
"fName":"John",
"lName":"Doe"
}
}
And i want to change the value of "fName" to something else, like Robert.
I have tried alot searching about it, but got no clue... Can anyone help me.
I am using SBJSON Framework!
Code:
NSString *filePath = #"/Users/dev/Desktop/SQLiteFile/myJSON2.json";
NSData *myData = [NSData dataWithContentsOfFile:filePath];
NSString *responseString = [[NSString alloc] initWithData:myData encoding:NSUTF8StringEncoding];
NSLog(#"FILE CONTENT : %#", responseString);
SBJsonParser *jsonParser = [[SBJsonParser alloc] init];
NSDictionary * dictionary = (NSDictionary*)[jsonParser objectWithString:responseString error:NULL];
[dictionary setObject:#"Robert" forKey:#"fName"];
//
// Code for writing this change into the file, which i needed.
//
[jsonParser release];

You want a mutable deep copy of your dictionary. Then you'll be able to modify it.

Related

Read .mobileprovisioning profile with Objective-C

So, I'm trying to open a .mobileprovisioning profile to read what's inside... this is what I'm doing:
NSString *path = [pathURL path];
NSData *data = [[NSFileManager defaultManager] contentsAtPath:path];
Of course I get the data read but I'm not finding the way of getting of get this data into something useful... an NSDictionary, an NSString or whatever...
I've already tried:
NSString *newStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
Any idea? I'm sure this is an encoding issue, but I can't solve it after reading and googling for some time... I think the provisioning profile is saved as hexadecimal, but I don't know how to read that from objective-c. I have found this but there wasn't an useful answer.
How to convert NData populated with hex values to NSString
Thanks!
The following method should do what you want. As #rbrockerhoff says the mobile provisioning profile is an encoded CMS message. This method uses a decoder to first decode the data using the CMS functions and then creates the plist string/contents from the decoded data. This string can then be converted into a dictionary which is returned from the method. The dictionary will contain all the details from the mobile provisioning profile.
- (NSDictionary *)provisioningProfileAtPath:(NSString *)path {
CMSDecoderRef decoder = NULL;
CFDataRef dataRef = NULL;
NSString *plistString = nil;
NSDictionary *plist = nil;
#try {
CMSDecoderCreate(&decoder);
NSData *fileData = [NSData dataWithContentsOfFile:path];
CMSDecoderUpdateMessage(decoder, fileData.bytes, fileData.length);
CMSDecoderFinalizeMessage(decoder);
CMSDecoderCopyContent(decoder, &dataRef);
plistString = [[NSString alloc] initWithData:(__bridge NSData *)dataRef encoding:NSUTF8StringEncoding];
NSData *plistData = [plistString dataUsingEncoding:NSUTF8StringEncoding];
plist = [NSPropertyListSerialization propertyListWithData:plistData options:NSPropertyListImmutable format:nil error:nil]
}
#catch (NSException *exception) {
NSLog(#"Could not decode file.\n");
}
#finally {
if (decoder) CFRelease(decoder);
if (dataRef) CFRelease(dataRef);
}
return plist;
}
A .mobileprovisioning file is an encoded CMS message.
See https://developer.apple.com/library/mac/documentation/security/Reference/CryptoMessageRef/Reference/reference.html for details and an API for decoding it.
If you just want the encoded property list as text, a quick-and-dirty hack is to get the byte pointer for your NSData, scan for the beginning "<?xml" and up to the closing "</plist>". Then make a NSString from that.
You can simply force to open the mobile provisioning profile in TextEdit where you can see the
interior contents and in which you can trim/Edit the encoded CMS message or whatever you want . Then you can simply decode with NSData encodewithUTF string method.
Hope this helps.

How to create JSONP on MacOS?

I use the following code to create a JSON file.
// Some data in keys and vals.
NSDictionary* dictionary = [NSDictionary dictionaryWithObjects:vals forKeys:keys];
NSError* writeError = nil;
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:dictionary
options:NSJSONWritingPrettyPrinted error:&writeError];
NSString* path = #"json.txt";
[jsonData writeToFile:path atomically:YES];
How can I output a JSONP file? Is there a Cocoa framework I can use?
Update: In the meantime, I used a quick-and-dirty solution: I read in the JSON file just written before to the disc and add the missing JSONP-function to the string. Then, I write the file a second time. I think that's not worth being the answer to my question. So I will leave this question open to a smarter solution.
You could convert the JSON data to a string, wrap it in your function call and then write it to a file. Example:
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:dictionary
options:NSJSONWritingPrettyPrinted
error:NULL];
NSMutableString *jsonString = [[[NSMutableString alloc] initWithData:jsonData
encoding:NSUTF8StringEncoding] autorelease];
[jsonString insertString:#"functionCall(" atIndex:0];
[jsonString appendString:#");"];
[jsonString writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:NULL];
(I'm using a mutable string here for better memory efficiency.)
I don't know objective-c or Cocoa. (I use python on MacOS to create JSNOP responses), but it's a simple thing to do.
The basic idea is to wrap the JSON data in a javascript function call:
functionCall({"Name": "Foo", "Id" : 1234, "Rank": 7});
The tricky part is that the function name, "functionCall", is set by the browser and AFAIK the name of that query parameter is not standardized. jQuery uses jsonCallback. Other's use json or callback. So the request url must be checked for that callback name and that function name must be used to wrap the json data.

Plist won't write to file

When i try this code:
if ([[NSFileManager defaultManager] fileExistsAtPath:#"~/Library/Application Support/Staying Afloat/stats/static-stats.plist"] == NO) {
NSMutableDictionary* tempDict = [[NSMutableDictionary alloc]initWithObjectsAndKeys:
[[NSString alloc] initWithString:#"0"],#"completedGames",
[[NSString alloc] initWithString:#"0"],#"floatsCollected",
[[NSString alloc] initWithString:#"0"],#"sec",
[[NSString alloc] initWithString:#"0"],#"subScore",
[[NSString alloc] initWithString:#"0"],#"highScore",
[[NSString alloc] initWithString:#"0"],#"longestSec", nil];
[tempDict writeToFile:#"~/Library/Application Support/Staying Afloat/stats/static-stats.plist" atomically:YES];
NSLog(#"written file");
}
and this outputs with
2012-04-14 19:15:10.009 Staying Afloat[3227:9c07] written file
so the loop has run, but the file isn't written?
can anyone point my in the right dirrection for saving plists to non-localized places?
EDIT: this is for mac
You need to expand your path using -(NSString *)stringByExpandingTildeInPath when you write and check if the file exist.
Edit: Read the method writeToFile:automatically in the NSDictionary documentation. It says
If path contains a tilde (~) character, you must expand it with stringByExpandingTildeInPath before invoking this method.
So just do something like
[tempDict writeToFile:[[NSString stringWithString:#"~/Library/Application Support/Staying Afloat/stats/static-stats.plist"] stringByExpandingTildeInPath] atomically:YES];
first you can't just write :
[tempDict writeToFile:#"~/Library/Application Support/Staying Afloat/stats/static-stats.plist" atomically:YES];
NSLog(#"written file");
because you don't check if the file was really written with success or not.
you should write :
if([tempDict writeToFile:#"~/Library/Application Support/Staying Afloat/stats/static-stats.plist" atomically:YES]) {
NSLog(#"written file");
} else {
NSLog(#"file not written");
}
WriteToFile method returns a Boolean.
I always use NSUserDefaults which takes care of everything for you with things like user prefs or storing app states between running. Off the top of my head I'd suggest you haven't got the right privileges to write a plist and what happens if you accidentally destroy a vital key and value for the os? Have a look at
Best practices for handling user preferences in an iPhone MVC app?
Are you sure -writeToFile:atomically: returns YES?

Write to file not working

I'm trying to combine images in my app into one file and write it to disk.
NSMutableArray *array = [NSMutableArray arrayWithObjects:
[NSData dataWithContentsOfFile:#"0.png"],
[NSData dataWithContentsOfFile:#"1.png"],
[NSData dataWithContentsOfFile:#"2.png"],
nil];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];
NSError *error = nil;
NSString *path=#"/Users/myusername/Desktop/_stuff.dat";
[data writeToFile:path options:NSDataWritingAtomic error:&error];
or
NSArray *array = [NSArray arrayWithObjects:
[NSImage imageNamed:#"0"],
[NSImage imageNamed:#"1"],
[NSImage imageNamed:#"2"],
nil];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];
NSError *error = nil;
NSString *path=#"/Users/myusername/Desktop/_stuff.dat";
[data writeToFile:path options:NSDataWritingAtomic error:&error];
But both produce a file that is 4KB (empty). If I NSLog the error it is (null). Am I making the data the wrong way?
Edit: If I open the resulting file with a text editor, it looks like this:
I wrote a quick example:
Missing: memory management / error handling / proper file handling
// Archive
NSMutableArray *array = [[NSMutableArray alloc] init];
NSString * input = #"/Users/Anne/Desktop/1.png";
[array addObject:[NSData dataWithContentsOfFile:input]];
[array addObject:[NSData dataWithContentsOfFile:input]];
[array addObject:[NSData dataWithContentsOfFile:input]];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];
NSString *path = #"/Users/Anne/Desktop/archive.dat";
[data writeToFile:path options:NSDataWritingAtomic error:nil];
// Unarchive
NSMutableArray *archive = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
NSData * firstObject = [archive objectAtIndex:0];
NSString * output = #"/Users/Anne/Desktop/2.png";
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath:output];
[firstObject writeToURL:fileURL atomically:YES];
You can also add NSImages to the NSMutableArray:
NSString * input = #"/Users/Anne/Desktop/1.png";
NSImage *image = [[NSImage alloc] initWithContentsOfFile: input];
[array addObject:image];
But that will significantly increase the file size.
Response to the following comment:
So if I only need to access an image at runtime (in the archive), is there a way to access that image at an index without unarchiving the whole thing? Seems like unnecessary overhead to me.
I assume you're still struggling with this problem?
Hiding (or encrypting) app resources?
Like i mentioned earlier, combining all files into one big file does the trick.
Just make sure you remember the file-length of each file and file-order.
Then you can extract any specific file you like without reading the whole file.
This might be a more sufficient way if you only need to extract one file at the time.
Quick 'dirty' sample:
// Two sample files
NSData *fileOne = [NSData dataWithContentsOfFile:#"/Users/Anne/Desktop/1.png"];
NSData *fileTwo = [NSData dataWithContentsOfFile:#"/Users/Anne/Desktop/2.png"];
// Get file length
int fileOneLength = [fileOne length];
int fileTwoLength = [fileTwo length];
// Combine files into one container
NSMutableData * container = [[NSMutableData alloc] init];
[container appendData:fileOne];
[container appendData:fileTwo];
// Write container to disk
[container writeToFile:#"/Users/Anne/Desktop/container.data" atomically:YES];
// Read data and extract sample files again
NSData *containerFile = [NSData dataWithContentsOfFile:#"/Users/Anne/Desktop/container.data"];
NSData *containerFileOne =[containerFile subdataWithRange:NSMakeRange(0, fileOneLength)];
NSData *containerFileTwo =[containerFile subdataWithRange:NSMakeRange(fileOneLength, fileTwoLength)];
// Write extracted files to disk (will be exactly the same)
[containerFileOne writeToFile:#"/Users/Anne/Desktop/1_extracted.png" atomically:YES];
[containerFileTwo writeToFile:#"/Users/Anne/Desktop/2_extracted.png" atomically:YES];
// Only extract one file from the container
NSString * containerPath = #"/Users/Anne/Desktop/container.data";
NSData * oneFileOnly = [[NSFileHandle fileHandleForReadingAtPath:containerPath] readDataOfLength:fileOneLength];
// Write result to disk
[oneFileOnly writeToFile:#"/Users/Anne/Desktop/1_one_file.png" atomically:YES];
Tip:
You can also save the 'index' inside the container file.
For example: The first 500 bytes contain the required information.
When you need a specific file: Read the index, get the file position and extract it.
You are archiving a NSMutable array of NSImage. This two classes conform to the NSCoding protocol required by NSKeyedArchiver, so I don't see where would be your problem.
So, here are many ideas to test.
First, are you sure that the data you think you have are valid? In your first code snippet, you write [NSData dataWithContentsOfFile:#"0.png"]. This method expects an absolute file path.
Assuming the problem is not in your code, just in your question, let's continue:
Do you have something different than nil in the variable data after your archiving? Ie, after the assignement to data, can you add this code. If the assertion fail, you will get an exception at runtime:
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];
NSAssert(nil != data, #"My object data is nil after archiving");
If the problem was not here, what is the return of the line [data writeToFile:path options:NSDataWritingAtomic error:&error];
(Not the variable error, but the return value of the call to the method - writeToFile: options: error:)
What happens if you simplify your code and just do this:
result = [NSKeyedArchiver archiveRootObject:data
toFile:archivePath];
If everything was ok, have you tried to unarchive your file with NSKeyedUnarchiver?
The problem is that [NSData dataWithContentsOfFile:#"0.png"] looks for the file "0.png" in the current directory, but what the application thinks of as the current directory is probably not the place you're expecting. For graphical apps, you should always either use an absolute path or a path relative to some place that you can get the absolute path of (e.g. your app bundle, the application support directory, some user-selected location).
For command-line tools, using the current directory is more common. But I doubt that's the case here.
Another thing I noticed on Mavericks and up is that the folders in the path must be in existence. Meaning you must create the folder structure prior to saving into that folder. If you try to write to a folder on the desktop or elsewhere, even with sandboxing off, it will fail if the folder does not exist. I know this has been answered already, but I found that my issue continued regardless, but once I make sure that the folder structure was in place, I could do my writing to that folder.
On a side note: I'm sure that you could do this from NSFileManager, and I'll be doing that myself once I finalize my app structure, but hope this helps someone else lost in the sauce.

Object-c/iOS :About use NSUserDefaults set serial numbers/password

I got many issues about setting sn/pwd
Because the first default data is from URL
got a plist data like this:
{
serial_number=1234;
password = 7777;
}
The application finish launched,It will download from URL via the code like this
NSString *urlString = [[NSString alloc]initWithString:#"http://getdefaults.php"];
NSArray *plistData;
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURLURLWithString:urlString]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
NSData *returnData = [NSURLConnection sendSynchronousRequest:theRequest returningResponse:nil error:nil];
NSString *listFile = [[NSString alloc] initWithData:returnDataencoding:NSUTF8StringEncoding];
[listFile dataUsingEncoding:NSUTF8StringEncoding];
plistData = [listFile propertyList];
At first , I try to create a plist data in iphone:
NSArray *localPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *localDocPath = [localPaths objectAtIndex:0];
NSString *localFilePath = [localDocPath stringByAppendingPathComponent:#"InputPassword.plist"];
NSMutableDictionary *localDictread = [[NSMutableDictionary alloc] initWithContentsOfFile:localFilePath];
Than I compare with the information I get
if(localDictread==nil){
//pop a textfield to let user enter the serial numbers and password
//if the text in the textfield is equal the default SN/PWD from plistData,write the sn/pwd to plist in iphone
}
But I found NSUserDefaults , Can it work as the same ?
I mean work as the same is can I record a serial number and password in my iphone ???
What is best way to record a user serial number/password in iphone ?
If you are a storing password, not NSUserDefaults nor serializing the data in NSDocumentDirectory are great solutions because data is stored with little protection. iOS provides the KeyChain mechanism for this porupose. It provides C interfaces that are a bit obscure, but a Buzz Andersen got a nice Objective-C interface going called SFHFKeychainUtils. With SFHFKeychainUtils you can do something like:
[SFHFKeychainUtils storeUsername:user.text andPassword:pass.text forServiceName:#"my_service" updateExisting:YES error:nil];
And that gets stored securely into the keychain. To recover the password:
NSString *pw = [SFHFKeychainUtils getPasswordForUsername:user.text andServiceName:#"my_service"