Skip to content

Commit

Permalink
- Refactor model -> JSON function.
Browse files Browse the repository at this point in the history
- Add white list and black list for model -> JSON conversion.
- Remove useless APIs.
- Complete the missing test cases implementation.
  • Loading branch information
wolfcon committed Jan 6, 2022
1 parent c0b26ee commit 097fbbf
Show file tree
Hide file tree
Showing 12 changed files with 442 additions and 328 deletions.
1 change: 1 addition & 0 deletions MJExtension/MJEClass.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ NS_ASSUME_NONNULL_BEGIN
NSInteger _propertiesCount;
NSLocale * _Nullable _numberLocale;
NSDateFormatter * _Nullable _dateFormatter;
BOOL _shouldReferenceKeyReplacementInJSONExport;
}

- (void)setNeedsUpdate;
Expand Down
85 changes: 57 additions & 28 deletions MJExtension/MJEClass.m
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,15 @@ - (instancetype)initWithClass:(Class)cls {
if ([cls respondsToSelector:@selector(mj_shouldAutoInheritConfigurations)]) {
shouldAutoInheritFromSuper = [cls mj_shouldAutoInheritConfigurations];
}
_shouldReferenceKeyReplacementInJSONExport = YES;
if ([cls respondsToSelector:@selector(mj_shouldReferenceKeyReplacementInJSONExport)]) {
_shouldReferenceKeyReplacementInJSONExport = [cls mj_shouldReferenceKeyReplacementInJSONExport];
}

NSMutableSet *ignoredList = [NSMutableSet new];
NSMutableSet *allowedList = [NSMutableSet new];
NSMutableSet *ignoredList2JSON = [NSMutableSet new];
NSMutableSet *allowedList2JSON = [NSMutableSet new];
NSMutableSet *ignoredCodingList = [NSMutableSet new];
NSMutableSet *allowedCodingList = [NSMutableSet new];
NSMutableDictionary *genericClasses = [NSMutableDictionary new];
Expand All @@ -65,19 +71,29 @@ - (instancetype)initWithClass:(Class)cls {
@selector(mj_allowedPropertyNames),
allowedList);

// get ignored property names
// get ignored property names to JSON
MJEAddSelectorResult2Set(currentClass,
@selector(mj_ignoredPropertyNamesToJSON),
ignoredList2JSON);

// get allowed property names to JSON
MJEAddSelectorResult2Set(currentClass,
@selector(mj_allowedPropertyNamesToJSON),
allowedList2JSON);

// get ignored coding property names
MJEAddSelectorResult2Set(currentClass,
@selector(mj_ignoredCodingPropertyNames),
ignoredCodingList);

// get allowed property names
// get allowed coding property names
MJEAddSelectorResult2Set(currentClass,
@selector(mj_allowedCodingPropertyNames),
allowedCodingList);

// get old value to new one property name list
MJEAddSelectorResult2Set(currentClass,
@selector(mj_modifyOld2NewPropertyNames),
@selector(mj_modifyOldToNewPropertyNames),
old2NewList);

// get generic classes
Expand Down Expand Up @@ -110,6 +126,8 @@ - (instancetype)initWithClass:(Class)cls {
// get the property lists
[self mj_handlePropertiesWithAllowedList:allowedList
ignoredList:ignoredList
allowedList2JSON:allowedList2JSON
ignoredList2JSON:ignoredList2JSON
allowedCodingList:allowedCodingList
ignoredCodingList:ignoredCodingList
old2NewList:old2NewList
Expand Down Expand Up @@ -141,8 +159,8 @@ + (instancetype)cachedClass:(Class)cls {
classCache = [NSMutableDictionary new];
lock = dispatch_semaphore_create(1);
});
// uses only 1 lock to avoid concurrent operation from a mess.
// too many locks before 4.0.0 version.
// Uses only 1 lock to avoid concurrent operation from a mess.
// There are too many locks before 4.0.0 version.
MJ_LOCK(lock);
MJEClass *cachedClass = classCache[cls];
if (!cachedClass || cachedClass->_needsUpdate) {
Expand All @@ -157,6 +175,8 @@ + (instancetype)cachedClass:(Class)cls {

- (void)mj_handlePropertiesWithAllowedList:(NSSet *)allowedList
ignoredList:(NSSet *)ignoredList
allowedList2JSON:(NSSet *)allowedList2JSON
ignoredList2JSON:(NSSet *)ignoredList2JSON
allowedCodingList:(NSSet *)allowedCodingList
ignoredCodingList:(NSSet *)ignoredCodingList
old2NewList:(NSSet *)old2NewList
Expand All @@ -166,8 +186,7 @@ - (void)mj_handlePropertiesWithAllowedList:(NSSet *)allowedList
inClass:(Class)cls {
NSMutableArray<MJProperty *> *allProperties = [NSMutableArray array];
NSMutableArray<MJProperty *> *codingProperties = [NSMutableArray array];
// TODO: 4.0.0 new feature
// NSMutableArray<MJProperty *> *allProperties2JSON = [NSMutableArray array];
NSMutableArray<MJProperty *> *allProperties2JSON = [NSMutableArray array];
[cls mj_enumerateClasses:^(__unsafe_unretained Class c, BOOL *stop) {
// 1. get all property list
unsigned int outCount = 0;
Expand All @@ -187,6 +206,12 @@ - (void)mj_handlePropertiesWithAllowedList:(NSSet *)allowedList
[codingProperties addObject:property];
}
}
// handle properties for object to JSON conversion.
if (!allowedList2JSON.count || [allowedList2JSON containsObject:property.name]) {
if (![ignoredList2JSON containsObject:property.name]) {
[allProperties2JSON addObject:property];
}
}
// check allowed list
if (allowedList.count && ![allowedList containsObject:property.name]) continue;
// check ingored list
Expand All @@ -198,30 +223,33 @@ - (void)mj_handlePropertiesWithAllowedList:(NSSet *)allowedList
|| !old2NewList.count) {
property->_hasValueModifier = YES;
}

id key = property.name;
// Modify replaced key using special method
if (hasKeyReplacementModifier) {
key = [cls mj_replacedKeyFromPropertyName121:key] ?: key;
}
// serch key in replaced dictionary
key = replacedKeys[property.name] ?: key;

// handle keypath / keypath array / keypath array(with subkey)
[property handleOriginKey:key];

// handle generic class
id clazz = genericClasses[property.name];
if ([clazz isKindOfClass:NSString.class]) {
clazz = NSClassFromString(clazz);
{
id genericClass = genericClasses[property.name];
if ([genericClass isKindOfClass:NSString.class]) {
genericClass = NSClassFromString(genericClass);
}
property.classInCollection = genericClass;
// check the ability to change class.
if (genericClass) { // generic
property->_hasClassModifier = [genericClass respondsToSelector:@selector(mj_modifiedClassForDictionary:)];
} else if (property.isCustomModelType) { // for those superclass and subclass customization
property->_hasClassModifier = [property.typeClass respondsToSelector:@selector(mj_modifiedClassForDictionary:)];
}
}
property.classInCollection = clazz;

// check the ability to change class.
if (clazz) { // generic
property->_hasClassModifier = [clazz respondsToSelector:@selector(mj_modifiedClassForDictionary:)];
} else if (property.typeClass && property->_basicObjectType == MJEBasicTypeUndefined) { // common class (base class)
property->_hasClassModifier = [property.typeClass respondsToSelector:@selector(mj_modifiedClassForDictionary:)];
// handle key modifier and replacement
{
id key = property.name;
// Modify replaced key using special method
if (hasKeyReplacementModifier) {
key = [cls mj_replacedKeyFromPropertyName121:key] ?: key;
}
// serch key in replaced dictionary
key = replacedKeys[property.name] ?: key;

// handle keypath / keypath array / keypath array(with subkey)
[property handleOriginKey:key];
}

[allProperties addObject:property];
Expand All @@ -233,6 +261,7 @@ - (void)mj_handlePropertiesWithAllowedList:(NSSet *)allowedList

_allProperties = allProperties.copy;
_allCodingProperties = codingProperties.copy;
_allProperties2JSON = allProperties2JSON.copy;

_propertiesCount = _allProperties.count;
}
Expand Down
6 changes: 3 additions & 3 deletions MJExtension/MJExtensionPredefine.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
#define mj_msgSendOne(obj, sel, type, value) ((void (*)(id, SEL, type))objc_msgSend)(obj, sel, value)
#endif
#ifndef mj_msgSendGet
#define mj_msgSendGet(obj, sel, type) ((type (*)(id, SEL))objc_msgSend)(obj, sel)
#define mj_msgSendGet(obj, sel, type) (((type (*)(id, SEL))objc_msgSend)(obj, sel))
#endif

#ifndef MJ_LOCK
Expand Down Expand Up @@ -76,15 +76,15 @@ MJExtensionAssert2((param) != nil, returnValue)
#define MJLogAllIvars \
- (NSString *)description \
{ \
return [self mj_keyValues].description; \
return [self.mj_JSONObject description]; \
}
#define MJExtensionLogAllProperties MJLogAllIvars

/** 仅在 Debugger 展示所有的属性 */
#define MJImplementDebugDescription \
- (NSString *)debugDescription \
{ \
return [self mj_keyValues].debugDescription; \
return [self.mj_JSONObject description]; \
}

#endif
17 changes: 15 additions & 2 deletions MJExtension/MJExtensionProtocols.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
- (id)mj_newValueFromOldValue:(id)oldValue property:(MJProperty *)property;
/// Filters for `mj_newValueFromOldValue:property` protocol method usage.
/// @discussion Boosts speed significantly for converting to object if sets.
+ (NSArray *)mj_modifyOld2NewPropertyNames;
+ (NSArray *)mj_modifyOldToNewPropertyNames;

/// Called after the object has been converted from JSON.
/// @param keyValues JSON dictionary
Expand Down Expand Up @@ -48,16 +48,26 @@
/// Array for those properties that should be ignored.
+ (NSArray *)mj_ignoredPropertyNames;

/// Array for those properties that should only be allowed when converts into json. If not implemented, `mj_allowedPropertyNames` will be used.
+ (NSArray *)mj_allowedPropertyNamesToJSON;

/// Array for those properties that should be ignored when converts into json. If not implemented, `mj_ignoredPropertyNames` will be used.
+ (NSArray *)mj_ignoredPropertyNamesToJSON;

/// Dictionary for each property to correspond with replaced key.
/// @discussion - `key` is property name
///
/// - `value` is the replaced key to be recognized in JSON.
/// @warning This configuration has lower priorty to `mj_replacedKeyFromPropertyName121`. MJExtension would check the modifier `mj_replacedKeyFromPropertyName121` first, then this method.
/// @remark It is highly recommended to use these two methods separately.
+ (NSDictionary *)mj_replacedKeyFromPropertyName;

/// Gives an opportunity to customize modifier, which can be used replace property name to the corresponding key in dictionary.
/// @param propertyName property name
/// @discussion For examples:
/// Use @code propertyName.mj_underlineFromCamel @endcode in this category @link NSString+MJExtension @/link to do some changes.
/// @warning This configuration has higher priorty to `mj_replacedKeyFromPropertyName`. MJExtension would check this modifier first, then `mj_replacedKeyFromPropertyName`.
/// @remark It is highly recommended to use these two methods separately.
+ (id)mj_replacedKeyFromPropertyName121:(NSString *)propertyName;

/// Dictionary for each collection property (Array, Dictionary, Set, etc...) to correspond with object class.
Expand Down Expand Up @@ -85,7 +95,10 @@
+ (NSDateFormatter *)mj_dateFormatter;

/// Inherits configurations from super class or not.
/// If not configurate, default value is YES.
/// @discussion If not implements, default value is YES.
+ (BOOL)mj_shouldAutoInheritConfigurations;

/// Only works in replaced key configuration of JSON to Object. Other configurations are referenced.
/// @discussion Default value is `true`(YES), if not implements it.
+ (BOOL)mj_shouldReferenceKeyReplacementInJSONExport;
@end
15 changes: 9 additions & 6 deletions MJExtension/MJProperty.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,19 +97,21 @@ NS_ASSUME_NONNULL_BEGIN
BOOL _isBasicNumber;
}

/// Property name that defined by class.
/// `Property` name that defined by class.
@property (nonatomic, readonly) NSString *name;
/// Property ivar name synthesized
/// `Property` ivar name synthesized
@property (nonatomic, readonly) NSString *ivarName;
/// Property value type that defined by class.
/// `Property` value type that defined by belonged class.
@property (nonatomic, readonly) MJEPropertyType type;
/// True if _isBasicNumber or number object(NSNumber, NSDecimalNumber)
@property (nonatomic, readonly) BOOL isNumber;
/// This property belonged by what class. It may be from super class.
/// `Property` belonged by what class. It may be from super class.
@property (nonatomic, readonly) Class srcClass;
/// The property type class could be nil if property is a standard value(int / double, Class ...)
/// The `Property` type class could be nil if `Property` is a standard value(int / double, Class ...)
@property (nullable, nonatomic, readonly) Class typeClass;
/// If class type is a collection, this property is the type of those element in it.
/// If the `Property` type is a not standard value(typeClass == nil) nor a basic object type(_basicObjectType == .undefined), this value will be true(YES).
@property (nonatomic, readonly) BOOL isCustomModelType;
/// If class type is a collection, this value is the type of those element in it.
@property (nonatomic) Class classInCollection;
/// Could be nil if getter is not reponded in srcClass instances.
@property (nullable, nonatomic) SEL getter;
Expand All @@ -121,6 +123,7 @@ NS_ASSUME_NONNULL_BEGIN

- (void)setValue:(id)value forObject:(id)object;
- (id)valueForObject:(id)object;
- (NSNumber *)numberForObject:(id)object;

- (id)valueInDictionary:(NSDictionary *)dictionary;

Expand Down
47 changes: 39 additions & 8 deletions MJExtension/MJProperty.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#import "NSString+MJExtension.h"
#import "NSString+MJExtension_Private.h"

#define mj_objGet(obj, type) mj_msgSendGet(obj, _getter, type)

@interface NSString (MJPropertyKey)

/// If JSON key is "xxx.xxx", so add one more key for it.
Expand Down Expand Up @@ -75,6 +77,7 @@ - (MJPropertyKey *)propertyKey {
@import CoreData;

@interface MJProperty()

@end

@implementation MJProperty
Expand Down Expand Up @@ -130,6 +133,7 @@ - (instancetype)initWithProperty:(objc_property_t)property inClass:(nonnull Clas

if (_type == MJEPropertyTypeObject) {
_basicObjectType = MJEGetBasicObjectType(_typeClass);
_isCustomModelType = !_basicObjectType && _typeClass;
}
// If getter or setter is nil, sets them with property name
if (_name.length) {
Expand Down Expand Up @@ -193,24 +197,53 @@ - (BOOL)isNumber {
/**
* 获得成员变量的值
*/
- (id)valueForObject:(id)object
{
- (id)valueForObject:(id)object {
if (!_isKVCCompliant) return NSNull.null;

id value = [object valueForKey:self.name];
id value = [object valueForKey:_name];

// 32位BOOL类型转换json后成Int类型
/** https://github.com/CoderMJLee/MJExtension/issues/545 */
// 32 bit device OR 32 bit Simulator
#if defined(__arm__) || (TARGET_OS_SIMULATOR && !__LP64__)
if (_type1 == MJEPropertyTypeBool) {
value = @([(NSNumber *)value boolValue]);
if (_type == MJEPropertyTypeBool) {
value = @([value boolValue]);
}
#endif

return value;
}

- (NSNumber *)numberForObject:(id)object {
switch (_type) {
case MJEPropertyTypeBool: {
id value = @(mj_objGet(object, BOOL));
// 32位BOOL类型转换json后成Int类型
/** https://github.com/CoderMJLee/MJExtension/issues/545 */
// 32 bit device OR 32 bit Simulator
#if defined(__arm__) || (TARGET_OS_SIMULATOR && !__LP64__)
value = @([value boolValue]);
#endif
return value;
}
case MJEPropertyTypeInt64: return @(mj_objGet(object, int64_t));
case MJEPropertyTypeUInt64: return @(mj_objGet(object, uint64_t));
case MJEPropertyTypeFloat:
case MJEPropertyTypeDouble: {
double num = (double)mj_objGet(object, double);
if (isinf(num)) num = 0;
if (isnan(num)) return nil;
return @(num);
}
case MJEPropertyTypeLongDouble: {
double num = (double)mj_objGet(object, long double);
if (isinf(num)) num = 0;
if (isnan(num)) return nil;
return @(num);
}
default: return @(mj_objGet(object, int64_t));
}
}

- (id)valueInDictionary:(NSDictionary *)dictionary {
id value;
for (NSArray *propertyKeys in _mappedMultiKeys) {
Expand All @@ -229,9 +262,7 @@ - (id)valueInDictionary:(NSDictionary *)dictionary {
*/
- (void)setValue:(id)value forObject:(id)object {
if (!_isKVCCompliant || value == nil) return;
//FIXME: Bottleneck #4: Enhanced
[object setValue:value forKey:self.name];
// mj_msgSendOne(object, _setter, id, value);
}

/** 对应着字典中的key */
Expand Down
Loading

0 comments on commit 097fbbf

Please sign in to comment.