SoFunction
Updated on 2025-04-13

Detailed explanation of the method of converting JSON data into a model in Objective-C

In our daily development, we need to model the loaded local data such as plist, json and other files, and Apple also provides us with a very convenient key-value conversion method KVC. However, in some cases, KVC cannot save the data conversion successfully. For example, it is necessary to ensure that the number of attributes of the model is greater than or equal to the number of dictionaries, and the attribute name must be the same as the key of the dictionary. So this time we assume that the conversion method is when the attribute name is inconsistent with the key in the dictionary.
First of all, we should try to use KVC to solve this problem
The model is as follows:

Copy the codeThe code is as follows:

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

The JSON data is as follows:
{
 "title" : "Shunpinghou",
 "name" : "Zhao Yun",
 "id" : "sph"
 },
 {
 "title" : "Henghou",
 "html" : "Zhang Fei",
 "id" : "hh"
 },
 {
 "title" : "Weihou",
 "html" : "Ma Chao",
 "id" : "wh"
 },
 {
 "title" : "Ganghou",
 "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 because the id in OC is the keyword, we use ID to replace it, but in this way, we cannot use KVC directly, so we need to perform corresponding processing to continue using our KVC conversion model. The code is as follows:
First, update the code in the model.h file and provide a class method for model conversion:

Copy the codeThe code is as follows:

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

+(instancetype) heroDict:(NSDictionary*) dict;


Implement this method in .m file
Copy the codeThe code is as follows:

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

    [item setValuesForKeysWithDictionary:dict];
    return item;
}


When the program goes here, it will traverse all keys in the dictionary in the model. So the part we need to modify is to rewrite the setValue forKey method in KVC. The code is as follows:
Copy the codeThe code is as follows:

- (void)setValue:(id)value forKey:(NSString *)key{
//Because you already know the key to be modified, you can directly determine the equality
    if ([key isEqualToString:@"id"]) {
//Replace
        [self setValue:value forKeyPath:@"ID"];
    }else{
//Transfer back to the parent class to process
        [super setValue:value forKey:key];
    }
}

After modifying the program here, you can basically use the KVC method to convert it. But what if there are many inconsistencies in our data? So let's take a look at the conversion from today's highlight runtime.
The idea of ​​the above example is to compare the keys in the dictionary. This time we try to traverse the model and then compare the response keys in the dictionary.
First, import the header file we need in our model.m
Copy the codeThe code is as follows:

#import <objc/>

Complete this step and you can use runtime in the model class, and then we create a converted class method in .m.
Copy the codeThe code is as follows:

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

}


In this method, what we need to do is to traverse the properties in the model through runtime and compare the properties. If the properties in the model do not exist in the dictionary, then we will search in updateDict. If the properties in the updateDict dictionary exist, it will be converted. The objctWithDict: method is updated as follows:
Copy the codeThe code is as follows:

(instancetype)objcWithDict:(NSDictionary *)dict updateDict:(NSDictionary *)updateDict{
id objc = [[self alloc] init];
//Travel over 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];
// Attribute name
        NSString *ivarName = @(ivar_getName(ivar));
        ivarName = [ivarName substringFromIndex:1];
        id value = dict[ivarName];
// The attribute name in the model corresponds to the key in the dictionary
        if (value == nil) {
            if (updateDict) {
            NSString *keyName = updateDict[ivarName];
            value = dict[keyName];
            }
        }
            [objc setValue:value forKeyPath:ivarName];
    }
    return objc;
}

The conversion has been completed here, so let's update the heroDict: method code:
Copy the codeThe code is as follows:

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

Here, the runtime conversion method is also completed. If you compare the two methods, you may obviously find that the first method is simpler. However, if there are multiple models, a large number of rewrites the setValue: method, and the second method can be encapsulated to be suitable for various models. Of course, if it is really a large-scale project, it is recommended to use some very excellent third-party frameworks to deal with models. For example, the MJ Extension of the MJ master is simple and convenient to use, and is definitely the best choice for development.

Using jastor
If there isjastorThis library will also be convenient for many people to introduce the basic usage now.

If we have a class like this

Copy the codeThe code is as follows:

#import <Foundation/>
#import ""

@interface DeviceEntity : Jastor

@property (nonatomic,strong) NSNumber *isonline;
@property (nonatomic,strong) NSNumber *isopen;
@property (nonatomic,copy) NSString *brand;

@end

#import ""

@implementation DeviceEntity

@synthesize isopen,isonline,brand;

@end

#import <Foundation/>
#import ""
#import ""

@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 ""
#import ""

@implementation UserDevicesEntity

@synthesize closecount,opencount,success,items;

+ (Class) items_class {
    return [DeviceEntity class];
}

@end


Note that when defining the corresponding attributes here, if they are basic types, we need to use NSNumber to wrap them. The above example also shows that we can use arrays as a property, and only when it is implemented, we need to tell it what type of the array is. The attribute name you define is followed by the form of _class. Note that this cannot be mistaken.

When calling the service, the other party will usually return a json. What we need to do is to instantiate an NSDictionary based on this string, and then instantiate the corresponding model based on this NSDictionay. It is much more convenient than directly parsing this string. The code is as follows:

Copy the codeThe code is as follows:

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

We can verify that
Copy the codeThe code is as follows:

NSLog(@"device's brand is %@",);
NSLog(@"device's isonline is %d",[ intValue]);
NSLog(@"device's isopen is %d",[ intValue]);

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

It's much more convenient to see if it's a lot. Of course, the above is just a very simple model. Generally speaking, the models in real projects are definitely more complicated than this, such as one-to-one, one-to-many, etc. There are corresponding examples on the official website to refer to.