Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide a mtl_JSONSetTransformerWithModelClass function in NSValueTransformer+MTLPredefinedTransformerAdditions.m #290

Closed
fatuhoku opened this issue Mar 31, 2014 · 8 comments

Comments

@fatuhoku
Copy link

At present, Core Data users who are using standard unordered relationships in their data models will be bitten by #287.

Mantle implicitly encourages users to use NSArray in their MTLModels but Core Data enforces aforementioned relationships with NSSets. The user is then put in a situation where he must also use NSSet in Mantle, which has no convenient JSON value transformer for serialisation.

@fatuhoku fatuhoku changed the title Provide a mtl_JSONSetTransformerWithModelClass in NSValueTransformer+MTLPredefinedTransformerAdditions.m Provide a mtl_JSONSetTransformerWithModelClass function in NSValueTransformer+MTLPredefinedTransformerAdditions.m Mar 31, 2014
@adamwaite
Copy link

Think I've just run into this issue too. To clarify...

  1. CoreData stores collections in NSSet form
  2. Receiving/Sending a collection through JSON will require the collection being in NSArray form
  3. Mantle ignores somethingEntityAttributeTransformer for relationships

Therefore, we need to implement a custom somethingJSONTransformer to transform the collection between NSSet and NSArray...

That's what you're facing too, right?

This means that the following becomes redundant:

+ (NSValueTransformer *)somethingsJSONTransformer
{
    return [NSValueTransformer mtl_JSONArrayTransformerWithModelClass:[Something class]];
}

Edit:

Just read the title of the issue, that's exactly what's needed, but mtl_JSONSetTransfromer may be confusing if you're going in the direction of transforming a JSON array to an NSSet (what I'm attempting to do)

@adamwaite
Copy link

Ah hang on, NSArrays automatically transform to NSOrdereredSets, and NSOrdereredSets back to NSArrays as long as the To-Many Relationship is set as ordered in the xcdatamodel.

That solved my issue.

@fatuhoku
Copy link
Author

fatuhoku commented Apr 9, 2014

W00t! That's really neat and very good to know. Thank you!

The issue remains for those people who'd like to use unordered sets. Unordered relationships are by far more common than ordered relationships.

I've actually written a converter (it wasn't difficult) but I'll need to find some time to submit a PR for it.

@ryanmaxwell
Copy link

Here's a transformer that is basically a copy of the mtl_JSONArrayTransformerWithModelClass method, you can use it as a category or just a static class method, and it will do what you want.

+ (NSValueTransformer *)JSONSetTransformerWithModelClass:(Class)modelClass
{
    NSValueTransformer *dictionaryTransformer = [NSValueTransformer mtl_JSONDictionaryTransformerWithModelClass:modelClass];

    return [MTLValueTransformer reversibleTransformerWithForwardBlock:^ id (NSArray *dictionaries) {
        if (dictionaries == nil) return nil;

        NSAssert([dictionaries isKindOfClass:NSArray.class], @"Expected an array of dictionaries, got: %@", dictionaries);

        NSMutableSet *models = [NSMutableSet setWithCapacity:dictionaries.count];
        for (id JSONDictionary in dictionaries) {
            if (JSONDictionary == NSNull.null) {
                [models addObject:NSNull.null];
                continue;
            }

            NSAssert([JSONDictionary isKindOfClass:NSDictionary.class], @"Expected a dictionary or an NSNull, got: %@", JSONDictionary);

            id model = [dictionaryTransformer transformedValue:JSONDictionary];
            if (model == nil) continue;

            [models addObject:model];
        }

        return models;
    }
    reverseBlock:^ id (NSSet *models) {
        if (models == nil) return nil;

        NSAssert([models isKindOfClass:NSSet.class], @"Expected a set of MTLModels, got: %@", models);

        NSArray *allModels = [(NSSet *)models allObjects];

        NSMutableArray *dictionaries = [NSMutableArray arrayWithCapacity:allModels.count];
        for (id model in allModels) {
            if (model == NSNull.null) {
                [dictionaries addObject:NSNull.null];
                continue;
            }

            NSAssert([model isKindOfClass:MTLModel.class], @"Expected an MTLModel or an NSNull, got: %@", model);

            NSDictionary *dict = [dictionaryTransformer reverseTransformedValue:model];
            if (dict == nil) continue;

            [dictionaries addObject:dict];
        }

        return dictionaries;
    }];
}

