0

json 解析有什么可说道的

 2 years ago
source link: https://mp.weixin.qq.com/s/_jFHgAP0vKx1Cv9XGkh_DA
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

1. json是什么

JSON ,是 JavaScript Object Notation 的缩写。其实,JSON 最初是被设计为 JavaScript 语言的一个子集,但最终因为和编程语言无关,所以成为了一种开放标准的常见数据格式。
JSON 这种文本数据交换格式易读,且结构简单。JSON 基于两种结构:

  • 名字 / 值对集合:这种结构在其他编程语言里被实现为对象、字典、Hash 表、结构体或者关联数组。

  • 有序值列表:这种结构在其他编程语言里被实现为数组、向量、列表或序列。

2. YYModel json解析实现

///类型结构体
YYEncodingNSType;
///获取传入的类型
static force_inline YYEncodingNSType YYClassGetNSType(Class cls);
///是否number
static force_inline BOOL YYEncodingTypeIsCNumber(YYEncodingType type);
///转number
static force_inline NSNumber *YYNSNumberCreateFromID(__unsafe_unretained id value);
///字符串转date
static force_inline NSDate *YYNSDateFromString(__unsafe_unretained NSString *string);
///获取NSBlock类
static force_inline Class YYNSBlockClass();
///获取 ISO date formatter yyyy-MM-dd'T'HH:mm:ssZ
static force_inline NSDateFormatter *YYISODateFormatter();
///自定义key为数组情况时,循环取值,每次代替
static force_inline id YYValueForKeyPath(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *keyPaths);
///key key可能为数组
static force_inline id YYValueForMultiKeys(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *multiKeys);
640?wx_fmt=png

调用路径:

+ (instancetype)modelWithDictionary:(NSDictionary *)dictionary {
if (!dictionary || dictionary == (id)kCFNull) return nil;
if (![dictionary isKindOfClass:[NSDictionary class]]) return nil;

Class cls = [self class];
///生成_YYModelMeta对象
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
///如果自定义类
if (modelMeta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
}

NSObject *one = [cls new];
///给对象set
if ([one modelSetWithDictionary:dictionary]) return one;
return nil;
}

- (BOOL)modelSetWithDictionary:(NSDictionary *)dic {
if (!dic || dic == (id)kCFNull) return NO;
if (![dic isKindOfClass:[NSDictionary class]]) return NO;

_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
if (modelMeta->_keyMappedCount == 0) return NO;

if (modelMeta->_hasCustomWillTransformFromDictionary) {
dic = [((id<YYModel>)self) modelCustomWillTransformFromDictionary:dic];
if (![dic isKindOfClass:[NSDictionary class]]) return NO;
}

ModelSetContext context = {0};
context.modelMeta = (__bridge void *)(modelMeta);
context.model = (__bridge void *)(self);
context.dictionary = (__bridge void *)(dic);

if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
if (modelMeta->_keyPathPropertyMetas) {
CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
if (modelMeta->_multiKeysPropertyMetas) {
CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
ModelSetWithPropertyMetaArrayFunction,
&context);
}
} else {
CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
CFRangeMake(0, modelMeta->_keyMappedCount),
ModelSetWithPropertyMetaArrayFunction,
&context);
}

if (modelMeta->_hasCustomTransformFromDictionary) {
return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
}
return YES;
}
核心setter方法:
static void ModelSetValueForProperty(__unsafe_unretained id model,
__unsafe_unretained id value,
__unsafe_unretained _YYModelPropertyMeta *meta)

//数组:
case YYEncodingTypeNSArray:
case YYEncodingTypeNSMutableArray:
for {
if (meta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:one];
if (!cls) cls = meta->_genericCls; // for xcode code coverage
}
NSObject *newOne = [cls new];
[newOne modelSetWithDictionary:one];
}

//字典
case YYEncodingTypeNSDictionary:
case YYEncodingTypeNSMutableDictionary: {
if (meta->_hasCustomClassFromDictionary) {
cls = [cls modelCustomClassForDictionary:one];
if (!cls) cls = meta->_genericCls; // for xcode code coverage
}
NSObject *newOne = [cls new];
[newOne modelSetWithDictionary:one];
}

