Using the following models as examples, what are the best practices of handling polymorphism within JSONModel?
@interface GameModel : JSONModel
@property (nonatomic, assign) long id;
@property (nonatomic, assign) NSArray<GameEventModel> *events;
/*
...
*/
@end
@interface GameEventModel : JSONModel
@property (nonatomic, assign) long long timestamp;
/*
...
*/
@end
@interface GameTouchEventModel : GameEventModel
@property (nonatomic, assign) CGPoint point;
/*
...
*/
@end
When GameModel is initiated with a JSON string of {id:1, events:[{point:{x:1, y:1}, timestamp:...}]}
JSONModel will use the GameEventModel
and ignore the point
property.
Would it be better to use a generic GameEventModel
which contains a type
property and info
property such as...
@interface GameTouchEventModel : GameEventModel
@property (nonatomic, strong) NSString *type;
@property (nonatomic, strong) NSDictionary *info;
@end
And therefore the model could accept JSON as {id:1, events:[{ type:"GameTouchEventModel", info:{ point:{x:1, y:1}, timestamp:... } }]}
The problem with this approach is harder to read code and no compiler warnings/errors amongst others.
Is there no way to use polymorphic models in JSONModel?
We solved this with 2 minor alterations to JSONModel.m
, introducing a new special JSON property __subclass
which is picked up by the JSONModel
parser and uses the value as the object type. __subclass
is required to be a reserved keyword (therefore no models can use __subclass
as a property name).
Alterations to JSONModel.m
// ...
-(id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err
{
// ...
if ([self __isJSONModelSubClass:property.type]) {
//initialize the property's model, store it
JSONModelError* initErr = nil;
-- id value = [[property.type alloc] initWithDictionary: jsonValue error:&initErr];
++ id value;
++ if([jsonValue valueForKey:@"subclass"] != NULL)
++ {
++ Class jsonSubclass = NSClassFromString([d valueForKey:@"subclass"]);
++ if(jsonSubclass)
++ obj = [[jsonSubclass alloc] initWithDictionary:d error:&initErr];
++ }
++ else
++ value = [[property.type alloc] initWithDictionary: jsonValue error:&initErr];
//...
//...
+(NSMutableArray*)arrayOfModelsFromDictionaries:(NSArray*)array error:(NSError**)err
{
// ...
for (NSDictionary* d in array) {
JSONModelError* initErr = nil;
-- id obj = [[self alloc] initWithDictionary:d error:&initErr];
++ id obj;
++ if([d valueForKey:@"subclass"] != NULL)
++ {
++ Class jsonSubclass = NSClassFromString([d valueForKey:@"subclass"]);
++ if(jsonSubclass)
++ obj = [[jsonSubclass alloc] initWithDictionary:d error:&initErr];
++ }
++ else
++ obj = [[self alloc] initWithDictionary:d error:&initErr];
// ...
// ...
NOTE: If the _subclass
'ed JSON model class doesn't exist, then the model will fallback to the superclass.
This will then work with the following models
@interface GameModel : JSONModel
@property (nonatomic, assign) long id;
@property (nonatomic, assign) NSArray<GameEventModel> *events;
@end
@protocol GameEventModel
@end
@interface GameEventModel : JSONModel
@property (nonatomic, assign) long long timestamp;
@end
@interface GameTouchEventModel : GameEventModel
@property (nonatomic, strong) NSArray *point;
@end
When passed the JSON string {id:1, events:[ { __subclass:'GameTouchEventModel', timestamp:1, point: [0,0] } ] }