I have MyModel inheriting from MTLModel (using the GitHub Mantle pod).
MyModel.h
#import <Mantle/Mantle.h>
#interface MyModel : MTLModel <MTLJSONSerializing>
#property (nonatomic, copy, readonly) NSString *UUID;
#property (nonatomic, copy) NSString *someProp;
#property (nonatomic, copy) NSString *anotherProp;
#end
MyModel.m
#import "MyModel.h"
#implementation MyModel
+ (NSDictionary *)JSONKeyPathsByPropertyKey
{
return #{
#"UUID": #"id",
#"someProp": #"some_prop",
#"anotherProp": #"another"
};
}
}
#end
Now I want to send the JSON to the backend using AFNetworking. Before that I convert the model instance to a JSON NSDictionary to use as parameters/body payload within my request.
NSDictionary *JSON = [MTLJSONAdapter JSONDictionaryFromModel:myModel];
But this JSON consists of strange "" Strings for properties of my model that are nil. What i instead want is Mantle to omit these key/value pairs and just spit out a JSON with only the properties that are not nil or NSNull.null, whatever.
This is a common issue with Mantle and it's called implicit JSON mapping.
MTLJSONAdapter reads all properties of a model to create a JSON string optionally replacing property names with ones given in +JSONKeyPathsByPropertyKey.
If you want some properties to be excluded from the JSON representation of your model, map them to NSNull.null in your +JSONKeyPathsByPropertyKey:
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return #{
#"UUID": #"id",
#"someProp": #"some_prop",
#"anotherProp": #"another",
#"myInternalProperty": NSNull.null,
#"myAnotherInternalProperty": NSNull.null,
};
}
The implicit JSON mapping has lately become a noticeable problem, a solution for which is currently being discussed at Mantle's home repository at GitHub.
See issues #137, #138, #143 and the current discussion under #149.
EDIT: I clearly misunderstood the question, but now, when I suppose I understand it correctly, the answer is simple.
MTLJSONAdapter generates the JSON data using MTLModel's dictionaryValue property. If you wish to exclude a property from the JSON itself, you can overwrite that method in your MYModel:
- (NSDictionary *)dictionaryValue {
NSMutableDictionary *originalDictionaryValue = [[super dictionaryValue] mutableCopy];
if (self.aPropertyThatShouldBeExcludedWhenNil == nil) {
[originalDictionaryValue removeObjectForKey:#"aPropertyThatShouldBeExcludedWhenNil"];
}
/* repeat the process for other "hidden" properties */
return originalDictionaryValue;
}
EDIT #2: Check out the code* for removing all values that are nil:
- (NSDictionary *)dictionaryValue {
NSMutableDictionary *modifiedDictionaryValue = [[super dictionaryValue] mutableCopy];
for (NSString *originalKey in [super dictionaryValue]) {
if ([self valueForKey:originalKey] == nil) {
[modifiedDictionaryValue removeObjectForKey:originalKey];
}
}
return [modifiedDictionaryValue copy];
}
* - code sample suggested by matths.
I remove nil valued keys by creating an MTLJSONAdapter subclass, and overriding -serializablePropertyKeys:forModel: method.
MTLJSONAdapterWithoutNil.h
/** A MTLJSONAdapter subclass that removes model dictionaryValue keys whose value is `[NSNull null]`. */
#interface MTLJSONAdapterWithoutNil : MTLJSONAdapter
#end
MTLJSONAdapterWithoutNil.m
#import "MTLJSONAdapterWithoutNil.h"
#implementation MTLJSONAdapterWithoutNil
- (NSSet *)serializablePropertyKeys:(NSSet *)propertyKeys forModel:(id<MTLJSONSerializing>)model {
NSMutableSet *ms = propertyKeys.mutableCopy;
NSDictionary *modelDictValue = [model dictionaryValue];
for (NSString *key in ms) {
id val = [modelDictValue valueForKey:key];
if ([[NSNull null] isEqual:val]) { // MTLModel -dictionaryValue nil value is represented by NSNull
[ms removeObject:key];
}
}
return [NSSet setWithSet:ms];
}
#end
And use this to create JSON dictionary instead. Like this:
NSDictionary *JSONDictionary = [MTLJSONAdapterWithoutNil JSONDictionaryFromModel:collection error:nil];
NOTE: if you are overriding NSValueTransformer methods for array or dictionary properties, you also have to change the MTLJSONAdapter class to your subclass as well. Like this:
+ (NSValueTransformer *)myDailyDataArrayJSONTransformer {
return [MTLJSONAdapterWithoutNil arrayTransformerWithModelClass:KBDailyData.class];
}
Overriding - dictionaryValues did not give me the expected behavior
So I created a method for MTL Base class
- (NSDictionary *)nonNullDictionaryWithAdditionalParams:(NSDictionary *)params error:(NSError *)error {
NSDictionary *allParams = [MTLJSONAdapter JSONDictionaryFromModel:self error: &error];
NSMutableDictionary *modifiedDictionaryValue = [allParams mutableCopy];
for (NSString *originalKey in allParams) {
if ([allParams objectForKey:originalKey] == NSNull.null) {
[modifiedDictionaryValue removeObjectForKey:originalKey];
}
}
[modifiedDictionaryValue addEntriesFromDictionary:params];
return [modifiedDictionaryValue copy];
}
The EDIT #2 used to work for me with the previous Mantle code base. Now I have to do the following to continue using EDIT #2:
In file MTLJSONAdapter.m, replace this line:
NSDictionary *dictionaryValue = [model.dictionaryValue dictionaryWithValuesForKeys:propertyKeysToSerialize.allObjects];
with
NSDictionary *dictionaryValue = model.dictionaryValue;
The above is my current workaround to get
{ }
instead of
{
"AddressLine2" : null,
"City" : null,
"ZipCode" : null,
"State" : null,
"AddressLine1" : null
}
Related
Hello everyone i am new to MTLModel and trying to understand it correctly. I am making a network query and retrieve a NSDictionary json data.
Data include an array of objects. There are two types of objects in the array mixed.
I use the following line of code to create my model.
HashTagsContentTilesResult *model = [MTLJSONAdapter modelOfClass:[HashTagsContentTilesResult class] fromJSONDictionary:response error:&error];
The model returns the transformed data but all objects are transformed using one class. I need to transform each one item that is included in the data dictionary using a different class because they have a different type of properties. This is my code in the MTLmodel .m file
#implementation C8MHashTagsContentTilesResultForOtherTiles
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return
#{
#"total" : #"totalTermCount",
#"data" : #"data"
};
}
+ (NSValueTransformer *)dataJSONTransformer {
return [MTLJSONAdapter arrayTransformerWithModelClass:C8MHashTagsContentItemForOtherTiles.class];
}
#end
#implementation C8MHashTagsContentTilesResult
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return
#{
#"total" : #"totalTermCount",
#"data" : #"data"
};
}
+ (NSValueTransformer *)dataJSONTransformer {
return [MTLJSONAdapter arrayTransformerWithModelClass:C8MHashTagsContentItem.class];
}
+ (Class)classForParsingJSONDictionary:(NSDictionary *)JSONDictionary {
for (NSDictionary *dictionary in JSONDictionary[#"data"])
{
if ([dictionary[#"feedType"] isEqualToString:#"creator_content"]) {
NSLog(#"mtlmodel: creators content");
return C8MHashTagsContentTilesResult.class;
}
else{
NSLog(#"mtlmodel: other content");
return C8MHashTagsContentTilesResultForOtherTiles.class;
}
}
NSAssert(NO, #"No matching class for the JSON dictionary '%#'.", JSONDictionary);
return self;
}
The above code returns the data but uses the same class for all items in the dictionary. How can i use a different class for each item in the dictionary and when all items in the dictionary are transformed one by one...then return all the data? Any help appreciated.
I want to use Mantle to serialize some objects to this JSON:
{
"name": "John Smith",
"age": 30,
"department_id":123
}
I have two classes Department Employee:
#import <Mantle/Mantle.h>
#interface Department : MTLModel <MTLJSONSerializing>
#property(nonatomic)int id;
#property(nonatomic)NSString *name;
#end
and the Employee class:
#import <Mantle/Mantle.h>
#import "Department.h"
#interface Employee : MTLModel <MTLJSONSerializing>
#property(nonatomic)NSString *name;
#property(nonatomic)int age;
#property(nonatomic)Department *department;
#end
#implementation Employee
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return #{
#"name":#"name",
#"age":#"age",
#"department.id":#"department_id"
};
}
#end
when serializing an Employee instance I receive the following
exception: "NSInternalInconsistencyException", "department.id is not a
property of Employee."
What's wrong here? is there a way to serialize the object as as a single dictionary instead of nesting the department object inside the employee object?
first remove this code from your Employee.m file
#implementation Employee
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return #{
#"name":#"name",
#"age":#"age",
#"department.id":#"department_id"
};
}
and then use the following whenever you want to serialize the Employee object
Employee *objEmployee = [Employee instanceFromDict:responseObject];
I hope it will work for you. All the best!!
OK, I got it from here:
Mantle property class based on another property?
I modified the mapping dictionary to be like this
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return #{
#"name":#"name",
#"age":#"age",
NSStringFromSelector(#selector(department)) : #[#"department_id"]
};
}
and added:
+ (NSValueTransformer *)departmentJSONTransformer {
return [MTLValueTransformer transformerUsingReversibleBlock:^id(Department *department, BOOL *success, NSError *__autoreleasing *error) {
return [MTLJSONAdapter JSONDictionaryFromModel:department error:nil];
}];
}
I would like to map a json string to an anonymous object using the specific class. Suppose i have a country class. I would like to parse a json string into this object without knowing which object it is. So i use the class for parsing.
#interface CountryModel
#property (assign, nonatomic) int id;
#property (strong, nonatomic) NSString* country;
#end
NSString* json = (fetch here JSON from Internet) ...
CountryModel* country ;
id obj = country ;
obj = tojson( [obj class] , json )
https://github.com/icanzilb/JSONModel does what i need but i need same thing without using the inheritance. I would like to do same thing without inheriting from JSONModel;
You could define a Category for your custom model class (say, CountryModel) which implements a class factory method. A contrived example:
#interface CountryModel (JSONExtension)
+ (CountryModel*) jsonExtension_modelWithJSONObject:(NSDictionary*)jsonObject error:(NSError**)error;
#end
#implementation CountryModel (JSONExtension)
+ (CountryModel*) jsonExtension_modelWithJSONObject:(NSDictionary*)jsonObject error:(NSError**)error {
// Create an object of type Foo with the given NSDictionary object
CountryModel* result = [[CountryModel alloc] initWithName:jsonObject[#"name"]];
if (result == nil) {
if (error) {
*error = [NSError errorWithDomain:#"CountryModel"
code:-100
userInfo:#{NSLocalizedDescriptionKey: #"Could not initialize CountryModel with JSON Object"}];
}
return nil;
}
// "recursively" use jsonExtension_modelWithJSONObject:error: in order to initialize internal objects:
BarModel* bar = [BarModel jsonExtension_modelWithJSONObject:jsonObject[#"bar"] error:error];
if (bar == nil) // bar is required
{
result = nil;
return nil;
}
result.bar = bar;
return result;
}
#end
jsonObject is a representation of a JSON Object as a NSDictionary object. You need to first create this representation before passing it the class factory method, e.g.:
NSError* error;
NSDictionary* jsonObject = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
assert([jsonObject isKindOfClass[NSDictionary class]]);
CountryModel* model = [CountryModel jsonExtension_modelWithJSONObject:jsonObject error:&error];
I have to send a data by post in JSON format. I have my nsdictionary with keys and values.
NSDictionary *params_country=[NSDictionary dictionaryWithObjectsAndKeys:
#"1111",#"#id",
nil];
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
#"dummy3", #"#name",
#"dummy3#example.com", #"#mail",
#"password",#"#password", params_country,#"country",
nil];
When i am doing a log
DLog(#"params %#",[params description]);
I am getting the following
params {
"#mail" = "dummy3#example.com";
"#name" = dummy3;
"#password" = password;
}
The problem is that i have to sent the JSON in the order that i have listed in the above initialisation of my nsdictionary but the keys are being sorted somehow.
Any solution?
EDIT
Sorry i am sending a nsdictionary also in the params. If i remove the country then its fine.
Dictionaries are an unordered collection type. If you need to maintain a certain order, then you should use an ordered collection type like NSArray. But for this, your web service shouldn't care about the order, since it should be looking up the values by the keys provided.
As per some of the comments, this requirement does not match a valid JSON object as the official JSON Specification states:
An object is an unordered set of name/value pairs. An object begins with { (left brace) and ends with } (right brace). Each name is followed by : (colon) and the name/value pairs are separated by , (comma).
Unfortunately we don't live in a perfect world with perfect web services and there are often certain things that are out of our control.
I wrote a subclass of NSMutableDictionary after reading up on the internet that will order the dictionary based on the order you call setValue:forKey:.
I put the class into a gist you can download from here: https://gist.github.com/liamnichols/7869468 or you can just copy it from below:
LNOrderedMutableDictionary.h
#interface LNOrderedMutableDictionary : NSMutableDictionary
///If `anObject` is nil, it will not be added to the dictionary.
- (void)setNothingIfNil:(id)anObject forKey:(id)aKey;
#end
LNOrderedMutableDictionary.m
#import "LNOrderedMutableDictionary.h"
#interface LNOrderedMutableDictionary ()
#property (nonatomic, strong) NSMutableDictionary *dictionary;
#property (nonatomic, strong) NSMutableOrderedSet *array;
#end
#implementation LNOrderedMutableDictionary
- (id)initWithCapacity:(NSUInteger)capacity
{
self = [super init];
if (self != nil)
{
self.dictionary = [[NSMutableDictionary alloc] initWithCapacity:capacity];
self.array = [[NSMutableOrderedSet alloc] initWithCapacity:capacity];
}
return self;
}
- (id)init
{
self = [self initWithCapacity:0];
if (self)
{
}
return self;
}
- (void)setObject:(id)anObject forKey:(id)aKey
{
[self.array removeObject:aKey];
[self.array addObject:aKey];
[self.dictionary setObject:anObject forKey:aKey];
}
- (void)setNothingIfNil:(id)anObject forKey:(id)aKey
{
if (anObject != nil)
[self setObject:anObject forKey:aKey];
}
- (void)removeObjectForKey:(id)aKey
{
[self.dictionary removeObjectForKey:aKey];
[self.array removeObject:aKey];
}
- (NSUInteger)count
{
return [self.dictionary count];
}
- (id)objectForKey:(id)aKey
{
return [self.dictionary objectForKey:aKey];
}
- (NSEnumerator *)keyEnumerator
{
return [self.array objectEnumerator];
}
#end
If possible, your web service shouldn't have to rely on the JSON objects to be formatted in a specific order but if there is nothing you can do to change this then the above solution is what you are looking for.
Source: cocoawithlove
I am making a NSObjectClass that has a method in it that returns self.
This is what it looks like roughtly
storageclass.h
// storageclass vars go here
- (storageclass)assignData:(NSDictionary *)dictionary;
storageclass.m
//#synthesise everything
- (storageclass)assignData:(NSDictionary *)dictionary {
//assign values from dictionary to correct var types (i.e. NSString, Int, BOOL)
//example
Side = [dictionary valueForKey:#"Side"];
return self;
}
Then what I want to do is use this class by passing a NSDictionary var through its method to return a object of type storageclass that I can then use to access the vars using dot notation.
this is how I am trying to access this class at the moment
accessorViewController.h
storageclass *store;
#property (strong, nonatomic) storageclass *store;
accessorViewController.m
#synthesize store;
- (void)getstoreready {
[store assignData:someDictionary];
nslog(#"%#", store);
}
this NSLog returns nothing and in the debugger all of stores class vars are empty showing nothing has been assigned. I am 100% positive the dictionary vars being used in the assignData method have the correct valueForKey values.
I think it has something to do with how I am using it here [store assignData:someDictionary]; how do i catch the turned data so I can use it?
any help would be appreciated.
The store object is never initialized so it will be nil thats obvious isn't it. Initialize the store object first, then call its instance methods onto it. And by doing that, you'll have a storageclass object which is properly assigned with some dictionary already.
And if you want to have a storageclass object like your code shows, you should make your (storageclass)assignData:(NSDictionary *)dictionary method a class method instead of an instance method by putting a + sign
+(storageclass*)assignData:(NSDictionary *)dictionary;
Then properly initialize it and assign the data (dictionary to variables) accordingly and return it to the caller. For example :-
in .m file
+(storageclass*)assignData:(NSDictionary *)dictionary{
storageclass *test = [[storageclass alloc] init];
if (test) {
test.someDict = dictionary;
}
return test;
}
Then use this class method in your view controller as
- (void)getstoreready {
store = [storageClass assignData:someDictionary];
nslog(#"%#", store);
}
Also Do follow the naming convention for classes and instances. A class's name must start with a capital letter only and the opposite for any class instances.
In User.h
#interface User : NSObject
#property (nonatomic, copy) NSString *name;
- (id)initWithDictionary:(NSDictionary *)dictionary;
+ (NSArray *)usersFromArray:(NSArray *)array;
#end
In User.m
- (id)initWithDictionary:(NSDictionary *)dictionary
{
self = [super init];
if (self) {
if (dictionary)
{
self.name = dictionary[#"kUserName"];
}
}
return self;
}
+ (NSArray *)usersFromArray:(NSArray *)array
{
NSMutableArray *users = [NSMutableArray array];
for (NSDictionary *dict in array) {
User *user = [[User alloc]initWithDictionary:dict];
[users addObject:user];
}
NSSortDescriptor *descriptor = [NSSortDescriptor sortDescriptorWithKey:#"name"
ascending:YES];
return [users sortedArrayUsingDescriptors:#[descriptor]];
}
In ViewController.m
import "User.h"
self.currentArray = [User usersFromArray:array];