aryaxt / OCMapper

Objective-C library to easily map NSDictionary to model objects, works perfectly with Alamofire. ObjectMapper works similar to GSON
MIT License
347 stars 45 forks source link

[[ObjectMapper sharedInstance] dictionaryFromObject:object] doesn't work if the object comes from a framework #36

Open fatuhoku opened 9 years ago

fatuhoku commented 9 years ago

I know that MESRecipePreviewRecipeIngredientViewModel can be converted into a dictionary normally in a standard iOS app project. The moment I moved it to another private pod framework, I saw this error:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (MESRecipePreviewRecipeIngredientViewModel)'
*** First throw call stack:
(
    0   CoreFoundation                      0x0000000103ccac65 __exceptionPreprocess + 165
    1   libobjc.A.dylib                     0x0000000103963bb7 objc_exception_throw + 45
    2   CoreFoundation                      0x0000000103ccab9d +[NSException raise:format:] + 205
    3   Foundation                          0x00000001035fafd0 _writeJSONValue + 689
    4   Foundation                          0x00000001035ff34d ___writeJSONArray_block_invoke + 130
    5   CoreFoundation                      0x0000000103c14026 __53-[__NSArrayI enumerateObjectsWithOptions:usingBlock:]_block_invoke + 70
    6   CoreFoundation                      0x0000000103c13f5c -[__NSArrayI enumerateObjectsWithOptions:usingBlock:] + 284
    7   Foundation                          0x00000001035ff262 _writeJSONArray + 264
    8   Foundation                          0x00000001035faf3c _writeJSONValue + 541
    9   Foundation                          0x00000001035ff46f ___writeJSONObject_block_invoke + 220
    10  CoreFoundation                      0x0000000103c3ecd5 __65-[__NSDictionaryI enumerateKeysAndObjectsWithOptions:usingBlock:]_block_invoke + 85
    11  CoreFoundation                      0x0000000103c3ebec -[__NSDictionaryI enumerateKeysAndObjectsWithOptions:usingBlock:] + 236
    12  Foundation                          0x00000001035ff080 _writeJSONObject + 376
    13  Foundation                          0x00000001035faea6 _writeJSONValue + 391
    14  Foundation                          0x00000001035ff46f ___writeJSONObject_block_invoke + 220
    15  CoreFoundation                      0x0000000103c3ecd5 __65-[__NSDictionaryI enumerateKeysAndObjectsWithOptions:usingBlock:]_block_invoke + 85
    16  CoreFoundation                      0x0000000103c3ebec -[__NSDictionaryI enumerateKeysAndObjectsWithOptions:usingBlock:] + 236
    17  Foundation                          0x00000001035ff080 _writeJSONObject + 376
    18  Foundation                          0x00000001035faea6 _writeJSONValue + 391
    19  Foundation                          0x00000001035ff46f ___writeJSONObject_block_invoke + 220
    20  CoreFoundation                      0x0000000103c3ecd5 __65-[__NSDictionaryI enumerateKeysAndObjectsWithOptions:usingBlock:]_block_invoke + 85
    21  CoreFoundation                      0x0000000103c3ebec -[__NSDictionaryI enumerateKeysAndObjectsWithOptions:usingBlock:] + 236
    22  Foundation                          0x00000001035ff080 _writeJSONObject + 376
    23  Foundation                          0x00000001035faea6 _writeJSONValue + 391
    24  Foundation                          0x00000001035ff34d ___writeJSONArray_block_invoke + 130
    25  CoreFoundation                      0x0000000103c14026 __53-[__NSArrayI enumerateObjectsWithOptions:usingBlock:]_block_invoke + 70
    26  CoreFoundation                      0x0000000103c13f5c -[__NSArrayI enumerateObjectsWithOptions:usingBlock:] + 284
    27  Foundation                          0x00000001035ff262 _writeJSONArray + 264
    28  Foundation                          0x00000001035faf3c _writeJSONValue + 541
    29  Foundation                          0x00000001035ff34d ___writeJSONArray_block_invoke + 130
    30  CoreFoundation                      0x0000000103c14026 __53-[__NSArrayI enumerateObjectsWithOptions:usingBlock:]_block_invoke + 70
    31  CoreFoundation                      0x0000000103c13f5c -[__NSArrayI enumerateObjectsWithOptions:usingBlock:] + 284
    32  Foundation                          0x00000001035ff262 _writeJSONArray + 264
    33  Foundation                          0x00000001035faf3c _writeJSONValue + 541
    34  Foundation                          0x00000001035ff46f ___writeJSONObject_block_invoke + 220
    35  CoreFoundation                      0x0000000103c3ecd5 __65-[__NSDictionaryI enumerateKeysAndObjectsWithOptions:usingBlock:]_block_invoke + 85
    36  CoreFoundation                      0x0000000103c3ebec -[__NSDictionaryI enumerateKeysAndObjectsWithOptions:usingBlock:] + 236
    37  Foundation                          0x00000001035ff080 _writeJSONObject + 376
    38  Foundation                          0x00000001035faea6 _writeJSONValue + 391
    39  Foundation                          0x00000001035ff34d ___writeJSONArray_block_invoke + 130
    40  CoreFoundation                      0x0000000103c14026 __53-[__NSArrayI enumerateObjectsWithOptions:usingBlock:]_block_invoke + 70
    41  CoreFoundation                      0x0000000103c13f5c -[__NSArrayI enumerateObjectsWithOptions:usingBlock:] + 284
    42  Foundation                          0x00000001035ff262 _writeJSONArray + 264
    43  Foundation                          0x00000001035faf3c _writeJSONValue + 541
    44  Foundation                          0x00000001035facea -[_NSJSONWriter dataWithRootObject:options:error:] + 137
    45  Foundation                          0x00000001035fd76b +[NSJSONSerialization dataWithJSONObject:options:error:] + 345
    46  React                               0x0000000101d2b2ee RCTJSONStringify + 94
    47  React                               0x0000000101c99152 __70-[RCTContextExecutor executeJSCall:method

I.e. I was trying to use OCMapper to convert my ViewModel class into a dictionary that React Native can consume. RN is barfing because the object didn't get converted at all

aryaxt commented 9 years ago

what do you mean by doesn't work? What's the behavior?

Would it work if you update the code according to this PR? https://github.com/aryaxt/OCMapper/pull/34/files

fatuhoku commented 9 years ago

@aryaxt Hi aryaxt, thanks for the super quick reply.

Yes I noticed the cause is definitely the bundle checking.

...
    // For example when we are mapping an array of string, we shouldn't try to map the string objects inside the array
    if ([NSBundle mainBundle] != [NSBundle bundleForClass:object.class] && [object class] != [NSArray class])
    {
        return object;
    }

I think the pull request might go some way to fixing it... I'll need to try that.

fatuhoku commented 9 years ago

@aryaxt I checked out the version from the pull request but it's not helped.

I don't understand why bundle checking is necessary at all. It really shouldn't matter whether the class that I'm trying to convert belongs in one bundle or another — it's just a class, right :S?

Plus, I'm using AppCode, not Xcode. When I debugged the comparison between mainBundlePath and classBundlePath, I found their values were WILDLY different:

mainBundlePath = {__NSCFString * | 0x7ff013d6eb20} "/Users/myusername/Library/Developer/CoreSimulator/Devices/4CCEBA75-D7CE-408F-8135-97927736A940/data/Containers/Bundle/Application/854F84EA-647C-4B99-A94B-E5E94186714B/MakeEatSeeRNUI_Example.app"
classBundlePath = {__NSCFString * | 0x7ff016005020} "/Users/myusername/Library/Caches/AppCode32/DerivedData/MakeEatSeeRNUI-8ccb4a77/Build/Products/Debug-iphonesimulator/MakeEatSeePresenters.framework"

So yeah, there's just no way this comparison could possibly succeed. This causes -dictionaryFromObject:object to fail and return object. Not very useful at all.

aryaxt commented 9 years ago

yeah the code to detect project-specific classes need to be reworked, right now it only works if the models are in the main bundle

To understand the reason behind that logic, you can pull my code, comment out the code, and run the unit tests. Fell free to open a PR if you find a solution. I'll look into it myself as well

fatuhoku commented 9 years ago

Hmmm. The relevant test in question is this:

- (void)testShouldMapArrayOfStringFromObjectToDictionary
{
    User *user = [[User alloc] init];
    user.randomKeywords = @[@"keyword1", @2].mutableCopy;

    NSDictionary *dictionary = [self.mapper dictionaryFromObject:user];
    NSArray *array = [dictionary objectForKey:@"randomKeywords"];

    XCTAssertTrue(array.count == 2);
    XCTAssertTrue([array[0] isEqualToString:@"keyword1"]);
    XCTAssertTrue([array[1] isEqualToNumber:@2]);
}

:/ the implementation appears to make quite a lot of assumptions about what classes belong in bundles and which don't. I've got a wild idea: how about check for the NS prefix from the class name instead?

aryaxt commented 9 years ago

Let me think about the NS prefix, maybe it could be in additions to bundle checking. Bundle checking is very reliable as long as your models are in the main bundle

fatuhoku commented 9 years ago

@aryaxt Yeah I gave just checking for the NS prefix a quick try, but one of the tests still failed.

aryaxt commented 9 years ago

@fatuhoku Did you manage to find a solution? I haven't had time to look into this

fatuhoku commented 9 years ago

@aryaxt Yes — I just used HRCoder + AutoCoding to serialise the JSON out for React Native consumption instead! I'm not sure how they solve the cross-bundle issue. Probably worth checking the HRCoder code.