Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Own adapters #203

Closed
dcordero opened this Issue · 14 comments

4 participants

@dcordero

I created my own adapter using the new "JSONKeyPathForPropertyKey" way. But I am having some problems since from the reversed transformer I can not know what Adapter was used for the main model. And I am forced to use the MTLJSONAdapter instead of mine, getting a different behaviour for the nested models.

So my proposal is sending the Adapter to the transformers, allowing get the wanted behaviour on demand

// MTLJSONAdapter.m
-value = [transformer reverseTransformedValue:value withAdapter:self] ?: NSNull.null;
+value = [transformer reverseTransformedValue:value] ?: NSNull.null;
// MTLValueTransformer
- - (id)reverseTransformedValue:(id)value {
-   return self.reverseBlock(value);
+ - (id)reverseTransformedValue:(id)value withAdapter:(MTLJSONAdapter *)adapter {
+   return self.reverseBlock(value, adapter);
// MTLValueTransformer.h
-typedef id (^MTLValueTransformerBlock)(id);
+typedef id (^MTLValueTransformerBlock)(id,MTLJSONAdapter *);
@robb
Owner

I am not sure that coupling MTLValueTransformer to MTLJSONAdapter is the way to go. What kind of logic would you have that changes based on the adapter? Have you considered using a different method for declaring the transformers, like so:

// XYModel.h

+ (NSValueTransformer *)propertyJSONTransformer {
    return …
}

+ (NSValueTransformer *)propertyCustomTransformer {
    return …
}

What is your underlying motive for the custom transformer, are you working with another API that has different constraints on the values?

@dcordero

More than a custom transformer what I would like is use the same one but from different Adapters. Let me explain myself with an example:

Imagine I have this json:

{
   "key1" : "Hello World",
   "key2" : nil,
   "key3" : "Hola Mundo",
   "nestedModel" : [{
       "subkey1" : 12345,
       "subkey2" : nil,
       "subkey3" : "Hola Mundo"
   }]
}

And I model it with a couple of models. The first one with 4 properties for: key1, key2, key3, and nestedmodel. And this last one is an array of objects of another model with 3 keys: subkey1, subkey2 and subkey3

So, apart from the behaviour I get using MTLJSONAdapter that let me generate this json as it is using, "in addition" I would like to have another behaviour as well

The second behaviour I would like to have is if for example, create an Adapter that let me reverse-transform this model but I want to avoid having keys equals to "Hola Mundo" in the generated JSONDictionary. So, I can create my own Adapter using JSONKeyPathForPropertyKey as follow....

- (NSString *)JSONKeyPathForPropertyKey:(NSString *)propertyKey {

    id value = [self.model valueForKey:propertyKey];

    if ([value isKindOfClass:NSString.class] && [value isEqualToString:@"Hola Mundo"]){
        return [super JSONKeyPathForPropertyKey:propertyKey];

    }

    return nil;

}

And I get my JSONDictionary using:

NSDictionary *myReversedModel = [MyCustomJSONAdaper JSONDictionaryFromModel:myModel];

or

NSDictionary *myReversedModel = [MTLJSONAdapter JSONDictionaryFromModel:myModel];

depending on the behaviour I want in any moment.

With this situation... As the transformer of my nested my model is something like that in the main model:

+ (NSValueTransformer *)nestedModelJSONTransformer
{
    return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(id whatever) {
            [..... WHAT EVER ....]
    } reverseBlock:^(NSArray *nestedModels) {
        NSArray *result;
        if (nestedModels) {
            result = [[NSArray alloc] init];
            for (NestedModel *submodel in nestedModels) {
                NSDictionary *nestedModelReversed = [MTLJSONAdapter JSONDictionaryFromModel:submodel];
                if (nestedModelReversed) {
                    [result addObject:nestedModelReversed];
                }
            }
        }
        return result;
    }];
}

And in this reverse transformer I am forced to always use MTLJSONAdapter instead of my own adapter when the model is beeing transformer from a different Adapter. That is why I think it could be a good idea be able to know the adapter is currently doing the transformation sending it to the transformer.

@robb
Owner
@robb
Owner

Did you figure this out? :-)

@dcaunt

@robb I guess it would be useful to be able to specify the MTLJSONAdapter subclass to use in mtl_JSONDictionaryTransformerWithModelClass: (and mtl_JSONArrayTransformerWithModelClass:)

@robb
Owner

@dcaunt Yeah, I guess we should expect MTLJSONAdapters to be subclassed post 2.0.

Alternatively, I was thinking we could also make it a class method on the adapter

@interface MTLJSONAdapter (NSValueTransformer)

+ (NSValueTransformer *)arrayTransformerWithModelClass:(Class)class;
+ (NSValueTransformer *)dictionaryTransformerWithModelClass:(Class)class;

@end

that way the class of the adapter would be implied and subclasses wouldn't even have to care. On the other hand, there's something to be said for keeping everything in the NSValueTransformer category.

@dcaunt

The predefined transformers could call the class methods on MTLJSONAdapter giving you Mantle's default behaviour. Calling either of the class methods on an adapter subclass yields an instance of the subclass.

@robb
Owner

The predefined transformers could call the class methods on MTLJSONAdapter giving you Mantle's default behaviour.

This would be needed for backwards compatibiliy, but I'd rather only have

@interface NSValueTransformer (PredefinedTransformers)

+ (NSValueTransformer *)mtl_JSONArrayTransformerWithModelClass:(Class)class __attribute((deprecated));
+ (NSValueTransformer *)mtl_JSONDictionaryTransformerWithModelClass:(Class)class __attribute((deprecated));

@end

@interface MTLJSONAdapter (NSValueTransformer)

+ (NSValueTransformer *)arrayTransformerWithModelClass:(Class)class;
+ (NSValueTransformer *)dictionaryTransformerWithModelClass:(Class)class;

@end

OR

@interface NSValueTransformer (PredefinedTransformers)

+ (NSValueTransformer *)mtl_JSONArrayTransformerWithModelClass:(Class)class;
+ (NSValueTransformer *)mtl_JSONArrayTransformerWithModelClass:(Class)class JSONAdapterClass:(Class)adapterClass;

+ (NSValueTransformer *)mtl_JSONDictionaryTransformerWithModelClass:(Class)class;
+ (NSValueTransformer *)mtl_JSONDictionaryTransformerWithModelClass:(Class)class JSONAdapterClass:(Class)adapterClass;

@end

That being said, I don't think we'll have a common superclass or protocol for the adapters any time soon, so +arrayTransformerWithModelClass:JSONAdapterClass: could only take MTLJSONAdapters subclasses and it seems pointless to have that method if there is no other viable class shipping with Mantle…

@robb
Owner

FWIW, I think the MTLJSONAdapter (NSValueTransformer) pattern would also scale better for future and external adapters.

@dcaunt

Agreed, the first option looks much better to me. The transformers are integral to JSON adaptation and I think it's clearer to group them in this way. Otherwise, you're right about the issue of scale - there would be lots of similarly named mtl_ category methods.

@robb robb added the enhancement label
@dcaunt

@robb Do you want to implement this against 2.0-development or master?

@jspahrsummers

:+1: on moving these methods to MTLJSONAdapter (against 2.0-development).

@dcaunt
@robb robb added the JSON label
@jspahrsummers jspahrsummers added this to the 2.0 milestone
@jspahrsummers jspahrsummers self-assigned this
@robb
Owner

Closed by #474

@robb robb closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.