@suricforever
Copy link

@adamwaite Thanks so much, This solve my question

@shaneowens
Copy link

@adamwaite @fatuhoku - Thanks for the input guys. Are you guys still using the suggested approach above? Or did you come across something better? Cheers.

@medopaw
Copy link

medopaw commented Apr 23, 2017

Here's the NSSet version of Mantle's arrayTransformerWithModelClass: and mtl_arrayMappingTransformerWithTransformer: with minimal modification:

setTransformerWithModelClass:

+ (NSValueTransformer <MTLTransformerErrorHandling> *)setTransformerWithModelClass:(Class)modelClass {
    NSValueTransformer<MTLTransformerErrorHandling> *dictionaryTransformer = [self dictionaryTransformerWithModelClass:modelClass];

    return [MTLValueTransformer transformerUsingForwardBlock:^ id (NSArray *dictionaries, BOOL *success, NSError **error) {
        if (dictionaries == nil) return nil;

        if (![dictionaries isKindOfClass:NSArray.class]) {
            if (error != NULL) {
                NSDictionary *userInfo = @{
                        NSLocalizedDescriptionKey: NSLocalizedString(@"Could not convert JSON array to model set", @""),
                        NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"Expected an NSArray, got: %@.", @""), dictionaries],
                        MTLTransformerErrorHandlingInputValueErrorKey : dictionaries
                };

                *error = [NSError errorWithDomain:MTLTransformerErrorHandlingErrorDomain code:MTLTransformerErrorHandlingErrorInvalidInput userInfo:userInfo];
            }
            *success = NO;
            return nil;
        }

        NSMutableSet *models = [NSMutableSet setWithCapacity:dictionaries.count];
        for (id JSONDictionary in dictionaries) {
            if (JSONDictionary == NSNull.null) {
                [models addObject:NSNull.null];
                continue;
            }

            if (![JSONDictionary isKindOfClass:NSDictionary.class]) {
                if (error != NULL) {
                    NSDictionary *userInfo = @{
                            NSLocalizedDescriptionKey: NSLocalizedString(@"Could not convert JSON array to model set", @""),
                            NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"Expected an NSDictionary or an NSNull, got: %@.", @""), JSONDictionary],
                            MTLTransformerErrorHandlingInputValueErrorKey : JSONDictionary
                    };

                    *error = [NSError errorWithDomain:MTLTransformerErrorHandlingErrorDomain code:MTLTransformerErrorHandlingErrorInvalidInput userInfo:userInfo];
                }
                *success = NO;
                return nil;
            }

            id model = [dictionaryTransformer transformedValue:JSONDictionary success:success error:error];

            if (*success == NO) return nil;

            if (model == nil) continue;

            [models addObject:model];
        }

        return models;
    } reverseBlock:^ id (NSSet *models, BOOL *success, NSError **error) {
        if (models == nil) return nil;

        if (![models isKindOfClass:NSSet.class]) {
            if (error != NULL) {
                NSDictionary *userInfo = @{
                        NSLocalizedDescriptionKey: NSLocalizedString(@"Could not convert model set to JSON array", @""),
                        NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"Expected an NSSet, got: %@.", @""), models],
                        MTLTransformerErrorHandlingInputValueErrorKey : models
                };

                *error = [NSError errorWithDomain:MTLTransformerErrorHandlingErrorDomain code:MTLTransformerErrorHandlingErrorInvalidInput userInfo:userInfo];
            }
            *success = NO;
            return nil;
        }

        NSMutableArray *dictionaries = [NSMutableArray arrayWithCapacity:models.count];
        for (id model in models) {
            if (model == NSNull.null) {
                [dictionaries addObject:NSNull.null];
                continue;
            }

            if (![model isKindOfClass:MTLModel.class]) {
                if (error != NULL) {
                    NSDictionary *userInfo = @{
                            NSLocalizedDescriptionKey: NSLocalizedString(@"Could not convert model set to JSON array", @""),
                            NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"Expected a MTLModel or an NSNull, got: %@.", @""), model],
                            MTLTransformerErrorHandlingInputValueErrorKey : model
                    };

                    *error = [NSError errorWithDomain:MTLTransformerErrorHandlingErrorDomain code:MTLTransformerErrorHandlingErrorInvalidInput userInfo:userInfo];
                }
                *success = NO;
                return nil;
            }

            NSDictionary *dict = [dictionaryTransformer reverseTransformedValue:model success:success error:error];

            if (*success == NO) return nil;

            if (dict == nil) continue;

            [dictionaries addObject:dict];
        }

        return dictionaries;
    }];
}

