I am trying to decode json being sent to my application from Twitter's Streaming API.
Twitter will send my client data which has the following structure:
The body of a streaming API response consists of a series of newline-delimited messages, where "newline" is considered to be \r\n (in hex, 0x0D 0x0A) and "message" is a JSON encoded data structure or a blank line.
They basically separate tweets with \r\n. Sometimes they send you one tweet, sometimes they send you many at once.
Decoding the 'many' case is problematic for me. I use NSJSONSerialization class built into iOS. The problem is that it reads the first tweet and then see's the \r\n and considers this as garbage and returns an empty array and accompanying error.
To get around this problem, I use the following code to basically remove the newlines.
// json is the received json data
NSString* responseString = [[NSString alloc] initWithData:json encoding:NSASCIIStringEncoding];
NSMutableArray* responseMutableArray = [[responseString componentsSeparatedByString:#"\r\n"] mutableCopy];
[responseMutableArray removeLastObject];
NSArray* responseArray = [NSArray arrayWithArray:responseMutableArray];
[responseMutableArray removeAllObjects];
NSError* error;
for(NSString* tweetString in responseArray)
{
NSDictionary* dict = [NSJSONSerialization JSONObjectWithData:[tweetString dataUsingEncoding:NSUTF8StringEncoding] options: NSJSONReadingAllowFragments|NSJSONReadingMutableContainers error:&error];
[responseMutableArray addObject:dict];
}
//responseMutableArray contains the parsed tweets
The approach is, convert the data into a NSString, NSString into an array (by spliting by \r\n), array back into data and then parse the JSON into a dictionary. Its a messy process and I wonder if there is something more efficient way that I can do this.
Thanks very much
Vb
Disclaimer: I actually got here by encountering this issue myself.
I think the simplest solution would be a revision of #Elmundo 's answer that removes the newlines completely. It worked for me.
Note: this is Swift version 3.something
guard var string = String(data: data, encoding: .utf8) else {
return
}
string = string.stringByReplacingOccurrencesOfString("\r\n", withString: "")
guard let data = string.data(using: .utf8) else {
return false
}
do {
let json = try NSJSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as! [String:Any]
}catch let e as NSError {
print(e.description)
}
Try this:
NSString *string = [[NSString alloc] initWithData:json encoding:NSASCIIStringEncoding]:
string = [NSString stringWithFormat:#"[%#]", [string stringByReplacingOccurrencesOfString:#"\\r\\n" withString:#","]];
NSArray *array= [NSJSONSerialization JSONObjectWithData:[string dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingAllowFragments|NSJSONReadingMutableContainers error:&error];
Touch this:
let stringData = String(data: data, encoding: NSUTF8StringEncoding)
if let strData = stringData {
let newString = strData.stringByReplacingOccurrencesOfString("\r\n", withString: "\\r\\n")
if let newString = newString {
do {
let dict:[AnyObject] = try NSJSONSerialization.JSONObjectWithData(newString.dataUsingEncoding(NSUTF8StringEncoding)!, options: [.AllowFragments, .MutableContainers]) as! [AnyObject]
}catch let e as NSError {
print(e.description)
}
}
}
Related
I have NSMutableDictionary inside this values are present like this
{
0 = {
imageangle = "0.09630692";
imageheight = "129.0245";
imagepath = "assets-library://asset/asset.PNG?id=BD849AC4-EDF2-4E05-B5F8-F5EE34385A97&ext=PNG";
imagescalex = "85.48777";
imagescaley = "85.48777";
imagewidth = "129.0245";
mainheight = 282;
mainwidth = 316;
};
memes = {
alpha = 1;
alphaFont = 1;
blue = 1;
blueFont = 0;
green = 1;
greenFont = "0.5";
memestext = "Kishore kumar kumar ";
red = 0;
redFont = 1;
textheight = 34;
textscalex = 13;
textscaley = "30.5";
textwidth = 316;
};
}
Using this i like to export this dictionary in .json file.
After that i am converting into JSONSTRING:
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dickeyjson options:NSJSONWritingPrettyPrinted error:&error];
NSString* aStr;
aStr = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
My Question:
Is it possible to export it as .json and store it to local storage?
You certainly can export a string to a .json file, and this would be an outline of a correctly error handling way to do so:
do {
let jsonString = "{\"what\":\"ever\"}"
let jsonFile = "file.json"
guard let docs = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.AllDomainsMask, true).first else {
throw NSError(domain: "what-no-Documents-folder??", code: 1, userInfo: nil)
}
let path = NSURL(fileURLWithPath: docs).URLByAppendingPathComponent(jsonFile)
try text.writeToURL(path, atomically: false, encoding: NSUTF8StringEncoding)
} catch {
fatalError("Writing failed!")
}
and to read it back in,
do {
let path = NSURL(fileURLWithPath: "same as for writing")
let jsonString = try NSString(contentsOfURL: path, encoding: NSUTF8StringEncoding)
}
catch {
fatalError("Reading failed!")
}
However, it is probable that you want to put this JSON back into a dictionary when reading, yes? In that case, there's no need to convert to JSON, you should just use NSDictionary's writeToURL method directly, using the same outline as above; as I don't believe there's any JSON-representable object not conforming to NSCoder and therefore archivable with writeToFile/writeToURL.
What exactly is the problem? You convert the dictionary to NSData using NSJSONSerialization. Then you write the NSData to a file using a variant of writeToFile: or writeToURL: There is no reason to go through NSString, it's just a waste of time, memory, and battery life.
To read the data, use dataWithContentsOfFile or dataWithContentsOfURL, and again NSJSONSerialization. If you use a mapped file, the NSData doesn't even use memory; that would be useful if your data is multiple megabytes.
I'm currently building an food tracker iOS app (using swift 2) and I would like to have a database with all the foods (and their info) stored in the app and accessible.
The idea is that when some add a 'ice cream' to their meal, his calories/sugar/fat 'counters' increase by the respective nutritional value of the ice cream. (so that this data can be processed later on)
I have found a database of food in what seems like JSON format (see bellow) but I have no idea how to process all this data with swift so that I could access the number of calories in a specific ingredient for example.
So far I tried this:
let url = NSURL(string: "myURL")
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url!, completionHandler: { (data, response, error) -> Void in
if error != nil {
print(error)
} else {
let jsonResult = (try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)) as! NSDictionary
print(jsonResult)
}
})
task.resume()
}
It allows me to process the JSON format into a dictionary that I can access but what I need (I think) would be maybe a dictionary of arrays and I can't manage to make it work with the JSON format that I have bellow.
[
{
"Description": "Juice",
"Energy(kcal)Per 100 g": 29,
},
{
"Description": "Alcoholic beverage, daiquiri, canned",
"Energy(kcal)Per 100 g": 125,
}
...
]
I admit my question wasn't quite clear at first (I'm really new at this I apologize) but I actually tried to research it on Stackoverflow before posting, but I haven't find something that works for my case. Sorry again and many thank you for taking the time to still answer it :)
Have a look into NSJSONSerialization. That is what you get for free once installed xcode and the SDK. And it is not that bad actually.
This is Ray's approach to Swifty Json:
http://www.raywenderlich.com/82706/working-with-json-in-swift-tutorial
This is what you find when you use the search. You will have to "translate" it to swift though.
How do I parse JSON with Objective-C?
You may want to look at RestKit for some more convenient way of dealing with JSON sources.
Give it a try. And when you run into concrete problems, then get back to SO.
Just give it a try
var arrDicts: [Dictionary<String, AnyObject>] = []
arrDicts = try! NSJSONSerialization.JSONObjectWithData(dataFromService!, options: NSJSONReadingOptions.AllowFragments) as! [Dictionary<String, AnyObject>]
dataFromService is the data that you have received from web service.
Answer for reference pruposes. How to do this in Objective-C
1- First get the info
a) If you are getting the JSON from an API or any online site:
//Set url of the JSON
NString *urlReq = #"http://www.URLHERE.com/PATH/TO/THE/JSON"
//Get data from the JSON
NSData *jsonData = [NSData dataWithContentsOfURL:[NSURL URLWithString:urlReq]];
//Parse JSON
if(jsonData != nil){ //If the response is nil, the next line will crash
NSArray *resultArray = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
//Do stuff with the result...
}
b) If you are getting the information from the Core Data:
//Get context
NSManagedObjectContext *context = [self managedObjectContext];
//Preapre your fetch
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Products" inManagedObjectContext:context];
NSFetchRequest *requestCoreData = [[NSFetchRequest alloc] init];
[requestCoreData setEntity:entityDescription];
//add predicates if needed
//Execute fetch
NSArray *resultArray = [context executeFetchRequest:requestCoreData error:nil];
//do stuff with the result....
2- Then parse the retrieved info
a) If you want a specific index:
NSString *description = resultArray[0][#"description"];
b) if you don't know what is the index you want (most likely what happens in your JSON):
BOOL found = NO;
int index = 0;
while(index < [resultArray count] && !found){
if([resultArray[index][#"description"] isEqualToString:#"Juice"])
found = YES;
else
++index;
}
if(found){
//'index' => index where the info you are searching can be found
}
else{
//The info couldn't be found in the array
}
I'm getting this error while parsing JSON:
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
Error Domain=NSCocoaErrorDomain Code=3840 "The operation couldn’t be completed. (Cocoa error 3840.)" (Unable to convert data to string around character 73053.) UserInfo=0x1d5d8250 {NSDebugDescription=Unable to convert data to string around character 73053.}
Any suggestions how to fix this?
ADDED
As it says in error report, the parser can't go through the character at position 73053, which is "ø" in my JSON response. As far as I know characters like Ø,Å,Æ etc. shouldn't be a problem for json parsers?
Yes, I have the same problem with encoding issue and got the above error. I got the NSData from server as encoding:NSISOLatin1StringEncoding. So I had to convert it to UTF8 before parsing it using NSJSONSerialization.
NSError *e = nil;
NSString *iso = [[NSString alloc] initWithData:d1 encoding:NSISOLatin1StringEncoding];
NSData *dutf8 = [iso dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:dutf8 options:NSJSONReadingMutableContainers error:&e];
Switf 3
let responseStrInISOLatin = String(data: data, encoding: String.Encoding.isoLatin1)
guard let modifiedDataInUTF8Format = responseStrInISOLatin?.data(using: String.Encoding.utf8) else {
print("could not convert data to UTF-8 format")
return
}
do {
let responseJSONDict = try JSONSerialization.jsonObject(with: modifiedDataInUTF8Format)
} catch {
print(error)
}
Check that the data you're parsing is actually valid JSON (and not just 'nearly' JSON). That error is known to occur when you have a different data format that can't be parsed as JSON. See for example:
iOS 5 JSON Parsing Results in Cocoa Error 3840
Do you have a top-level container in your JSON too? An array or dictionary. Example:
{ "response" : "Success" }
Update
JSON's default encoding is UTF-8. Special/exotic characters aren't a problem for UTF-8, but please ensure that your server is returning its content properly encoded as UTF-8. Also, have you done anything to tell your JSON interpretter to use a different encoding?
If your JSON is coming from a web service, put the URL into this page to see what it has to see about the encoding:
http://validator.w3.org/
Swift 5:
Yes, i got the same error while parsing JSON data.
Solution : You have to first convert response data into String and then convert that Sting to Data using UTF8 encoding before decoding.
let utf8Data = String(decoding: responseData, as: UTF8.self).data(using: .utf8)
In my iOS 5 app, I have an NSString that contains a JSON string. I would like to deserialize that JSON string representation into a native NSDictionary object.
"{\"password\" : \"1234\", \"user\" : \"andreas\"}"
I tried the following approach:
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:#"{\"2\":\"3\"}"
options:NSJSONReadingMutableContainers
error:&e];
But it throws the a runtime error. What am I doing wrong?
-[__NSCFConstantString bytes]: unrecognized selector sent to instance 0x1372c
*** Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '-[__NSCFConstantString bytes]: unrecognized selector sent to instance 0x1372c'
It looks like you are passing an NSString parameter where you should be passing an NSData parameter:
NSError *jsonError;
NSData *objectData = [#"{\"2\":\"3\"}" dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:objectData
options:NSJSONReadingMutableContainers
error:&jsonError];
NSData *data = [strChangetoJSON dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions
error:&error];
For example you have a NSString with special characters in NSString strChangetoJSON.
Then you can convert that string to JSON response using above code.
I've made a category from #Abizern answer
#implementation NSString (Extensions)
- (NSDictionary *) json_StringToDictionary {
NSError *error;
NSData *objectData = [self dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:objectData options:NSJSONReadingMutableContainers error:&error];
return (!json ? nil : json);
}
#end
Use it like this,
NSString *jsonString = #"{\"2\":\"3\"}";
NSLog(#"%#",[jsonString json_StringToDictionary]);
With Swift 3 and Swift 4, String has a method called data(using:allowLossyConversion:). data(using:allowLossyConversion:) has the following declaration:
func data(using encoding: String.Encoding, allowLossyConversion: Bool = default) -> Data?
Returns a Data containing a representation of the String encoded using a given encoding.
With Swift 4, String's data(using:allowLossyConversion:) can be used in conjunction with JSONDecoder's decode(_:from:) in order to deserialize a JSON string into a dictionary.
Furthermore, with Swift 3 and Swift 4, String's data(using:allowLossyConversion:) can also be used in conjunction with JSONSerialization's jsonObject(with:options:) in order to deserialize a JSON string into a dictionary.
#1. Swift 4 solution
With Swift 4, JSONDecoder has a method called decode(_:from:). decode(_:from:) has the following declaration:
func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable
Decodes a top-level value of the given type from the given JSON representation.
The Playground code below shows how to use data(using:allowLossyConversion:) and decode(_:from:) in order to get a Dictionary from a JSON formatted String:
let jsonString = """
{"password" : "1234", "user" : "andreas"}
"""
if let data = jsonString.data(using: String.Encoding.utf8) {
do {
let decoder = JSONDecoder()
let jsonDictionary = try decoder.decode(Dictionary<String, String>.self, from: data)
print(jsonDictionary) // prints: ["user": "andreas", "password": "1234"]
} catch {
// Handle error
print(error)
}
}
#2. Swift 3 and Swift 4 solution
With Swift 3 and Swift 4, JSONSerialization has a method called jsonObject(with:options:). jsonObject(with:options:) has the following declaration:
class func jsonObject(with data: Data, options opt: JSONSerialization.ReadingOptions = []) throws -> Any
Returns a Foundation object from given JSON data.
The Playground code below shows how to use data(using:allowLossyConversion:) and jsonObject(with:options:) in order to get a Dictionary from a JSON formatted String:
import Foundation
let jsonString = "{\"password\" : \"1234\", \"user\" : \"andreas\"}"
if let data = jsonString.data(using: String.Encoding.utf8) {
do {
let jsonDictionary = try JSONSerialization.jsonObject(with: data, options: []) as? [String : String]
print(String(describing: jsonDictionary)) // prints: Optional(["user": "andreas", "password": "1234"])
} catch {
// Handle error
print(error)
}
}
Using Abizern code for swift 2.2
let objectData = responseString!.dataUsingEncoding(NSUTF8StringEncoding)
let json = try NSJSONSerialization.JSONObjectWithData(objectData!, options: NSJSONReadingOptions.MutableContainers)
I have UTF-8 encoded NSData from windows server and I want to convert it to NSString for iPhone. Since data contains characters (like a degree symbol) which have different values on both platforms, how do I convert data to string?
If the data is not null-terminated, you should use -initWithData:encoding:
NSString* newStr = [[NSString alloc] initWithData:theData encoding:NSUTF8StringEncoding];
If the data is null-terminated, you should instead use -stringWithUTF8String: to avoid the extra \0 at the end.
NSString* newStr = [NSString stringWithUTF8String:[theData bytes]];
(Note that if the input is not properly UTF-8-encoded, you will get nil.)
Swift variant:
let newStr = String(data: data, encoding: .utf8)
// note that `newStr` is a `String?`, not a `String`.
If the data is null-terminated, you could go though the safe way which is remove the that null character, or the unsafe way similar to the Objective-C version above.
// safe way, provided data is \0-terminated
let newStr1 = String(data: data.subdata(in: 0 ..< data.count - 1), encoding: .utf8)
// unsafe way, provided data is \0-terminated
let newStr2 = data.withUnsafeBytes(String.init(utf8String:))
You could call this method
+(id)stringWithUTF8String:(const char *)bytes.
I humbly submit a category to make this less annoying:
#interface NSData (EasyUTF8)
// Safely decode the bytes into a UTF8 string
- (NSString *)asUTF8String;
#end
and
#implementation NSData (EasyUTF8)
- (NSString *)asUTF8String {
return [[NSString alloc] initWithData:self encoding:NSUTF8StringEncoding];
}
#end
(Note that if you're not using ARC you'll need an autorelease there.)
Now instead of the appallingly verbose:
NSData *data = ...
[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
You can do:
NSData *data = ...
[data asUTF8String];
The Swift version from String to Data and back to String:
Xcode 10.1 • Swift 4.2.1
extension Data {
var string: String? {
return String(data: self, encoding: .utf8)
}
}
extension StringProtocol {
var data: Data {
return Data(utf8)
}
}
extension String {
var base64Decoded: Data? {
return Data(base64Encoded: self)
}
}
Playground
let string = "Hello World" // "Hello World"
let stringData = string.data // 11 bytes
let base64EncodedString = stringData.base64EncodedString() // "SGVsbG8gV29ybGQ="
let stringFromData = stringData.string // "Hello World"
let base64String = "SGVsbG8gV29ybGQ="
if let data = base64String.base64Decoded {
print(data) // 11 bytes
print(data.base64EncodedString()) // "SGVsbG8gV29ybGQ="
print(data.string ?? "nil") // "Hello World"
}
let stringWithAccent = "Olá Mundo" // "Olá Mundo"
print(stringWithAccent.count) // "9"
let stringWithAccentData = stringWithAccent.data // "10 bytes" note: an extra byte for the acute accent
let stringWithAccentFromData = stringWithAccentData.string // "Olá Mundo\n"
Sometimes, the methods in the other answers don't work. In my case, I'm generating a signature with my RSA private key and the result is NSData. I found that this seems to work:
Objective-C
NSData *signature;
NSString *signatureString = [signature base64EncodedStringWithOptions:0];
Swift
let signatureString = signature.base64EncodedStringWithOptions(nil)
Just to summarize, here's a complete answer, that worked for me.
My problem was that when I used
[NSString stringWithUTF8String:(char *)data.bytes];
The string I got was unpredictable: Around 70% it did contain the expected value, but too often it resulted with Null or even worse: garbaged at the end of the string.
After some digging I switched to
[[NSString alloc] initWithBytes:(char *)data.bytes length:data.length encoding:NSUTF8StringEncoding];
And got the expected result every time.
With Swift 5, you can use String's init(data:encoding:) initializer in order to convert a Data instance into a String instance using UTF-8. init(data:encoding:) has the following declaration:
init?(data: Data, encoding: String.Encoding)
Returns a String initialized by converting given data into Unicode characters using a given encoding.
The following Playground code shows how to use it:
import Foundation
let json = """
{
"firstName" : "John",
"lastName" : "Doe"
}
"""
let data = json.data(using: String.Encoding.utf8)!
let optionalString = String(data: data, encoding: String.Encoding.utf8)
print(String(describing: optionalString))
/*
prints:
Optional("{\n\"firstName\" : \"John\",\n\"lastName\" : \"Doe\"\n}")
*/