158

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'
zekel
  • 9,227
  • 10
  • 65
  • 96
Andreas
  • 397
  • 4
  • 18
  • 37
  • That was my approach: NSDictionary *JSON = [NSJSONSerialization JSONObjectWithData: @"{\"2\":\"3\"}" options: NSJSONReadingMutableContainers error: &e]; i get: 2011-12-22 17:18:59.300 Pi9000[938:13803] -[__NSCFConstantString bytes]: unrecognized selector sent to instance 0x1372c 2011-12-22 17:18:59.302 Pi9000[938:13803] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFConstantString bytes]: unrecognized selector sent to instance 0x1372c' – Andreas Dec 22 '11 at 16:19
  • See [my answer](https://stackoverflow.com/a/44337865/1966109) that shows two different ways to deserialize a JSON string into an dictionary for Swift 3 and Swift 4. – Imanou Petit Jul 10 '17 at 18:10

5 Answers5

342

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];
zekel
  • 9,227
  • 10
  • 65
  • 96
Abizern
  • 146,289
  • 39
  • 203
  • 257
  • @Abizem, what error could I use here? (op doesn't mention it) –  Apr 17 '13 at 09:41
  • Thanks, it worked. However, using `nil` as error instead of `&e` in XCode 5 – Michael Ho Chum Aug 03 '14 at 08:00
  • 3
    I like Objective C. Encode your string to raw bytes and then decode them back to NSStrings and NSNumbers. This is obvious, isn't it? – vahotm Jan 12 '15 at 10:55
  • If you had a dictionary or an array it would be simpler. Not many people write JSON by hand. – Abizern Jan 12 '15 at 19:46
  • 1
    @Abizern its common to receive JSON as a string from somewhere outside of your application – Chicowitz Nov 09 '17 at 00:31
  • is there a more effective solution? [NSString dataUsingEncoing] will copy/translate the contents of the NSString - which in JSON case may be quite big. This method (works) but costs memory - double of the JSON string (at least) – Motti Shneor Apr 02 '19 at 07:37
37
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.

OrangeDog
  • 36,653
  • 12
  • 122
  • 207
Desert Rose
  • 3,376
  • 1
  • 30
  • 36
6

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]);
Hemang
  • 26,840
  • 19
  • 119
  • 186
  • It's my understanding that it's best practice to not test `error` in these cases, but instead to test if the return value is nil or not before returning. i.e. `return json ?: nil;` Minor nitpick, but worth mentioning, I think. – Mike Jan 06 '16 at 17:04
  • @Mike, I think it's Ok to check for "error" regardless of the value? Because, if there's an error then we are returning `nil` right away. – Hemang Jan 06 '16 at 18:30
  • According to Apple's docs "When dealing with errors passed by reference, it’s important to test the return value of the method to see whether an error occurred, as shown above. Don’t just test to see whether the error pointer was set to point to an error." https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/ErrorHandling/ErrorHandling.html I believe this is because there could be cases where an error doesn't occur and the method returns a value, but the memory where the error pointer points to is written to, so you falsely think an error exists. – Mike Jan 06 '16 at 18:33
  • I was schooled in a prior question of mine: "The variable is uninitialized. That means the value at that address is undefined, so changing the value isn't going to mean anything...Since there's no guarantee the method won't write garbage into the address if an error doesn't occur, Apple's docs say that it's unsafe to test the value of the error variable." http://stackoverflow.com/questions/25558442/can-reusing-the-same-nserror-object-be-problematic – Mike Jan 06 '16 at 18:35
  • 1
    @Mike, oh great, good to know! Thanks for the references. I will update this soon. – Hemang Jan 06 '16 at 18:39
5

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 json​Object(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 json​Object(with:​options:​). json​Object(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 json​Object(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)
    }
}
Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
4

Using Abizern code for swift 2.2

let objectData = responseString!.dataUsingEncoding(NSUTF8StringEncoding)
let json = try NSJSONSerialization.JSONObjectWithData(objectData!, options: NSJSONReadingOptions.MutableContainers)
IOS Singh
  • 617
  • 6
  • 15