异常处理:

 case YYEncodingTypeNSString:
case YYEncodingTypeNSMutableString: {
if ([value isKindOfClass:[NSString class]]) {
if (meta->_nsType == YYEncodingTypeNSString) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
} else {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSString *)value).mutableCopy);
}
} else if ([value isKindOfClass:[NSNumber class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta->_setter,
(meta->_nsType == YYEncodingTypeNSString) ?
((NSNumber *)value).stringValue :
((NSNumber *)value).stringValue.mutableCopy);
} else if ([value isKindOfClass:[NSData class]]) {
NSMutableString *string = [[NSMutableString alloc] initWithData:value encoding:NSUTF8StringEncoding];
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, string);
} else if ([value isKindOfClass:[NSURL class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta->_setter,
(meta->_nsType == YYEncodingTypeNSString) ?
((NSURL *)value).absoluteString :
((NSURL *)value).absoluteString.mutableCopy);
} else if ([value isKindOfClass:[NSAttributedString class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta->_setter,
(meta->_nsType == YYEncodingTypeNSString) ?
((NSAttributedString *)value).string :
((NSAttributedString *)value).string.mutableCopy);
}
} break;

这里假如说我们申明的是string类型,内部会进行转化,保证取值正常

内部定义类

  • YYClassIvarInfo

  • YYClassMethodInfo

  • YYClassPropertyInfo

  • YYClassInfo

  • _YYModelPropertyMeta

  • _YYModelMeta

_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
metaWithClass 里有一个dic用于缓存,没有才会新建

- (instancetype)initWithClass:(Class)cls {
YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];
...获取黑名单
...获取白名单
...自定义映射类
...自定义模型是否响应映射方法
}
640?wx_fmt=png

YYClassInfo初始化,
在update里,利用runtime给_methodInfos, _ivarInfos, _propertyInfos赋值

 Method *methods = class_copyMethodList(cls, &methodCount);
objc_property_t *properties = class_copyPropertyList(cls, &propertyCount);
Ivar *ivars = class_copyIvarList(cls, &ivarCount);

总结一下,主要流程:
modelArrayWithClass->modelWithDictionary->modelSetWithDictionary->ModelSetWithPropertyMetaArrayFunction->ModelSetValueForProperty(如果为array或者dic 且模型里实现对应方法,继续递归)

modelSetWithDictionary: 生成_YYModelMeta,同时处理自定义模型里的黑白名单,映射等等,YYClassInfo,以及存储array
YYClassInfo _update 获取方法,ivar,属性

2. SwiftyJSON解析实现

1. 初始化
fileprivate init(jsonObject: Any) {
object = jsonObject
}
2. set方法

string-> rawString
array-> rawArray
dictionary-> rawDictionary

public var object: Any {
get {
switch type {
case .array: return rawArray
case .dictionary: return rawDictionary
case .string: return rawString
case .number: return rawNumber
case .bool: return rawBool
default: return rawNull
}
}
set {
error = nil
switch unwrap(newValue) {
case let number as NSNumber:
if number.isBool {
type = .bool
rawBool = number.boolValue
} else {
type = .number
rawNumber = number
}
case let string as String:
type = .string
rawString = string
case _ as NSNull:
type = .null
case nil:
type = .null
case let array as [Any]:
type = .array
rawArray = array
case let dictionary as [String: Any]:
type = .dictionary
rawDictionary = dictionary
default:
type = .unknown
error = SwiftyJSONError.unsupportedType
}
}
}
3. 取值方法:
3.1 如果是数组取值,json[0]。从set里的rawArray取值,并做异常判断

fileprivate subscript(index index: Int) -> JSON {
get {
if type != .array {
var r = JSON.null
r.error = self.error ?? SwiftyJSONError.wrongType
return r
} else if rawArray.indices.contains(index) {
return JSON(rawArray[index])
} else {
var r = JSON.null
r.error = SwiftyJSONError.indexOutOfBounds
return r
}
}
set {
if type == .array &&
rawArray.indices.contains(index) &&
newValue.error == nil {
rawArray[index] = newValue.object
}
}
}
3.2 如果是hash。从rawDictionary取值,并且做好异常处理
    /// If `type` is `.dictionary`, return json whose object is `dictionary[key]` , otherwise return null json with error.
