MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C中的JSON解析与模型转换方案

2022-04-171.3k 阅读

一、JSON 简介

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。它基于 JavaScript 的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据,简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。

1.1 JSON 数据结构

  1. 对象(Object):一个无序的键值对集合,在 JSON 中用花括号 {} 包围。每个键值对之间用逗号 , 分隔,键必须是字符串,值可以是字符串、数字、布尔值、数组、对象或者 null。例如:
{
    "name": "John",
    "age": 30,
    "isStudent": false,
    "hobbies": ["reading", "swimming"],
    "address": {
        "city": "New York",
        "country": "USA"
    }
}
  1. 数组(Array):一个有序的值的集合,在 JSON 中用方括号 [] 包围。数组中的值可以是任意 JSON 数据类型,并且不同值的类型可以不同。例如:
["apple", 123, true, null]

二、Objective-C 中 JSON 解析

在 Objective - C 中,我们通常使用 Foundation 框架中的类来处理 JSON 解析。NSJSONSerialization 类提供了将 JSON 数据转换为 Objective - C 对象以及将 Objective - C 对象转换为 JSON 数据的方法。

2.1 从 JSON 数据解析为 Objective - C 对象

  1. 解析 JSON 数据:假设我们有一个 JSON 字符串,我们可以使用以下代码将其解析为 Objective - C 对象。
NSString *jsonString = @"{\"name\":\"John\",\"age\":30,\"isStudent\":false,\"hobbies\":[\"reading\",\"swimming\"],\"address\":{\"city\":\"New York\",\"country\":\"USA\"}}";
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
id jsonObject = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error];
if (jsonObject &&!error) {
    NSLog(@"解析成功: %@", jsonObject);
} else {
    NSLog(@"解析失败: %@", error);
}

在上述代码中,我们首先将 JSON 字符串转换为 NSData 类型,然后使用 JSONObjectWithData:options:error: 方法进行解析。options 参数可以用来指定解析的选项,例如 NSJSONReadingMutableContainers 表示返回的数组和字典是可变的。

  1. 处理不同类型的 JSON 数据
    • 解析对象:如果 JSON 数据是一个对象,解析后得到的 jsonObject 是一个 NSDictionary。我们可以通过键来访问对应的值。
if ([jsonObject isKindOfClass:[NSDictionary class]]) {
    NSDictionary *jsonDict = (NSDictionary *)jsonObject;
    NSString *name = jsonDict[@"name"];
    NSNumber *ageNumber = jsonDict[@"age"];
    NSInteger age = [ageNumber integerValue];
    NSLog(@"姓名: %@, 年龄: %ld", name, (long)age);
}
- **解析数组**:如果 JSON 数据是一个数组,解析后得到的 `jsonObject` 是一个 `NSArray`。我们可以通过索引来访问数组中的元素。
NSString *arrayJsonString = @"[\"apple\", 123, true, null]";
NSData *arrayJsonData = [arrayJsonString dataUsingEncoding:NSUTF8StringEncoding];
id arrayJsonObject = [NSJSONSerialization JSONObjectWithData:arrayJsonData options:NSJSONReadingMutableContainers error:&error];
if (arrayJsonObject &&!error && [arrayJsonObject isKindOfClass:[NSArray class]]) {
    NSArray *jsonArray = (NSArray *)arrayJsonObject;
    for (id element in jsonArray) {
        if ([element isKindOfClass:[NSString class]]) {
            NSLog(@"字符串元素: %@", element);
        } else if ([element isKindOfClass:[NSNumber class]]) {
            NSLog(@"数字元素: %@", element);
        } else if ([element isKindOfClass:[NSNull class]]) {
            NSLog(@"空元素");
        } else if ([element isKindOfClass:[NSNumber class]]) {
            NSLog(@"布尔元素: %@", element);
        }
    }
}

2.2 解析嵌套的 JSON 数据

当 JSON 数据包含嵌套结构时,我们需要递归地处理。例如,对于包含嵌套对象和数组的 JSON:

{
    "students": [
        {
            "name": "Alice",
            "age": 25,
            "scores": [85, 90, 78]
        },
        {
            "name": "Bob",
            "age": 23,
            "scores": [70, 80, 85]
        }
    ]
}

