1

I have an NSInputStream *inputStream receiving small JSON objects from a network connection. If I read the stream to a buffer like so:

NSError *err = nil;
uint8_t buffer[1024];
NSMutableData *data = [[NSMutableData alloc] init];
while ([inputStream hasBytesAvailable]) {
    int const len = [inputStream read:buffer maxLength:sizeof(buffer)];
    if (0 < len) {
        [data appendBytes:buffer length:len];
    }
}
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&err];

then I get the JSON I expect; but this is very brittle, as it assumes that there is exactly one JSON object to read. With this scheme, I could end up trying to deserialize {"cheese":17}{"ch, which is obviously invalid. I'd rather use

NSError *err = nil;
NSDictionary *json = [NSJSONSerialization JSONObjectWithStream:inputStream options:0 error:&err];

but, but stepping through with the debugger, I discovered that this last line does not return, and logs no error! What's the right approach?

Richard
  • 3,316
  • 30
  • 41
  • Does `inputStream` read from a network connection? You never know how many bytes are read by `read:maxLength:`, so if the server is sending a sequence of JSON objects you need some delimiter anyway. Are the JSON objects perhaps newline-separated? – Martin R Aug 21 '13 at 16:53
  • @MartinR Streaming on the network. (Well-formed) JSON is self-delimiting by counting open/close brace pairs, so newline should be irrelevant. – Richard Aug 21 '13 at 17:02
  • Well, that's true. But NSJSONSerialization has (as far as I know) no method to parse only the "first JSON" (and perhaps return a offset to the start of the next JSON). I assume that you have to find the range "manually". You *could* try to convert the first 1, 2, 3, ... bytes until it succeeds, but that does not sound very effective. – Martin R Aug 21 '13 at 17:13
  • Don't attempt to parse the JSON until you've received the entire string. – Hot Licks Aug 21 '13 at 17:26
  • And don't send more than one JSON payload in a single transfer, unless you have some way to separate them. – Hot Licks Aug 21 '13 at 17:26
  • @HotLicks You mean that `NSJSONSerialization` will try to deserialize the entire stream? Documentation says "A stream from which to read JSON data," so I assumed it would read the first JSON object and then stop. – Richard Aug 21 '13 at 17:31
  • 1
    If you don't mind to "simplify" your network code such that you receive chunks of JSON text represented as `NSData` objects (well, exactly what you get from `NSURLConnection` in `connection:didReceiveData:`, you may use a third party JSON parser which is able to parse those chunks: [jsonlite](https://github.com/amamchur/jsonlite) or [JPJson](https://github.com/couchdeveloper/JPJson). JPJson can also parse multiple JSON documents in one stream of data. (I'm the author of JPJson, but jsonlite is excellent, too). – CouchDeveloper Aug 21 '13 at 17:31
  • NSJSONSerialization will almost certainly diagnose "garbage at the end of input" or some such. – Hot Licks Aug 21 '13 at 17:33
  • @HotLicks That's what happens when my `NSData` includes multiple objects, as well. – Richard Aug 21 '13 at 17:33
  • @HotLicks but then this API is useless for network connections, because you cannot know when the stream contains exactly one JSON object. – Richard Aug 21 '13 at 17:35
  • It's not normal to have more than one JSON object in a stream. – Hot Licks Aug 21 '13 at 17:36
  • You could always take an open source JSON parser and modify it to do what you want. JSON parsing is not that complex, and figuring out the parser (if it's well-written) should not be a problem. – Hot Licks Aug 21 '13 at 17:38
  • In any case, it should at least return `nil` and an `err`, rather than not returning at all! – Richard Aug 21 '13 at 17:55
  • `JSONObjectWithStream` is probably not very intelligent. I assume that it reads from the stream until EOF and then uses JSONObjectWithData on the complete data. – Martin R Aug 21 '13 at 18:00
  • @MartinR I guess so, although I still don't understand why `JSONObjectWithStream` throws. I think I'll have to rewrite this question or do more research. – Richard Aug 21 '13 at 18:02

0 Answers0