How to use NSJSONSerialization
Your root json object is not a dictionary but an array:
[{"id": "1", "name":"Aaa"}, {"id": "2", "name":"Bbb"}]
This might give you a clear picture of how to handle it:
NSError *e = nil;
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData: data options: NSJSONReadingMutableContainers error: &e];
if (!jsonArray) {
NSLog(@"Error parsing JSON: %@", e);
} else {
for(NSDictionary *item in jsonArray) {
NSLog(@"Item: %@", item);
}
}
NSJSONSerialization from NSString
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];
JSON parsing using NSJSONSerialization in iOS
First of all in your JSON
response dictionary
, under the key 'RESPONSE' you have a array not a dictionary
and that array contains dictionary
object.
So to extract username and email ID so as below
NSMutableDictionary *response = [[[json valueForKey:@"RESPONSE"] objectAtIndex:0]mutableCopy];
NSString *username = [response valueForKey:@"username"];
NSString *emailId = [response valueForKey:@"email"];
NSJSONSerialization to plain old object?
Here is my solution, which is not based on a library - as I couldn't find any - but instead using the Foundation and Objective-C runtime methods - as discussed in the comments above:
#import <objc/runtime.h>
NSArray<NSString*>* classPropertyList(id instance) {
NSMutableArray* propList = [NSMutableArray array];
unsigned int numProps = 0;
objc_property_t* props = class_copyPropertyList(object_getClass(instance), &numProps);
for (int i = 0; i < numProps; i++)
[propList addObject:[NSString stringWithUTF8String:property_getName(props[i])]];
free(props);
return propList;
}
NSString* typeOfProperty(Class clazz, NSString* propertyName) {
objc_property_t prop = class_getProperty(clazz, [propertyName UTF8String]);
NSArray<NSString*>* propAttrs = [[NSString stringWithUTF8String:property_getAttributes(prop)] componentsSeparatedByString:@","];
if ([(propAttrs[0]) hasPrefix:@"T@\""])
return [propAttrs[0] componentsSeparatedByString:@"\""][1];
return nil;
}
@implementation JSONMarshallable
- (NSData*)toJSON {
return [self toJSON:self withNullValues:YES];
}
- (NSString*)toJSONString {
return [self toJSONString:self withNullValues:YES];
}
- (NSData*)toJSON:_ withNullValues:(bool)nullables {
NSError* error;
NSDictionary* dic = [self toDictionary:self withNullValues:nullables];
NSData* json = [NSJSONSerialization dataWithJSONObject:dic options:0 error:&error];
if (!json) {
NSLog(@"Error encoding DeviceConfigurationRequest: %@", error);
return nil;
}
return json;
}
- (NSString*) toJSONString:_ withNullValues:(bool)nullables {
NSData* json = [self toJSON:self withNullValues:nullables];
return [[NSString alloc] initWithBytes:[json bytes] length:[json length] encoding:NSUTF8StringEncoding];
}
- (NSDictionary*)toDictionary:_ withNullValues:(bool)nullables {
NSMutableDictionary* dic = [NSMutableDictionary new];
for (id propName in classPropertyList(self)) {
id val = [self valueForKey:propName];
if (!nullables && (val == nil || val == NSNull.null))
continue;
if ([val respondsToSelector:@selector(toDictionary:withNullValues:)])
val = [val toDictionary:val withNullValues:nullables];
[dic setObject:(val == nil ? NSNull.null : val) forKey:propName];
}
return dic;
}
- (instancetype)initWithJSONString:(NSString*)json {
return [self initWithJSON:[json dataUsingEncoding:NSUTF8StringEncoding]];
}
- (instancetype)initWithJSON:(NSData*)json {
NSError* error;
if (json == nil)
return nil;
NSDictionary* dataValues = [NSJSONSerialization JSONObjectWithData:json options:0 error:&error];
if (!dataValues) {
NSLog(@"Error parsing invalid JSON for %@: %@", NSStringFromClass(object_getClass(self)), error);
return nil;
}
return [self initWithDictionary:dataValues];
}
- (instancetype)initWithDictionary:(NSDictionary*)dataValues {
if (dataValues == nil)
return nil;
if (self = [super init])
for (id key in dataValues) {
id val = [dataValues objectForKey:key];
if (![self respondsToSelector:NSSelectorFromString(key)])
continue;
NSString* typeName = typeOfProperty([self class], key);
if ([val isKindOfClass:[NSNull class]]) { // translate NSNull values to something useful, if we can
if (typeName == nil)
continue; // don't try to set nil to non-pointer fields
val = nil;
} else if ([val isKindOfClass:[NSDictionary class]] && typeName != nil)
val = [[NSClassFromString(typeName) alloc] initWithDictionary:val];
[self setValue:val forKey:key];
}
return self;
}
@end
It is then easy to create custom model objects by inheriting from JSONMarshallable
, like so:
model.h
:
#import "JSONMarshallable.h"
@interface MyModel : JSONMarshallable
@property NSString* stringValue;
@property NSNumber* numericValue;
@property bool boolValue;
@end
model.m
:
@implementation MyModel
@end
SomeThingElse.m
:
// ...
NSData* someJson;
MyModel* obj = [[MyModel alloc] initWithJSON:someJson];
NSString* jsonObj = [obj toJSONString:nil withNullValues:NO];
Critics are welcome! (I'm not very good at Objective C and probably made a lot of faux pas )
Issues:
- I can handle nullable numbers with
NSNumber*
(though C primitives work fine for non-nullable numbers), but I don't know how to represent nullable booleans - i.e. a field that is optional and not encoded when usingwithNullValues:NO
. Sending fields for which there are no properties (for example, the server I work with sends values in both snake-case and underscrore-case to make it easy to parse) throws exception.(solved by usingrespondsToSelector:
andsetValue:
instead ofsetValuesForKeysWithDictionary:
).Trying to set(solved by checking for property type andnil
values to primitive-typed fields causes exceptions.NSNull
).Doesn't work at all for nesting objects - i.e. a custom model object with properties that are also custom model objects.(solved by checking for property types and recursing encoding/decoding).- Probably doesn't handle arrays well - I have yet to need those in my software, so I haven't implemented proper support (though I verified that encoding simple string arrays works well).
Swift, NSJSONSerialization and NSError
The problem is that you cast the result of the JSON deserialization before
checking for an error. If the JSON data is invalid (e.g. incomplete) then
NSJSONSerialization.JSONObjectWithData(...)
returns nil
and
NSJSONSerialization.JSONObjectWithData(...) as NSDictionary
will crash.
Here is a version that checks for the error conditions correctly:
var error:NSError? = nil
if let jsonObject: AnyObject = NSJSONSerialization.JSONObjectWithData(receivedData, options: nil, error:&error) {
if let dict = jsonObject as? NSDictionary {
println(dict)
} else {
println("not a dictionary")
}
} else {
println("Could not parse JSON: \(error!)")
}
Remarks:
- The correct way to check for an error is to test the return value, not the
error variable. The JSON reading option
.AllowFragments
does not help here. Setting this option
only allows that top-level objects that are not an instance ofNSArray
orNSDictionary
, for example{ "someString" }
You can also do it in one line, with an optional cast as?
:
if let dict = NSJSONSerialization.JSONObjectWithData(receivedData, options: nil, error:nil) as? NSDictionary {
println(dict)
} else {
println("Could not read JSON dictionary")
}
The disadvantage is that in the else
case you cannot distinguish whether reading
the JSON data failed or if the JSON did not represent a dictionary.
For an update to Swift 3, see LightningStryk's answer.
NSJSONSerialization adding \n to beginning and end of JSON string
First, the output is absolutely correct. It is fine to add spaces and newlines in JSON in the right places. And JSON parser will be able to parse it.
Why does it happen? Because you were absolutely careless in your cde. Look at the option that you pass. It is an option that is used when you parse JSON, not when you write JSON. Why are you passing an option for reading JSON to a method that writes JSON? Now go and look at the writing options, and look which writing option has exactly the same value as the reading option that you passed.
NSJSONSerialization.JSONObjectWithData changes field type
It doesn't. The JSON deserialisation respects the data type and will maintain it. You can't tell the data type from a simple description log, you need to actually interrogate the class. The description log will quote some things if it makes more sense for the human reader, like spaces in the description, but it also omits quotes in some cases.
NSJSONSerialization with C# Rest Service
Your JSON string is missing a closing ]
somewhere.
Assuming the string you posted is exactly what was returned by the server, you will also probably need to remove the backslashes in order to make it valid JSON.
NSString *responseString = [[[NSString alloc] initWithData:response] stringByReplacingOccurrencesOfString:@"\\" withString:@""];
NSDictionary *fields = [NSJSONSerialization JSONObjectWithData:responseString options:kNilOptions error:&error];
How to convert BOOL to JSON with NSJSONSerialization?
Use NSNumber
:
@{
@"foo": @YES
}
Related Topics
How to Download Multiple Files Sequentially Using Nsurlsession Downloadtask in Swift
What Is Silent Push Notification? When Does the Device Receive It
Firebase - Deleting and Reinstalling App Does Not Un-Authenticate a User
Passing Data With Unwind Segue
Uicolor Not Working With Rgba Values
How to Sort an Nsmutablearray With Custom Objects in It
How to Create Delegates in Objective-C
Loading/Downloading Image from Url on Swift
Symbolicating Iphone App Crash Reports
How to Develop or Migrate Apps For Iphone 5 Screen Resolution
Ios: How to Store Username/Password Within an App
Ios 7 Status Bar Back to iOS 6 Default Style in Iphone App
Evenly Space Multiple Views Within a Container View
Iphone Get Ssid Without Private Library
Exc_Bad_Access Signal Received
How to Scroll List Programmatically in Swiftui