解析代码如下:

NSString *nestedJsonString = @"{\"students\":[{\"name\":\"Alice\",\"age\":25,\"scores\":[85,90,78]},{\"name\":\"Bob\",\"age\":23,\"scores\":[70,80,85]}]}";
NSData *nestedJsonData = [nestedJsonString dataUsingEncoding:NSUTF8StringEncoding];
id nestedJsonObject = [NSJSONSerialization JSONObjectWithData:nestedJsonData options:NSJSONReadingMutableContainers error:&error];
if (nestedJsonObject &&!error && [nestedJsonObject isKindOfClass:[NSDictionary class]]) {
    NSDictionary *nestedDict = (NSDictionary *)nestedJsonObject;
    NSArray *studentsArray = nestedDict[@"students"];
    for (id studentDict in studentsArray) {
        if ([studentDict isKindOfClass:[NSDictionary class]]) {
            NSDictionary *student = (NSDictionary *)studentDict;
            NSString *name = student[@"name"];
            NSNumber *ageNumber = student[@"age"];
            NSInteger age = [ageNumber integerValue];
            NSArray *scoresArray = student[@"scores"];
            NSMutableString *scoresString = [NSMutableString string];
            for (NSNumber *score in scoresArray) {
                [scoresString appendFormat:@"%@ ", score];
            }
            NSLog(@"学生姓名: %@, 年龄: %ld, 分数: %@", name, (long)age, scoresString);
        }
    }
}

三、模型转换方案

在实际开发中,我们通常不直接使用解析后的 NSDictionaryNSArray,而是将其转换为自定义的模型类,这样代码结构更清晰,便于维护和扩展。

3.1 创建模型类

以一个简单的用户模型为例,我们创建一个 User 类。

#import <Foundation/Foundation.h>

@interface User : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, assign) BOOL isStudent;
@property (nonatomic, strong) NSArray<NSString *> *hobbies;
@property (nonatomic, strong) NSDictionary *address;

@end

@implementation User

@end

3.2 使用 KVC 进行模型转换

  1. 从字典转换为模型:我们可以利用 Key - Value Coding(KVC)来将解析后的 NSDictionary 转换为 User 模型。
User *user = [[User alloc] init];
NSDictionary *userDict = @{
    @"name": @"John",
    @"age": @30,
    @"isStudent": @NO,
    @"hobbies": @[@"reading", @"swimming"],
    @"address": @{
        @"city": @"New York",
        @"country": @"USA"
    }
};
[user setValuesForKeysWithDictionary:userDict];
NSLog(@"姓名: %@, 年龄: %ld, 是否学生: %@", user.name, (long)user.age, user.isStudent? @"是" : @"否");

在上述代码中,setValuesForKeysWithDictionary: 方法会根据字典中的键来设置模型对象的属性值。如果字典中的键与模型属性名相同,就会自动赋值。

  1. 处理属性名不一致的情况:当 JSON 数据中的键与模型属性名不一致时,我们可以通过重写 setNilValueForKey:setValue:forUndefinedKey: 方法来处理。例如,JSON 中的键是 user_name,而模型属性是 name
@implementation User

- (void)setNilValueForKey:(NSString *)key {
    // 防止将 nil 值设置到非对象类型属性
    if ([key isEqualToString:@"name"]) {
        self.name = @"";
    } else if ([key isEqualToString:@"age"]) {
        self.age = 0;
    }
}

- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    if ([key isEqualToString:@"user_name"]) {
        self.name = value;
    }
}

@end

3.3 使用第三方库进行模型转换

  1. 使用 Mantle:Mantle 是一个流行的 Objective - C 库,用于简化模型转换。首先,我们需要在项目中添加 Mantle 库。
    • 安装 Mantle:可以通过 CocoaPods 安装,在 Podfile 中添加 pod 'Mantle',然后执行 pod install
    • 创建模型类:基于 Mantle 的模型类需要继承自 MTLModel 并实现 MTLJSONSerializing 协议。
#import <Mantle/Mantle.h>

@interface User : MTLModel <MTLJSONSerializing>

@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, assign) BOOL isStudent;
@property (nonatomic, strong) NSArray<NSString *> *hobbies;
@property (nonatomic, strong) NSDictionary *address;

@end

@implementation User

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{
        @"name": @"name",
        @"age": @"age",
        @"isStudent": @"isStudent",
        @"hobbies": @"hobbies",
        @"address": @"address"
    };
}

+ (NSValueTransformer *)hobbiesJSONTransformer {
    return [NSValueTransformer valueTransformerForName:MTLArrayTransformerName];
}

@end
- **转换 JSON 数据**:使用 Mantle 进行 JSON 数据到模型的转换非常简单。
NSString *jsonString = @"{\"name\":\"John\",\"age\":30,\"isStudent\":false,\"hobbies\":[\"reading\",\"swimming\"],\"address\":{\"city\":\"New York\",\"country\":\"USA\"}}";
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
User *user = [MTLJSONAdapter modelOfClass:[User class] fromJSONData:jsonData error:&error];
if (user &&!error) {
    NSLog(@"姓名: %@, 年龄: %ld, 是否学生: %@", user.name, (long)user.age, user.isStudent? @"是" : @"否");
} else {
    NSLog(@"转换失败: %@", error);
}
  1. 使用 JSONModel:JSONModel 也是一个方便的模型转换库。
    • 安装 JSONModel:同样可以通过 CocoaPods 安装,在 Podfile 中添加 pod 'JSONModel',然后执行 pod install
    • 创建模型类:基于 JSONModel 的模型类需要继承自 JSONModel
#import <JSONModel/JSONModel.h>

@interface User : JSONModel

@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, assign) BOOL isStudent;
@property (nonatomic, strong) NSArray<NSString *> *hobbies;
@property (nonatomic, strong) NSDictionary *address;

@end

@implementation User

@end
- **转换 JSON 数据**:
NSString *jsonString = @"{\"name\":\"John\",\"age\":30,\"isStudent\":false,\"hobbies\":[\"reading\",\"swimming\"],\"address\":{\"city\":\"New York\",\"country\":\"USA\"}}";
NSError *error;
User *user = [[User alloc] initWithDictionary:@{
    @"name": @"John",
    @"age": @30,
    @"isStudent": @NO,
    @"hobbies": @[@"reading", @"swimming"],
    @"address": @{
        @"city": @"New York",
        @"country": @"USA"
    }
} error:&error];
if (user &&!error) {
    NSLog(@"姓名: %@, 年龄: %ld, 是否学生: %@", user.name, (long)user.age, user.isStudent? @"是" : @"否");
} else {
    NSLog(@"转换失败: %@", error);
}

四、处理复杂 JSON 结构的模型转换

4.1 嵌套模型

当 JSON 数据包含嵌套对象时,我们需要创建相应的嵌套模型类。例如,对于以下 JSON 结构:

{
    "user": {
        "name": "Alice",
        "age": 25,
        "contact": {
            "phone": "123 - 456 - 7890",
            "email": "alice@example.com"
        }
    }
}
  1. 创建模型类
#import <Foundation/Foundation.h>

@interface Contact : NSObject

@property (nonatomic, strong) NSString *phone;
@property (nonatomic, strong) NSString *email;

@end

@implementation Contact

@end

@interface User : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, strong) Contact *contact;

@end

@implementation User

@end
  1. 模型转换
NSDictionary *userDict = @{
    @"user": @{
        @"name": @"Alice",
        @"age": @25,
        @"contact": @{
            "phone": @"123 - 456 - 7890",
            "email": @"alice@example.com"
        }
    }
};
NSDictionary *innerDict = userDict[@"user"];
User *user = [[User alloc] init];
[user setValue:innerDict[@"name"] forKey:@"name"];
[user setValue:innerDict[@"age"] forKey:@"age"];

Contact *contact = [[Contact alloc] init];
[contact setValue:innerDict[@"contact"][@"phone"] forKey:@"phone"];
[contact setValue:innerDict[@"contact"][@"email"] forKey:@"email"];
[user setValue:contact forKey:@"contact"];

NSLog(@"姓名: %@, 年龄: %ld, 电话: %@, 邮箱: %@", user.name, (long)user.age, user.contact.phone, user.contact.email);

4.2 数组中的嵌套模型