setMappingTransformerWithTransformer:

+ (NSValueTransformer <MTLTransformerErrorHandling> *)setMappingTransformerWithTransformer:(NSValueTransformer *)transformer {
    NSValueTransformer<MTLTransformerErrorHandling> *arrayMappingTransformer = [NSValueTransformer mtl_arrayMappingTransformerWithTransformer:transformer];

    id (^forwardBlock)(NSArray *jsonArray, BOOL *success, NSError **error) = ^id (NSArray *jsonArray, BOOL *success, NSError **error) {
        NSError *underlyingError = nil;
        NSArray *modelArray = [arrayMappingTransformer transformedValue:jsonArray success:success error:&underlyingError];

        if (*success == NO) {
            if (error != NULL) {
                NSDictionary *userInfo = @{
                        NSLocalizedDescriptionKey: NSLocalizedString(@"Could not transform array", @""),
                        NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"Could not transform value", @"")],
                        NSUnderlyingErrorKey: underlyingError,
                        MTLTransformerErrorHandlingInputValueErrorKey: jsonArray
                };
                *error = [NSError errorWithDomain:MTLTransformerErrorHandlingErrorDomain code:MTLTransformerErrorHandlingErrorInvalidInput userInfo:userInfo];
            }
            return nil;
        }

        if (modelArray == nil) { return nil; }

        return [NSSet setWithArray:modelArray];
    };

    id (^reverseBlock)(NSSet *models, BOOL *success, NSError **error) = nil;
    if (transformer.class.allowsReverseTransformation) {
        reverseBlock = ^ id (NSSet *models, BOOL *success, NSError **error) {
            NSError *underlyingError = nil;
            NSArray *jsonArray = [arrayMappingTransformer reverseTransformedValue:models.allObjects success:success error:&underlyingError];

            if (*success == NO) {
                if (error != NULL) {
                    NSDictionary *userInfo = @{
                            NSLocalizedDescriptionKey: NSLocalizedString(@"Could not transform set", @""),
                            NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"Could not transform value", @"")],
                            NSUnderlyingErrorKey: underlyingError,
                            MTLTransformerErrorHandlingInputValueErrorKey: models
                    };
                    *error = [NSError errorWithDomain:MTLTransformerErrorHandlingErrorDomain code:MTLTransformerErrorHandlingErrorInvalidInput userInfo:userInfo];
                }
                return nil;
            }

            if (jsonArray == nil) { return nil; }

            return jsonArray;
        };
    }

    if (reverseBlock != nil) {
        return [MTLValueTransformer transformerUsingForwardBlock:forwardBlock reverseBlock:reverseBlock];
    } else {
        return [MTLValueTransformer transformerUsingForwardBlock:forwardBlock];
    }
}

@github-actions
Copy link

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants