I am converting a JSON file to a plist using the new NSJSONSerialization class and NSPropertyListSerialization class. I manage to convert my JSON to a Plist without errors, but then, at my last step, when I go to write the plist to my desktop, the program crashes, but AFTER the Plist has been generated!
NSData *data = [[NSData alloc] initWithContentsOfURL:path]; \\(NSURL *)path -->goes to my JSON file
NSMutableDictionary *json = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:nil];
//the following removes all key/object pairs where the object is null, because NSPropertyListSerialization with throw an error if there are null values
for (id __strong object in [json objectForKey:#"terms"]) {
if ([object objectForKey:#"image"] == [NSNull null]) {
[object removeObjectForKey:#"image"];
}
}
//the following NSPropertyListSerialization method returns an NSData
id plist = [NSPropertyListSerialization dataFromPropertyList:(id)json
format:NSPropertyListXMLFormat_v1_0
errorDescription:nil];
NSError *writeToFileError;
[plist writeToFile:#"/Users/kalaracey/Desktop/test.plist"
atomically:YES
encoding:NSUTF8StringEncoding
error:&writeToFileError];
Then, at this last line, an NSInvalidArgumentException is thrown, and crashes my program. However, the plist was successfully generated! I can read it, and all is well, except my program crashes.
Could someone please explain why this crashes, and how I could avoid crashing?
The problem seems to be that the variable plist is type id. Cast it to NSData and you should be fine.
NSData *plist = (NSData *) [NSPropertyListSerialization ...];
As you correctly point out in the comment, NSData should use the writeToFile:atomically: method.
Related
I have a json file that looks like this:
{
"data":
{
"level": [
{
//bunch of stuff
}
]
}
}
Now I want to convert that into a array of levels that I can access. If I take away the {"data: part, then I can use this:
NSData *allLevelsData = [[NSData alloc] initWithContentsOfFile:fileLoc];
NSError *error = nil;
NSMutableDictionary *allLevels = [NSJSONSerialization JSONObjectWithData:allLevelsData options:kNilOptions error:&error];
if(!error){
NSMutableArray *level = allLevels[#"level"];
for (NSMutableDictionary *aLevel in level){
//do stuff with the level...
But I have to have the {"data: as part of the file, and I can't figure out how to get a NSData object out of the existing NSData object. Any ideas?
Don't you need to pull the level NSArray out of the data NSDictionary first?
NSData *allLevelsData = [[NSData alloc] initWithContentsOfFile:fileLoc];
NSError *error = nil;
NSDictionary *dataDictionary = [NSJSONSerialization JSONObjectWithData:allLevelsData options:kNilOptions error:&error];
if(!error){
NSArray *levels = dataDictionary[#"data"][#"level"];
for (NSDictionary *aLevel in levels){
//do stuff with the level...
You won't get mutable objects back by default and declaring the variables as mutable doesn't make them so. Take a mutableCopy of the result instead (assuming you really do need mutability).
Why are you trying to prune ahead of time? If you decode the original JSON, you'll be able to extract the level array from the data dict in the decoded dict.
It's not clear what else you're trying to accomplish or why you are going the path you ask about. Note, this doesn't necessarily mean your path is wrong, just that without a clearer indication of what goal you're really trying to accomplish or what you've actually tried (and errored/failed, along with how it failed), you're likely only to get vague/general answers like this.
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.
Stackoverflow fellows.
I'm trying to load a plist. But then plist's root can be NSArray or NSDictionary. Is there prettier way to determine which one it is (by the header's type of root) NSArray or NSDictionary? Obviously, It is very common pull up file from NSDictionary or NSArray or NSData.
Thanks.
By using the NSPropertyListSerialization class you can deserialize your plist file from an NSData and then with a fast check understand if it's a NSDictionary or a NSArray through isMemberOfClass:
id plist = [NSPropertyListSerialization propertyListWithData:data options:0 format:nil error:nil]
if ([plist isMemberOfClass:[NSArray class]]) {
..
}
In the method below I'm receiving "EXC_BAD_ACCESS" on the line containing the "urlString" variable. My research suggests that this error occurs when the program sends a message to a variable that has already been released. However since I'm using ARC I'm not manually releasing memory. How can I prevent ARC from releasing this variable too soon?
-(NSMutableArray *)fetchImages:(NSInteger *)count {
//prepare URL request
NSString *urlString = [NSString stringWithFormat:#"http://foo.example.com/image?quantity=%#", count];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]];
//Perform request and get JSON as a NSData object
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
//Parse the retrieved JSON to an NSArray
NSError *jsonParsingError = nil;
NSArray *imageFileData = [NSJSONSerialization JSONObjectWithData:response options:0 error:&jsonParsingError];
//Create an Array to store image names
NSMutableArray *imageFileNameArray;
//Iterate through the data
for(int i=0; i<[imageFileData count];i++)
{
[imageFileNameArray addObject:[imageFileData objectAtIndex:i]];
}
return imageFileNameArray;
}
Your problem has nothing to do with ARC. NSInteger isn't a class, so you don't want to be using the %# format. %# is going to send a description method to what the system thinks is an object, but when it turns out not to be one - CRASH. To solve your problem, you have two options:
You might want:
NSString *urlString =
[NSString stringWithFormat:#"http://foo.example.com/image?quantity=%d",
*count];
Make sure the count pointer is valid first!
You might need to change your method signature to be:
-(NSMutableArray *)fetchImages:(NSInteger)count;
and then change the urlString line as follows:
NSString *urlString =
[NSString stringWithFormat:#"http://foo.example.com/image?quantity=%d",
count];
You'll also need to fix all of the callers to match the new signature.
The second option seems more "normal" to me, but without more of your program it's impossible to be more specific.
you also may want to alloc and init the
NSMutableArray *imageFileNameArray;
before adding objects to it, otherwise you'll keep crashing. So you'd have
//Create an Array to store image names
NSMutableArray *imageFileNameArray = [[NSMutableArray alloc] init];
I am allowing for application data (it's a Mac app on 10.7) to be exported as an XML file, and one field I would like to be able to export/import to/from XML is an NSData field. What would be the correct/accepted way of doing this? Should I convert to base64 and write that string to XML?
I would prefer not to roll my own solution, using a category, as the accepted answer to the linked question does (linking to Matt Gallagher's solution).
Update
I just discovered the NSPropertyListSerialization class. I got my hopes up, but it only has static serialization methods which return NSData representations.
I realized (as my updated alluded to) that I could use the NSPropertyListSerialization class, since the NSData returned by -dataWithPropertyList:format:options:error: is just a UTF-8 string. This is what I'm using to serialize:
NSData *data = value;
NSError *error = nil;
NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:data
format:NSPropertyListXMLFormat_v1_0
options:0
error:&error];
if (error) {
NSLog(#"Error serializing data to plist XML: %#", error);
} else {
NSString *plistString = [[NSString alloc] initWithData:plistData encoding:NSUTF8StringEncoding];
NSXMLElement *dataElement = [NSXMLElement elementWithName:field
stringValue:plistString];
}
And deserialize:
NSData *plistData = [element.stringValue dataUsingEncoding:NSUTF8StringEncoding];
NSData *originalData = [NSPropertyListSerialization propertyListWithData:plistData
options:NSPropertyListImmutable
format:NULL
error:&error];
if (error) {
NSLog(#"Error deserializing data from plist XML: %#", error);
} else {
value = originalData;
}