Better JSON parsing implementation in Objective-c and best practices? - ios

I am receiving below JSON response for saving user preferences in core data.
preferences={
1={
children=({
3=Samsung;4=Nokia;
});id=1;name=Mobiles;
};2={
children=({
5="Samsung Curve TV";
});id=2;name=Electronics;
};
};
Here is my code snippet which is working fine. But I think this is much verbose code.
NSLog(#"Preferences: %#", [response objectForKey:#"preferences"]);
for (NSDictionary *dic in [response objectForKey:#"preferences"]) {
NSLog(#"ID: %#", [[[response objectForKey:#"preferences"] objectForKey:dic] objectForKey:#"id"]);
NSLog(#"NAME: %#", [[[response objectForKey:#"preferences"] objectForKey:dic] objectForKey:#"name"]);
NSLog(#"Children DIC: %#", [[[[[response objectForKey:#"preferences"]
objectForKey:dic] objectForKey:#"children"] objectAtIndex:0] objectForKey:#"3"]);
for (NSDictionary *childDic in [[[[response objectForKey:#"preferences"]
objectForKey:dic] objectForKey:#"children"] objectAtIndex:0]) {
NSLog(#"Child Name: %#", [[[[[response objectForKey:#"preferences"]
objectForKey:dic] objectForKey:#"children"] objectAtIndex:0] objectForKey:childDic]);
}
}
I have 3 questions.
How can I improve my code snippet? is there any shorter way to implement this?
Is this JSON response is good enough for mobile end parsing? is it good JSON format? Is there any JSON response format we should follow as Mobile developers in terms of using Core data (which simply reduces DB implementation as best practice)?
How do I construct a JSON string like this again from Objective-c?

Ok so, (Sorry for NOT deep analysis) first of all I'd modify your JSON not to contain dictionary for dynamic elements (samsung has key 3 and so on, it should be array, make sense?)
I come up with imho better JSON structure:
{
"preferences":[
{
"items":[
{
"id" : "3",
"name" : "Samsung"
},
{
"id" : "3",
"name" : "Nokia"
}
],
"id":"1",
"name":"Mobiles"
},
{
"items":[
{
"id" : "3",
"name" : "Nokia"
}
],
"id":"2",
"name":"Electronics"
}
]
}
It's really easy now to map it to objects with JSONModel and only one thing you should care about is mapping keys.
NSString *JSONString = #" { \"preferences\":[ { \"items\":[ { \"id\" : \"3\", \"name\" : \"Samsung\" }, { \"id\" : \"3\", \"name\" : \"Nokia\" } ], \"id\":\"1\", \"name\":\"Mobiles\" }, { \"items\":[ { \"id\" : \"3\", \"name\" : \"Nokia\" } ], \"id\":\"2\", \"name\":\"Electronics\" } ] }";
NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:[JSONString dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
NSError *mapError;
GlobalPreferences *globalPreferences = [[GlobalPreferences alloc] initWithDictionary:dictionary error:&mapError];
if (mapError) {
NSLog(#"Something went wrong with mapping model %#", mapError);
}
NSLog(#"Your mapped model: %#", globalPreferences);
Models:
Kinda GlobalPreference, root model, in case if u decide to add something extra
#import <JSONModel/JSONModel.h>
#import "Preference.h"
#interface GlobalPreferences : JSONModel
#property (nonatomic, strong) NSArray<Preference> *preferences; // using protocol here you specifying to which model data should be mapped
#property (nonatomic, strong) NSArray<Optional> *somethingElse; // some other settings may be here
#end
#import "GlobalPreferences.h"
#implementation GlobalPreferences
#end
Preference
#import <JSONModel/JSONModel.h>
#import "PreferenceItem.h"
#protocol Preference <NSObject>
#end
#interface Preference : JSONModel
#property (nonatomic, strong) NSNumber *ID; // required
#property (nonatomic, strong) NSString *name; // required
#property (nonatomic, strong) NSArray<PreferenceItem> *items;
#end
#import "Preference.h"
#implementation Preference
#pragma mark - JSONModel
+ (JSONKeyMapper *)keyMapper {
return [[JSONKeyMapper alloc] initWithDictionary:#{
#"id": #"ID",
}];
}
#end
PreferenceItem
#import <JSONModel/JSONModel.h>
// This protocol just to let JSONModel know to which model needs to be parsed data in case if it's an array/dictionary
#protocol PreferenceItem <NSObject>
#end
#interface PreferenceItem : JSONModel
#property (nonatomic, strong) NSNumber *ID;
#property (nonatomic, strong) NSString *name;
#end
#import "PreferenceItem.h"
#implementation PreferenceItem
#pragma mark - JSONModel
+ (JSONKeyMapper *)keyMapper {
return [[JSONKeyMapper alloc] initWithDictionary:#{
#"id": #"ID",
}];
}
#end
Should be ok for coreData.
Maybe for you case all this is not necessary, but there are lot of things you need to care when u parsing/mapping network response such es data types, missing keys, error handling and so on. If u map it manually - you risk to have broken app some day.

I know you asked best practices for Objective-C, but I would suggest switching to Swift and using SwiftyJSON. With Swift the code is much more readable than with Objective-C.
let prefs = json["preferences"]
let userName = prefs["id"].stringValue
let name = prefs["name"].stringValue
let child = prefs["children"].arrayValue[0].arrayValue[3]
https://github.com/SwiftyJSON/SwiftyJSON
You can easily build your own JSON structures and send them with Alamofire:
let parameters: [String : AnyObject] = [
"preferences": [
"id": "123",
"name": "John",
"children": [
["3": "Three"]
]
]
]
let urlReq = NSMutableURLRequest(url)
urlReq.HTTPMethod = .POST
let req = Alamofire.ParameterEncoding.JSON.encode(urlReq, parameters: parameters).0
let request = Alamofire.request(req)
https://github.com/Alamofire/Alamofire

Related

How to declare optional property in Mantle?

Specific situation detail:
In my current photo app, There are such situation : allow user experience first without log in,
but no have some field(e.g:voted,purchased ,etc) in the JSON responded from backend. If user logged in,
the field is added in JSON .
I'll show off my implement details as following:
PhotoListResponseModel (directly inherit MTLModel)
#interface PhotoListResponseModel : MTLModel <MTLJSONSerializing>
...
#property (nonatomic, copy, readonly) NSNumber *foo;
#property (nonatomic, copy, readonly) NSArray<PhotoModel *> *subPhotos;
#end
#interface PhotoModel : MTLModel <MTLJSONSerializing>
#property (nonatomic, copy, readonly) NSNumber *photoID;
#property (nonatomic, copy, readonly) NSURL *imageUrl;
...
#end
#implementation PhotoListResponseModel
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return #{#"foo": #"foo"
#"subPhotos": #"photos"
};
}
+ (NSValueTransformer *)subPhotosJSONTransformer {
return [MTLJSONAdapter arrayTransformerWithModelClass:PhotoModel.class];
}
#end
#implementation PhotoModel
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return #{
#"photoID": #"id",
#"userID": #"user_id",
};
}
#end
Correspondly, the JSON example as following:
noAuthPhoto.json
{
"foo": 1,
"photos": [
{
"id": 204748881,
"image_url": "https://foo1/bar1",
},
{
"id": 204996257,
"image_url": "https://foo2/bar2"
}
],
…
…
}
AuthedPhoto.json
{
"foo": 1,
"photos": [
{
"id": 204748881,
"image_url": "https://foo1/bar2”,
"voted": false,
"purchased": false
},
{
"id": 204996257,
"image_url": "https://foo2/bar2”,
"voted": false,
"purchased": false
}
],
...
...
}
So, how let new field can compatible my already existing code?
Optional Property? (I not know how to do this.)
Or add a subclass of inherit from PhotoListResponseModel & PhotoModel?
Or any good idea : )
Update: I noticed this information,but still don't know how to do it.
Optional properties are only available in Swift, but similar idea for the Obj-C — objects with possible nil value — should work well. You can add the following NSNumber properties to your PhotoModel:
#property (nonatomic, strong) NSNumber *voted;
#property (nonatomic, strong) NSNumber *purchased;
And assign your BOOL values from parsed JSON during PhotoModel object creation like this:
photoModelObject.voted = [NSNumber numberWithBool:[parsedJson valueForKey:#"voted"]];
photoModelObject.purchased = [NSNumber numberWithBool:[parsedJson valueForKey:#"purchased"]];
Next, when you reference your photo object, just check if these properties have nil value. If they are — your user is not logged in and you don't need to show them. Otherwise, extract BOOL value from them like this:
if (photoModelObject.voted && photoMobelObject.purchased) {
BOOL isVoted = [photoModelObject.voted boolValue];
BOOL isPurchased = [photoModelObject.purchased boolValue];
// Use booleans to present info for registered users
} else {
// There is no info about votes and purchasing provided for the current user
}

Encoding working on a NSString but not on others

Well this is bizarre and I can't find any explanation.
The issue is that I am "transforming" a object property to a value into a NSDictionary and in a specific value it "encodes" a "special character" but on other values it doesn't.
This is the .h file for the object where I declare the properties:
#import <CoreLocation/CoreLocation.h>
#import "ModelObject.h"
#interface CheckinObject : ModelObject
#property (nonatomic,strong) NSString *name;
#property (nonatomic,strong) NSString *num;
#property (nonatomic,strong) NSString *date;
This is the .m file, where I synthesize and put it into a NSDictionary:
#implementation CheckinObject
#synthesize name;
#synthesize num;
#synthesize date;
//bunch of other uninteresting code
- (NSDictionary*)toDictionary {
NSLog(#"=> self.num : %#", self.num);
NSDictionary *dic = #{
#"date" :self.date,
#"name" :self.name,
#"num" :self.num
};
NSLog(#"=> dic.num : %#", dic[#"num"]);
NSLog(#"=> dic : %#", dic);
return dic;
}
This is the output:
2016-05-25 21:03:44.333 FoodTruckApp[1320:614394] => self.num : 23–187
2016-05-25 21:03:44.334 FoodTruckApp[1320:614394] => dic.num : 23–187
2016-05-25 21:03:44.335 FoodTruckApp[1320:614394] => dic : {
date = "2016-05-25 21:03:44";
name = "Alameda dos Carvalhos";
num = "23\U2013187";
}
Any thought on what I might be missing here?
Cheers.

Mantle modelOfClass json parsing

I have the following Mantle object & want to convert the dictionary to Mantle object. but the conversion always returns empty MTLModel object.
Any ideas?
//ABC.h
#interface ABC : MTLModel<MTLJSONSerializing>
#property (nonatomic, strong) NSString *Identifier;
#property (nonatomic, strong) NSString *Name;
#property (nonatomic, strong) NSString *ImageURLString;
#property (nonatomic, strong) NSString *Desc;
#property (nonatomic, strong) NSString *lastUpdated;
#end
// ABC.m
#implementation ABC
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return #{
#"Identifier" : #"id",
#"Name" : #"name",
#"Desc" : #"desc",
#"ImageURLString" : #"image_url",
#"lastUpdated" : #"last_updated_at"
};
}
#end
Calling code:
ABC *abc = [MTLJSONAdapter modelOfClass:ABC.class fromJSONDictionary:tempDict error:NULL];
The JSON dictionary is:
{
desc = "asdadasdakjqewqwmsdnasdaksdasd";
id = adsasdasdasdasd;
"image_url" = "http://upload.wikimedia.org/wikipedia/commons/thumb/1/19/abc.JPG";
"last_updated_at" = "2015-07-25 04:22:39.851710";
name = asdasdqwe;
}
The result ABC object has no contents. Can't figure what I am missing here.
Any ideas???
Try with this snippet and debug the adapterError. May have relevant information about the conversion from NSDictionary to Mantle object
//Create Mantle object from NSDictionary using MTLJSONSerialization
NSError *adapterError;
MTLModel *user = [MTLJSONAdapter modelOfClass:MTLModel.class fromJSONDictionary:dictionary error:&adapterError];
if(adapterError) {
PrintError(adapterError)
return nil;
}
id = adsasdasdasdasd;
needs to be
id = "adsasdasdasdasd";

get subTree of JSON using github-mantle

I'm trying to get the subnodes of a JSON file usning githubs mantle.
This is what I tried:
JSON
"countries": {
"name": "germany",
"population": "80620000000",
"populationInCities": {
"Hamburg": 1799000,
"Berlin": 3502000,
"Munich": 1378000
}
}
CountryInfo.h
#import <Mantle/Mantle.h>
#interface CountryInfo : MTLModel <MTLJSONSerializing>
#property (nonatomic, readonly, copy) NSString *collectionName;
#property (nonatomic, readonly, assign) NSUInteger cPopulation;
#property (nonatomic, readonly, copy) NSArray *populationInCities;
#end
CountryInfo.m
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return #{ #"cName": #"name",
#"cPopulation": #"population",
#"populationInCities": [NSSet setWithArray:#[ #"Hamburg", #"Hamburg", #"Hamburg"]]
};
}
+ (NSValueTransformer *)populationInCitiesJSONTransformer {
return [NSValueTransformer mtl_JSONArrayTransformerWithModelClass:CountryInfo.class];
}
I'm getting an error when I run my APP:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'populationInCities must either map to a JSON key path or NSNull, got: {
populationInCities = (
Hamburg,
Berlin,
Munich
);
}.'
If you want to store population numbers in populationInCities array, you need a custom transformer:
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return #{#"collectionName": #"name",
#"cPopulation": #"population"};// 'populationInCities' names in JSON and model are the same, so no need to include here.
}
+ (NSValueTransformer *)populationInCitiesJSONTransformer {
return [MTLValueTransformer transformerWithBlock:^(NSDictionary *dict) {
return [dict allValues];
}];
}
Embarrassing, but it was simly working using .-notation.
e.g.
popukationInCities.hamburg

RestKit 0.2: flattening the hierarchy in JSON

Maybe there's a similar question here, but I could not find it. So I have the RestKit 0.2pre4 and want to map such JSON ...
{
"location": {
"position": {
"latitude": 53.9675028,
"longitude": 10.1795594
}
},
"id": "da3224f2-5919-42f2-9dd8-9171088f4ad7",
"name": "Something even more beautiful",
}
... to such an object:
#interface FavoritePlace : NSManagedObject
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSString * placeId;
#property (assign, nonatomic) double latitude;
#property (assign, nonatomic) double longitude;
#end
As you can see, the point is that I don't want to create a relationship and store "Location" object in database separately - it just doesn't make sense. Therefore I want to assign those nested things (location.position.latitude and location.position.longitude) to particular properties of object itself. How to achieve that?
Apparently, I did a wild ass guess and it worked! Here's the mapping which does the trick:
[placeMapping addAttributeMappingsFromDictionary:#{
#"id": #"placeId",
#"name": #"name",
#"location.position.latitude": #"latitude",
#"location.position.longitude": #"longitude"
}];
Have not found this in documentation, but maybe it's just me... Anyway, if something logical, but undocumented, works, then framework should be good. :) RestKit FTW!
You can pass the string containig the json string to nsdata with
NSData* jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
Then, you will be able to parse de json data with the iOS API with
NSError *error;
NSDictionary *json = jsonData ? [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error] : nil;
Now, examine the generated NSDictionary (with NSlog for example), fill your 'FavoritePlace' object and persist it.
Hope helps!

Resources