fileprivate subscript(key key: String) -> JSON {
get {
var r = JSON.null
if type == .dictionary {
if let o = rawDictionary[key] {
r = JSON(o)
} else {
r.error = SwiftyJSONError.notExist
}
} else {
r.error = self.error ?? SwiftyJSONError.wrongType
}
return r
}
set {
if type == .dictionary && newValue.error == nil {
rawDictionary[key] = newValue.object
}
}
}
4. 取值方法,比如常用的stringValue ,numberValue,arrayValue
640?wx_fmt=png

get方法

640?wx_fmt=png

自定义运算符实现比较方法

数据递归解包装,这个方法不知道作用何在,但是占用了大部分的时间复杂度。在项目中试了一下,在set方法中不掉用,并没有发生异常。如果传入的是正常数据(类似array,dictionary)的话,这个递归方法好像仅仅是过了一遍。好像是,我传入了什么,出来的还是什么。

private func unwrap(_ object: Any) -> Any {
switch object {
case let json as JSON:
return unwrap(json.object)
case let array as [Any]:
return array.map(unwrap)
case let dictionary as [String: Any]:
var d = dictionary
dictionary.forEach { pair in
d[pair.key] = unwrap(pair.value)
}
return d
default:
return object
}
}

1. 两者比较

YYModel 是通过runtime获取到所有的属性,递归调用setter方法,实现数据,转化成model

SwiftyJSON 是用结构体保存了array,dic,string,number,null,bool等基本数据类型,每次set时候根据数据类型,赋值给对应的变量里。然后做了众多扩展方法,包括:

  • rawValue, 定义了stringValue等取值方法其中做了安全处理

  • 自定义运算符,实现Comparable

  • 自定义encode,decode 实现Codable

不好的地方在于模型转化全部需要手动处理

但是也存在一些共性:

  1. 安全的,都对异常情况做了处理

  2. 都运用了递归的思想

拓展:网上比较出名的swift json解析库

由于语言特性的问题,swift无法获取到类的属性

640?wx_fmt=png

swift中使用YYModel

如上,在swift model中使用YYModel转化,properties数据为空,无法获取到模型属性。这也是为什么swfit项目需要引入SwiftyJSON等swift框架。

先看看ObjectMapper是如何实现的

struct Temperature: Mappable {
var celsius: Double?
var fahrenheit: Double?

init?(map: Map) {

}

mutating func mapping(map: Map) {
celsius <- map["celsius"]
fahrenheit <- map["fahrenheit"]
}
}

从这个demo中,我们可以看到model必须实现Mappable,并在mapping里实现映射关系。和swiftyJson的思路类似,都是手动添加value与model属性对应关系。

那么有没有类似OC的不需要手动添加映射关系的swfit json转model库呢?

HandyJSON是阿里推出JSON转Model库,使用类似YYModel

HandyJSON目前依赖于从Swift Runtime源码中推断的内存规则,

Swift反射+内存赋值的方式来构造Model实例,
从类信息里获取所有属性的特征,包括名称,属性在内存里的偏移量、属性的个数、属性的类型等等,然后将服务端返回来的数据用操作内存的方式将数值写入对应的内存,来实现json转model。

HandyJSON的基本原理就是从类信息里获取所有属性的特征,包括名称,属性在内存里的偏移量、属性的个数、属性的类型等等,然后将服务端返回来的数据用操作内存的方式将数值写入对应的内存,来实现json转model。

但是,假如说苹果更改了metadata 结构,那么项目中所有使用HandyJSON的model就可能无法正确赋值。会产生灾难性后果!
可能这也是为什么HandyJSON只有3.8k星的原因吧~

TODO:

  1. swifty unwrap方法的原因

  2. HandyJson如何在内存层面获取到所有的属性

参考:
HandyJSON浅析


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK