get subTree of JSON using github-mantle - ios

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

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
}

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

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

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";

MOTIS Object Mapping, With NSDictionary with values NSArray how can I specify type of array elements?

I have the json
{"Types":{
"food":[{"cve":"1","description":"Pizza"},{"cve":"2","description":"Restaurant"},{"cve":"3","description":"Cafe"}],
"Health":[{"cve":"3","description":"Pharmacy"},{"cve":"4","description":"Hospital"}]
} }
Types.h
#import <Foundation/Foundation.h>
#interface Types: NSObject
#property (nonatomic, copy) NSDictionary *types;
#end
Types.m
#import "Types.h"
#import <Motis/Motis.h>
#import "SubTipo.h"
#implementation Types
+ (NSDictionary*)mts_mapping
{
return #{#"types": mts_key(types),};
}
#end
Subtype.h
#import <Foundation/Foundation.h>
#interface Subtype: NSObject
#property (nonatomic, assign) int cve;
#property (nonatomic, copy) NSString *description;
#end
Subtype.m
#import "Subtype.h"
#import <Motis/Motis.h>
#implementation Subtype
+ (NSDictionary*)mts_mapping
{
return #{#"cve": mts_key(cve),
#"description": mts_key(description),
};
}
#end
I deserialize with
Types * values=[[Types alloc]init];
NSDictionary * jsonObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
[values mts_setValuesForKeysWithDictionary:jsonObject ];
I get NSDictionary with NSArray of NSDictionary
but I need NSDictionary with NSArray of Subtypes
I try with
+ (NSDictionary*)mts_arrayClassMapping
{
return #{mts_key(types): Subtype.class};
}
but wasn't successful
How can I get these with Motis
As far as I see, your Types object is not properly defined. If you have an attribute of type NSDictionary* and the JSON received is a Dictionary, Motis won't perform any automatic conversion as the types already match (you are receiving a dictionary and your attribute is of type NSDictionary).
Therefore, you must implement your Type object following your JSON structure. This means that your Type object must have two properties of type array, one for food and one for health. Then, using the method +mts_arrayClassMapping you can specify the content type of the arrays to Subtype.
Here the implementation:
// ***** Type.h file ***** //
#interface Type: NSObject
#property (nonatomic, strong) NSArray *food;
#property (nonatomic, strong) NSArray *health;
#end
// ***** Type.m file ***** //
#implementation Type
+ (NSDictionary*)mts_mapping
{
return #{#"food": mts_key(food),
#"Health": mts_key(health),
};
}
+ (NSDictionary*)mts_arrayClassMapping
{
return #{mts_key(food): Subtype.class,
mts_key(health): Subtype.class,
};
}
#end
Regarding the implementation of Subtype, yours is already correct. However, you should not use the property name description as it is already being used by NSObject:
// ***** Subtype.h file ***** //
#interface Subtype: NSObject
#property (nonatomic, assign) NSInteger cve;
#property (nonatomic, copy) NSString *theDescription;
#end
// ***** Subtypes.m file ***** //
#implementation Subtype
+ (NSDictionary*)mts_mapping
{
return #{#"cve": mts_key(cve),
#"description": mts_key(theDescription),
};
}
#end
Finally, as you list above, you can map your JSON, but first you will have to extract the "dictionary" for key Types, which you will map to your "Type" model object.
// Get the json data
NSDictionary * jsonObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
// Extract the JSON dictionary of types.
NSDictionary *jsonType = [jsonObject objectForKey:#"Types"];
// Create a Type object
Type *type = [[Type alloc] init];
// Map JSON contents to the type object with Motis
[type mts_setValuesForKeysWithDictionary:jsonType];
Hoping this fixes your issue.

How to specify child objects type in an NSArray with Mantle

If I have a dictionary like
{
name: "Bob",
cars: [
{ make: "ford", year: "1972" },
{ make: "mazda", year: "2000" }
],
}
and two models like:
#interface CarModel : MTLModel
#property (nonatomic, strong) NSString *make;
#property (nonatomic, strong) NSString *year;
#end
#interface PersonModel : MTLModel
#property (nonatomic, strong) NSString *name;
#property (nonatomic, strong) NSArray *cars;
#end
How do I use Mantle so that my array of cars in my person model are CarModels?
Ah figured it out. I needed to add a private method:
+ (NSValueTransformer *)carsTransformer
{
return [NSValueTransformer mtl_externalRepresentationArrayTransformerWithModelClass:[CarModel class]];
}
and make sure I used:
[PersonModel modelWithExternalRepresentation:dict];
+[NSValueTransformer mtl_externalRepresentationArrayTransformerWithModelClass:] is deprecated. The new API is +[NSValueTransformer mtl_JSONArrayTransformerWithModelClass:].
After switching to the new API, the models can be initialized with the default initializers provided by, e.g., MTLJSONAdapter.
A note on:
[NSValueTransformer mtl_JSONArrayTransformerWithModelClass:CarModel.class];
This methods seems to now be deprecated. I'm using this new method in my code and it appears to be working just fine:
[MTLJSONAdapter arrayTransformerWithModelClass:CarModel.class];
+ (NSValueTransformer *)carsJSONTransformer {
return [NSValueTransformer mtl_JSONArrayTransformerWithModelClass:CarModel.class];
}
Read more here
+[NSValueTransformer mtl_JSONArrayTransformerWithModelClass:]
This method is deprecated. New method is:
+ (NSValueTransformer *)carsTransformer
{
return [MTLJSONAdapter arrayTransformerWithModelClass:[CarsModel class]];
}

Resources