JSONModel iOS and Polymorphism

耗尽温柔 提交于 2019-12-06 11:33:48

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] } ] }

I think BWJSONMatcher can handle it in a very neat way.

Declare your model as follows:

@interface GameModel : NSObject<BWJSONValueObject>
@property (nonatomic, assign) long id;
@property (nonatomic, strong) NSArray *events;
@end

@interface GameEventModel : NSObject
@property (nonatomic, assign) long long timestamp;
@end

@interface GameTouchEventModel : GameEventModel
@property (nonatomic, strong) NSDictionary *point;
@end

In the implementation of GameModel, implement this function:

- (Class)typeInProperty:(NSString *)property {
    if ([property isEqualToString:@"events"]) {
        return [GameEventModel class];
    }

    return nil;
}

And then your can get your own data instance from json string within one line:

GameModel *gameModel = [GameModel fromJSONString:jsonString];

The examples about how to use BWJSONMatcher to handle polymorphism can be found here.

TL;DR

Using Swagger could help, see this example in github.

About Swagger

The accepted solution is one way, but I'd like to offer an alternative. If you generate the models using Swagger and take advantage of the "allOf/discriminator" feature to implement inheritance, the generated Objective-C class will contain similar code to the one offered by the accepted solution.

Yaml

definitions:
  Point:
    type: object
    properties:
      x:
       type: number
      y:
       type: number

  GameEventModel:
    type: object
    discriminator: gameEventModelType

  GameTouchEventModel:
    type: object
    description: GameTouchEventModel
    allOf:
      - $ref: '#/definitions/GameEventModel'
      - type: object
        properties:
          gameEventModelType:
            type: string
          point:
             $ref: '#/definitions/Point'

  GameFooEventModel:
    type: object
    description: GameTouchEventModel
    allOf:
      - $ref: '#/definitions/GameEventModel'
      - type: object
        properties:
          gameEventModelType:
            type: string
          name:
             type: string

  GameModel:
    type: object
    properties:
      id:
        type: integer
        format: int64
      events:
        type: array
        items:
          $ref: '#/definitions/GameEventModel'

Generated code snippet

/**
 Maps "discriminator" value to the sub-class name, so that inheritance is supported.
 */
- (id)initWithDictionary:(NSDictionary *)dict error:(NSError *__autoreleasing *)err {


    NSString * discriminatedClassName = [dict valueForKey:@"gameEventModelType"];

    if(discriminatedClassName == nil ){
         return [super initWithDictionary:dict error:err];
    }

    Class class = NSClassFromString([@"SWG" stringByAppendingString:discriminatedClassName]);

    if([self class ] == class) {
        return [super initWithDictionary:dict error:err];
    }


    return [[class alloc] initWithDictionary:dict error: err];

}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!