How can you deserialize an escaped JSON string with NSJSONSerialization?
If you have nested JSON, then just call JSONObjectWithData
twice:
NSString *string = @"\"{ \\\"name\\\" : \\\"Bob\\\", \\\"age\\\" : 21 }\"";
// --> the string
// "{ \"name\" : \"Bob\", \"age\" : 21 }"
NSError *error;
NSString *outerJson = [NSJSONSerialization JSONObjectWithData:[string dataUsingEncoding:NSUTF8StringEncoding]
options:NSJSONReadingAllowFragments error:&error];
// --> the string
// { "name" : "Bob", "age" : 21 }
NSDictionary *innerJson = [NSJSONSerialization JSONObjectWithData:[outerJson dataUsingEncoding:NSUTF8StringEncoding]
options:0 error:&error];
// --> the dictionary
// { age = 21; name = Bob; }
Serialize JSON string that contains escaped (backslash and double quote) Swift return Badly formed object
First of all if you wrap the JSON in the literal string syntax of Swift 4 you have to escape the backslashes.
let jsonStr = """
{
"status": "success",
"data": "{\\"name\\":\\"asd\\",\\"address\\":\\"Street 1st\\"}"
}
"""
You got nested JSON. The value for key data
is another JSON string which must be deserialized separately
let jsonData = Data(jsonStr.utf8)
do {
if let object = try JSONSerialization.jsonObject(with: jsonData) as? [String:String] {
print(object)
if let dataString = object["data"] as? String {
let dataStringData = Data(dataString.utf8)
let dataObject = try JSONSerialization.jsonObject(with: dataStringData) as? [String:String]
print(dataObject)
}
}
} catch {
print(error)
}
Or – with a bit more effort but – much more comfortable with the (De)Codable
protocol
struct Response : Decodable {
private enum CodingKeys : String, CodingKey { case status, data }
let status : String
let person : Person
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
status = try container.decode(String.self, forKey: .status)
let dataString = try container.decode(String.self, forKey: .data)
person = try JSONDecoder().decode(Person.self, from: Data(dataString.utf8))
}
}
struct Person : Decodable {
let name, address : String
}
let jsonStr = """
{
"status": "success",
"data": "{\\"name\\":\\"asd\\",\\"address\\":\\"Street 1st\\"}"
}
"""
let jsonData = Data(jsonStr.utf8)
do {
let result = try JSONDecoder().decode(Response.self, from: jsonData)
print(result)
} catch {
print(error)
}
How to convert BOOL to JSON with NSJSONSerialization?
Use NSNumber
:
@{
@"foo": @YES
}
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).
NSDictionary to JSON with NSJSONSerialization issue
You can do this, however if your data has two back slashes, they will be removed. If you can guarantee your data doesn't have two back slashes, this is OK.
NSString *newString = [[yourDict objectForKey:@"endDate"]
stringByReplacingOccurrencesOfString:@"\\" withString:@""];
JSON encoding with backslashes
// This Dropbox url is a link to your JSON
// I'm using NSData because testing in Playground
if let data = NSData(contentsOfURL: NSURL(string: "https://www.dropbox.com/s/9ycsy0pq2iwgy0e/test.json?dl=1")!) {
var error: NSError?
var response: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.allZeros, error: &error)
if let dict = response as? NSDictionary {
if let key = dict["d"] as? String {
let strData = key.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
var error: NSError?
var response: AnyObject? = NSJSONSerialization.JSONObjectWithData(strData!, options: NSJSONReadingOptions.allZeros, error: &error)
if let decoded = response as? NSDictionary {
println(decoded["IsSuccess"]!) // => 1
}
}
}
}
I guess you have to decode twice: the wrapping object, and its content.
IOS JSON Deserialization failure - STIG/NSJSONSerializer
Ok ,the problem was the escape character and the quote character \", this is why when copying and pasting it as hard code it worked because when hardcoded the compiler reads only the quote " character.
This can be very annoying if someone gets into this issue in the future :
The problem is that the server url encoded the data (C# server side) , IOS url decode is known for its weakness and apprenlty does not remove the \" character from the string.
This was my current IOS decoding code :
json = [json stringByReplacingOccurrencesOfString:@"+" withString:@" "];
json = [json stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
json = [json stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:@""];
int lastChar = [json length]-1;
json = [json stringByReplacingCharactersInRange:NSMakeRange(lastChar,1) withString:@""];
json = [json stringByReplacingOccurrencesOfString:@"\\" withString:@""];
return json;
If first remove the quotes from the beginning and end of the json , then I remove all the \ chars before the " chars.
This appears to be working smoothly now , if you think I am doing something wrong please correct me .
Hope that will help someone someday.
Cheers ,
James
Related Topics
How to Get the Front Camera in Swift
How to Get Notified in Contact Changed Event in iOS
How to Remove Border from Segmented Control
How to Draw a Point Using Core Graphics
Prevent Dispatch_After() Background Task from Being Executed
How Reliable Is Kvo with Uikit
How to Broadcast Multiple Ibeacon Signals from Only One Bluetooth? and How
Reading Text and Images from a PDF Document in iOS
Undo with Multitouch Drawing in iOS
Hide the Cursor of a Uitextfield
Find Uialertview Without Having Reference to It iOS 7
Perform a Deeplink from Swiftui Widget on Tap
Differentiate Between Screen Lock and Home Button Press on iOS7
iOS 11 Uibarbuttonitem Images Not Sizing
Autolayout Views Make App Crash on Popviewcontroller
New Itunes Connect Interface -- Should It Immediately Be Seen on "Prerelease"