I am using BTLE to write data into one of the characteristics of peripheral. I wanted to send an NSDictionary into that characteristics. Since there is a limitation of 130 bytes of data being sent over BTLE, I want an efficient way of compressing NSDictionary into NSData and then send across. I am using below piece of code which is exceeding limit. Any ideas?
NSDictionary *aDict = #{ #"Value1": #"sadsadasdasdsadqwwqsadasd",
#"Value2": #"10",
#"Value3": #"12" };
NSData *aData = [NSKeyedArchiver archivedDataWithRootObject:aDict];
NSLog(#"Data Size = %#",
[NSByteCountFormatter stringFromByteCount:aData.length
countStyle:NSByteCountFormatterCountStyleFile]);
I don't think trying to use any form of compression will be effective, or even an improvement at all at this scale, because all compression algorithms work best when they have a lot of data to work with, and hence many duplicates and patterns to find. When your entire data size is 130 bytes, any form of zip compression isn't really a viable option.
If your dictionary will only contain property-list values (arrays, dictionaries, strings, numbers), then you can use JSON serialisation instead of NSKeyedArchiver:
NSData *JSONData = [NSJSONSerialization dataWithJSONObject:anObject
options:0
error:nil];
This immediately makes the output data much shorter in your case:
NSDictionary *aDict = #{ #"Value1": #"sadsadasdasdsadqwwqsadasd",
#"Value2": #"10",
#"Value3": #"12" };
NSData *aData = [NSKeyedArchiver archivedDataWithRootObject:aDict];
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:aDict
options:0
error:nil];
NSLog(#"NSKeyedArchiver Data Size = %#, JSON Data Size = %#",
[NSByteCountFormatter stringFromByteCount:aData.length
countStyle:NSByteCountFormatterCountStyleFile],
[NSByteCountFormatter stringFromByteCount:jsonData.length
countStyle:NSByteCountFormatterCountStyleFile]
);
NSKeyedArchiver Data Size = 380 bytes, JSON Data Size = 66 bytes
As you can see, the JSON serialised data is almost 6 times smaller than the NSKeyedArchiver serialised data, and fits easily in your 130 byte limit. And the best thing is, it's only one line of code.
UPDATE: Just to rub it in some more :), here is the data that NSKeyedArchiver produces (added as image because it contains a lot of "illegal" characters that I couldn't copy and paste):
As you can see, it contains a lot of useless data that you don't really need (highlighted blue), that's basically just to give NSKeyedUnarchiver enough information to be able to unarchive it later.
Now, let's look at the JSON data:
{"Value3":"12","Value2":"10","Value1":"sadsadasdasdsadqwwqsadasd"}
That's it. One line. 66 bytes. Of those, 19 bytes aren't your values. In other words, 71% of that JSON data is your values, and the rest is markup, so to speak. Meanwhile, in the NSKeyedArchiver data, your values make up, wait for it, 12% of the result. I think you can clearly see which one is more efficient for storage here.
Related
I need to sent NSData which holds JSON Strings as well total number of length in form of (length of actual string+actual string).I need to send a packet of data that reserves first 10 bytes for length of string and followed by string
while sending NSData object I also need to send its length in first 10 bytes followed by data like :
length of data + JSON string = total data sent to java client .
further java client will read first 10 byte to know actual length of data coming to make an byte array and move further.
This brute force example uses first 10 characters for string representation of payload length followed by actual payload.
NSArray *arrPayload = #[#"Hello", #"world"];
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:arrPayload
options:0
error:nil];
NSString *jsonString = [[NSString alloc] initWithData:jsonData
encoding:NSUTF8StringEncoding];
NSString *comboString = [NSString stringWithFormat:#"%010lu%#",
(unsigned long)jsonString.length, jsonString];
NSLog(#"%#", comboString);
NSData* combinedData = [comboString dataUsingEncoding:NSUTF8StringEncoding];
result:
0000000017["Hello","world"]
But: if this is supposed to be sent as a HTTP request you might want to consider using Content-Length header to pass the length information instead.
EDIT: I did not do a very good job of explaining for the server works and my apologies for the same. So here are two things in which the server works ( expects data from clients)
1) Server has a size limitation for receiving image data. It expects images to be broken up in chunks of byte array
2) Server expects to receive these chunks of byte array through JSON.
So I am assuming this translates to the following on the client side
1) I need to break the image in parts
2) Create a Byte array of each part
3) Bind those byte array with JSON and send with server
Once received by the server, those are constructed as an Image by the server.
I am trying to achieve the above mentioned goal by the following approach (I keep the image file in NSData, then I create a Byte Buffer and keep chunks of the image file's NSData in that buffer. Post that I bind this buffer with JSON )
Following is the code for above approach:
-(void)dividepacketId:(int)pktId fileData:(NSData*)dt //dt contain the NSData of image file
{
Byte buffer[20480];
long long dtLength,from=0;
long long len=[dt length];
BOOL b=YES;
while (b)
{
int k=0,indexCont=0;
if(len>20480)
{
dtLength=20480;
}
else
{
dtLength=len;
b=NO;
}
[dt getBytes:buffer range:NSMakeRange(from,dtLength)];
NSData *imageData=nil;
imageData = [NSData dataWithBytes:buffer length:dtLength];
len=len-dtLength;
from=from+dtLength;
NSLog(#"sending buffer=%s legth of buffer=%lli len value=%lli",buffer,dtLength,len); //everything is fine till here
NSMutableDictionary *projectDictionary3 = [NSMutableDictionary dictionaryWithCapacity:1];
[projectDictionary3 setObject:#"2100" forKey:#"Action"];
[projectDictionary3 setObject:[NSString stringWithFormat:#"%i",pktId] forKey:#"PacketId"];
[projectDictionary3 setObject:#"101" forKey:#"FileAction"];
if(imageData!=nil)
[projectDictionary3 setObject: imageData forKey:#"FData"];//data
[projectDictionary3 setObject:[NSString stringWithFormat:#"%i",(int)dtLength] forKey:#"DataLength"];//data
NSError *jsonSerializationError = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:projectDictionary3 options:NSJSONWritingPrettyPrinted error:&jsonSerializationError]; //"here crashed"
if(!jsonSerializationError)
{
NSString *serJSON = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSLog(#"Serialized JSON: %#", serJSON);
}
else
{
NSLog(#"JSON Encoding Failed: %#", [jsonSerializationError localizedDescription]);
}
code to send over stream
[self sendDataToServer:jsonData];
} //while loop
}
Here is the challenge. If I send simple data (for example a string) through this code, it goes over to server successfully ( through socket). But when I try to break an actual jpeg image into parts and bind it in nsdictionary to make json, it crashes with the following error.
terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (NSConcreteData)'. Any help here would be highly appreciated.
EDIT: As explained by bobnoble, I understand the reason for the exception. In that case however, how do I accomplish sending over data to server
Deleting all but the key portions that need to be changed.
As I stated in the comments the data needs to be in a format JSON handles, raw bytes are not acceptable so one way is to encode the data with Base64. The receiver will also need to decode the Base64 string into data.
while (b) {
//get chunk from and dtLength
NSData *imageData = [dt subdataWithRange:NSMakeRange(from, dtLength)];
NSData *imageBase64Data = [imageData base64EncodedDataWithOptions:0];
NSString *imageBase64String = [[NSString alloc] initWithData:imageBase64Data encoding: NSUTF8StringEncoding];
// update len and from
NSLog(#"sending imageData =%#. dtLength =%i len =%lli", imageBase64String, dtLength, len);
// Create projectDictionary3 and add items
// Added image data)
if(imageData.length) {
[projectDictionary3 setObject: imageBase64String forKey:#"FData"];
}
[projectDictionary3 setObject:#(imageBase64String.length) forKey:#"DataLength"];
// serialize projectDictionary3 into JSON and sent to server];
}
From the NSJSONSerialization class reference:
An object that may be converted to JSON must have the following
properties:
The top level object is an NSArray or NSDictionary.
All objects are instances of NSString, NSNumber, NSArray, NSDictionary, or NSNull.
All dictionary keys are instances of NSString.
Numbers are not NaN or infinity.
The second bullet does not include NSData, and is why the exception is being thrown.
Convert the image data to Base64 encoding, then put it in the dictionary as an NSString. Take a look at the NSData base64EncodedStringWithOptions method.
I'm writing an iOS loader that loads data from a plist intending to send vertex data, etc. to the GPU via OpenGL. I can easily extract objects of standard types, like strings, integers, etc.
Where I get stumped is when I encounter what appears to be raw data as a dictionary object. The plist is a native file saved by my 3D modeling software, of which I'm not the author, so I don't know how the data was written into this object.
Some things I DO know about the object, it's likely an array of floats, each vertex needs a float value for X, Y, and Z, and there are 26 vertices in the example below.
Here's the actual data object in the plist file:
<key>vertex</key>
<data>
AAAAAL8AAAAAAAAAAAAAAAAAAAC/AAAAPwAAAAAAAAA+gAAAvwAA
AD7ds9cAAAAAPt2z2L8AAAA+f///AAAAAD8AAAC/AAAAsru9LgAA
AAA+3bPXvwAAAL6AAAEAAAAAPoAAAb8AAAC+3bPXAAAAALM7vS6/
AAAAvwAAAAAAAAC+gAADvwAAAL7ds9UAAAAAvt2z2L8AAAC+f//9
AAAAAL8AAAC/AAAAMczeLgAAAAC+3bPYvwAAAD5///0AAAAAvn//
+L8AAAA+3bPaAAAAAD6AAAA/AAAAPt2z1wAAAAAAAAAAPwAAAD8A
AAAAAAAAAAAAAD8AAAAAAAAAAAAAAD7ds9g/AAAAPn///wAAAAA/
AAAAPwAAALK7vS4AAAAAPt2z1z8AAAC+gAABAAAAAD6AAAE/AAAA
vt2z1wAAAACzO70uPwAAAL8AAAAAAAAAvoAAAz8AAAC+3bPVAAAA
AL7ds9g/AAAAvn///QAAAAC/AAAAPwAAADHM3i4AAAAAvt2z2D8A
AAA+f//9AAAAAL5///g/AAAAPt2z2gAAAAA=
</data>
Any ideas about how to read this? Here's where I am:
// get plist
NSString *path = [[NSBundle mainBundle] pathForResource:#"Cylinder" ofType:#"jas"];
NSDictionary *cheetahFile = [NSDictionary dictionaryWithContentsOfFile:path];
NSArray *objectArray = [cheetahFile objectForKey:#"Objects"];
NSDictionary *model = [objectArray objectAtIndex:1];
//get vertex count
GLshort vertCount = [[model valueForKey:#"vertexcount"] intValue];
//All good so far...but...
//get vertex data?... this doesn't work:
NSMutableArray *vertArray = [NSMutableArray arrayWithObject:[model objectForKey:#"vertex"]];
P.S. Sorry in advance if I'm making a rookie mistake. I'm a designer by profession, not a programmer. So talk slow using soothing tones while I eat my crayons. :)
The <data> part is an encoded NSData object. You can do this:
NSData *vertexData = model[#"vertex"];
What you do with that data is a whole other discussion.
I've tried converting the same NSDictionary object into NSData and then NSString using NSJSONSerialization and SBJsonWriter several times, and sometimes got a different string. even null. It's quite weird and I can't find any reason. =( JSONKit and YAJL don't have problems like this.
Following is my test code.
for (int i = 0; i < 5; i++) {
NSDictionary *d = [NSDictionary dictionaryWithObject:#"value" forKey:#"key"];
NSData *data = [NSJSONSerialization dataWithJSONObject:d options:0 error:nil];
NSLog(#"%#", [NSString stringWithUTF8String:data.bytes]);
}
and the console output is ...
2012-04-25 01:35:33.113 Test[19347:c07] {"key":"value"}
2012-04-25 01:35:33.114 Test[19347:c07] (null)
2012-04-25 01:35:33.114 Test[19347:c07] {"key":"value"}
2012-04-25 01:35:33.114 Test[19347:c07] {"key":"value"}
2012-04-25 01:35:33.115 Test[19347:c07] (null)
output changes every time I run the test code.
data's byte size is the same, but UTF8-converted string length varies.
The bytes in an NSData object do not necessarily comprise a NUL-terminated string. If you want to convert the data into an NSString, do this instead:
[[NSString alloc] initWithBytes:data.bytes length:data.length encoding:NSUTF8StringEncoding]
There's a possibility that some parsers write '\0' to the end of the data they return for safety, which explains why they behave more predictably. But you shouldn't rely on that behavior, as you've seen.
Is it possible if I have a NSString and I want to use NSJSONSerialization? How do I do this?
First you will need to convert your NSString to NSData by doing the following
NSData *data = [stringData dataUsingEncoding:NSUTF8StringEncoding];
then simply use the JSONObjectWithData method to convert it to JSON
id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
You need to convert your NSString to NSData, at that point you can use the +[NSJSONSerialization JSONObjectWithData:options:error:] method.
NSString * jsonString = YOUR_STRING;
NSData * data = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSError * error = nil;
id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (!json) {
// handle error
}
You can convert your string to NSData by saying:
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
You can then use it with NSJSONSerialization. Note however that NSJSONSerialization is iOS5 only, so you might be better off using a library like TouchJSON or JSONKit, both of which let you work directly with strings anyway, saving you the step of converting to NSData.
I wrote a blog post that demonstrates how to wrap the native iOS JSON class in a general protocol together with an implementation that use the native iOS JSON class.
This approach makes it a lot easier to use the native functionality and reduces the amount of code you have to write. Furthermore, it makes it a lot easier to switch out the native implementation with, say, JSONKit, if the native one would prove to be insufficient.
http://danielsaidi.com/blog/2012/07/04/json-in-ios
The blog post contains all the code you need. Just copy / paste :)
Hope it helps!