如果 JSON 数据是一个包含嵌套模型的数组,例如:

[
    {
        "name": "Alice",
        "age": 25,
        "contact": {
            "phone": "123 - 456 - 7890",
            "email": "alice@example.com"
        }
    },
    {
        "name": "Bob",
        "age": 23,
        "contact": {
            "phone": "098 - 765 - 4321",
            "email": "bob@example.com"
        }
    }
]
  1. 模型转换
NSArray *usersArray = @[
    @{
        @"name": @"Alice",
        @"age": @25,
        @"contact": @{
            "phone": @"123 - 456 - 7890",
            "email": "alice@example.com"
        }
    },
    @{
        @"name": @"Bob",
        @"age": @23,
        @"contact": @{
            "phone": "098 - 765 - 4321",
            "email": "bob@example.com"
        }
    }
];
NSMutableArray *userModels = [NSMutableArray array];
for (NSDictionary *userDict in usersArray) {
    User *user = [[User alloc] init];
    [user setValue:userDict[@"name"] forKey:@"name"];
    [user setValue:userDict[@"age"] forKey:@"age"];

    Contact *contact = [[Contact alloc] init];
    [contact setValue:userDict[@"contact"][@"phone"] forKey:@"phone"];
    [contact setValue:userDict[@"contact"][@"email"] forKey:@"email"];
    [user setValue:contact forKey:@"contact"];

    [userModels addObject:user];
}
for (User *user in userModels) {
    NSLog(@"姓名: %@, 年龄: %ld, 电话: %@, 邮箱: %@", user.name, (long)user.age, user.contact.phone, user.contact.email);
}

五、性能优化与注意事项

5.1 性能优化

  1. 减少内存占用:在解析大的 JSON 数据时,尽量避免一次性将整个 JSON 数据解析为内存中的对象。可以考虑逐行解析或者使用流解析的方式,减少内存峰值。例如,可以使用 NSJSONReadingStreamed 选项来进行流解析。
NSString *largeJsonString = @"...";// 大的 JSON 字符串
NSData *largeJsonData = [largeJsonString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
id jsonObject = [NSJSONSerialization JSONObjectWithData:largeJsonData options:NSJSONReadingStreamed error:&error];
if (jsonObject &&!error) {
    NSLog(@"解析成功: %@", jsonObject);
} else {
    NSLog(@"解析失败: %@", error);
}
  1. 缓存解析结果:如果 JSON 数据不经常变化,可以考虑缓存解析后的模型对象,避免重复解析。可以使用 NSCache 或者自定义的缓存机制。
NSCache *cache = [[NSCache alloc] init];
NSString *jsonKey = @"your_json_key";
id cachedObject = [cache objectForKey:jsonKey];
if (cachedObject) {
    // 使用缓存的对象
} else {
    // 解析 JSON 数据并缓存
    NSString *jsonString = @"{...}";
    NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
    NSError *error;
    id jsonObject = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error];
    if (jsonObject &&!error) {
        [cache setObject:jsonObject forKey:jsonKey];
    }
}

5.2 注意事项

  1. 数据验证:在将 JSON 数据转换为模型时,要进行充分的数据验证,防止非法数据导致程序崩溃。例如,检查 JSON 数据的格式是否正确,以及数据类型是否与模型属性匹配。
  2. 内存管理:在使用 KVC 或第三方库进行模型转换时,要注意内存管理。确保对象的生命周期正确,避免内存泄漏。例如,在使用 setValuesForKeysWithDictionary: 方法时,要注意字典中的对象是否会被模型对象持有,以及何时释放这些对象。
  3. 版本兼容性:如果 JSON 数据的结构可能会随着后端服务的更新而改变,要考虑版本兼容性。可以在 JSON 数据中添加版本字段,在解析时根据版本号进行不同的处理,或者使用灵活的模型转换策略,使得模型能够适应一定程度的结构变化。

在 Objective - C 开发中,合理地处理 JSON 解析与模型转换是构建高效、稳定应用的关键环节。通过选择合适的方法和工具,并注意性能优化与各种注意事项,可以提升应用的质量和用户体验。无论是简单的 JSON 数据还是复杂的嵌套结构,都能通过上述方案有效地进行处理。