How to Deserialize an Escaped JSON String with Nsjsonserialization

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 using withNullValues: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 using respondsToSelector: and setValue: instead of setValuesForKeysWithDictionary:).
  • Trying to set nil values to primitive-typed fields causes exceptions. (solved by checking for property type and 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



Leave a reply



Submit