Details how Obejective C transforms JSON data into models

  • 2020-05-24 06:16:41
  • OfStack

In our daily development, we need to model transform some local data loaded in files such as plist and json, and apple also provides us with a very convenient key-value conversion method, KVC. However, in some cases, KVC cannot save the data successfully. For example, the number of properties of the model must be greater than or equal to the number of dictionaries, and the property name must be the same as key of the dictionary. So this time we're going to assume that the property name is not 1 to key in the dictionary.
First of all, we will try to solve this problem by using KVC
The model is as follows:


@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *ID;

The data of JSON are as follows:


{
 "title" : " Shunping hou ",
 "name" : " zhaoyun ",
 "id" : "sph"
 },
 {
 "title" : " Constant hou ",
 "html" : " Zhang fei ",
 "id" : "hh"
 },
 {
 "title" : " Wei hou ",
 "html" : " d ",
 "id" : "wh"
 },
 {
 "title" : " Just hou ",
 "html" : " Huang zhong ",
 "id" : "gh"
 },
 {
 "title" : " ShouTing hou ",
 "html" : " Guan yu ",
 "id" : "sth"
 }

From the above data comparison, it is not difficult to find that id is the keyword in OC, so we use ID instead, but we cannot directly use KVC, so we need to carry out corresponding processing to continue to use our KVC transformation model. The code is as follows:
First, update 1 in the model.h file to provide a class method for model transformation:


@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *ID; +(instancetype) heroDict:(NSDictionary*) dict;

Implement this method in the.m file

+ (instancetype)itemWithDict:(NSDictionary *)dict
{
    HeroItem *hero = [[self alloc]init];     [item setValuesForKeysWithDictionary:dict];
    return item;
}

So when we get there, we're going to go through all the key in the dictionary in the model. Therefore, what we need to modify is to rewrite the setValue forKey method in KVC. The code is as follows:

- (void)setValue:(id)value forKey:(NSString *)key{
    // Because we already know what to change key So we can just say equal
    if ([key isEqualToString:@"id"]) {
        // To replace
        [self setValue:value forKeyPath:@"ID"];
    }else{
        // Throw back to parent processing
        [super setValue:value forKey:key];
    }
}

At this point, you can basically use the KVC method for conversion. But what if our data has a lot of non-1's? So let's take a look at today's big show runtime's conversion.
The idea of the above example is to go through key in the dictionary to compare it in the model. We will try to walk through the model and then compare key in the dictionary to the response
First, import the header file we need in our model.m

#import <objc/runtime.h>

After completing this step, we can use runtime in the model class, and then we can create a transformation class method in.m

+ (instancetype)objcWithDict:(NSDictionary *)dict updateDict:(NSDictionary *)updateDict{ }

What we need to do in this method is to go through the properties in the model with runtime, and compare the properties, and if the properties in the model don't exist in the dictionary, we'll look for them in updateDict, and if they do exist in the updateDict dictionary, we'll convert them. objctWithDict: method updated as follows:

(instancetype)objcWithDict:(NSDictionary *)dict updateDict:(NSDictionary *)updateDict{
id objc = [[self alloc] init];
    // Traverse the properties in the model
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList(self, &count);
    for (int i = 0 ; i < count; i++) {
        Ivar ivar = ivars[i];
        // The attribute name
        NSString *ivarName = @(ivar_getName(ivar));
        ivarName = [ivarName substringFromIndex:1];
        id value = dict[ivarName];
        // The attribute names in the model correspond to those in the dictionary key
        if (value == nil) {
            if (updateDict) {
            NSString *keyName = updateDict[ivarName];
            value = dict[keyName];
            }
        }
            [objc setValue:value forKeyPath:ivarName];
    }
    return objc;
}

At this point, the conversion has been completed, so let's update heroDict: method code:

+ (instancetype)itemWithDict:(NSDictionary *)dict{
    // A method is called ,updateDict Is the data that needs to be replaced
    HeroItem *item = [HeroItem objcWithDict:dict updateDict:@{@"ID":@"id"}];
    return item;
}

This is where the runtime conversion method is done. If you compare the two methods, it might be obvious that the first method is simpler. However, if you have multiple models, you need to rewrite setValue: method a lot, while the second method can be encapsulated and applied to various models. Of course, if it is really a large project, it is still recommended to use some excellent 3rd party framework to handle the model, such as MJ MJExtension, which is easy to use and is definitely the best choice for development.

Using jastor
If you have the jastor library, it will be a lot easier for you to explain the basic usage now.

Let's say we have a class like this


#import <Foundation/Foundation.h>
#import "Jastor.h" @interface DeviceEntity : Jastor @property (nonatomic,strong) NSNumber *isonline;
@property (nonatomic,strong) NSNumber *isopen;
@property (nonatomic,copy) NSString *brand; @end #import "DeviceEntity.h" @implementation DeviceEntity @synthesize isopen,isonline,brand; @end #import <Foundation/Foundation.h>
#import "Jastor.h"
#import "DeviceEntity.h" @interface UserDevicesEntity : Jastor @property (nonatomic,strong) NSNumber *closecount;
@property (nonatomic,strong) NSNumber *opencount;
@property (nonatomic,copy) NSString *success;
@property (nonatomic,strong) NSArray *items; @end #import "UserDevicesEntity.h"
#import "DeviceEntity.h" @implementation UserDevicesEntity @synthesize closecount,opencount,success,items; + (Class) items_class {
    return [DeviceEntity class];
} @end

Note here in time if it is a basic types of corresponding attributes definition we need to use NSNumber for packaging, the above example also shows that we can use an array to as an attribute, only when is to realize the need to tell it is what type of the array, you define the attribute name followed by _class form, pay attention to this 1 point can't make a mistake.

When invoking the service, the other party 1 will normally return 1 json. What we need to do is to instantiate 1 NSDictionary according to this string, and then we can instantiate the corresponding model according to this NSDictionay, which is much more convenient than we can directly parse this string. The code is as follows:


NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:@"1",@"isonline",@"1",@"isopen",@"brand1",@"brand", nil];  
DeviceEntity *device = [[DeviceEntity alloc] initWithDictionary:dictionary];

And we can verify that,

@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *ID; +(instancetype) heroDict:(NSDictionary*) dict;
0
It will print out


2014-02-17 22:36:37.602 objc-grammar-learing[819:f803] device's brand is brand1
2014-02-17 22:36:37.605 objc-grammar-learing[819:f803] device's isonline is 1
2014-02-17 22:36:37.605 objc-grammar-learing[819:f803] device's isopen is 1

See if it is more convenient, of course, the above is only a very simple model, 1 generally speaking, the model in real projects must be more complex, such as 1 to 1, 1 to many, etc., there are corresponding examples on the official website for reference.


Related articles: