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

Allow specification of properties for serialization to JSON #185

Closed
dcaunt opened this issue Nov 15, 2013 · 20 comments · Fixed by #199
Closed

Allow specification of properties for serialization to JSON #185

dcaunt opened this issue Nov 15, 2013 · 20 comments · Fixed by #199

Comments

@dcaunt
Copy link
Member

dcaunt commented Nov 15, 2013

In some cases it is useful to serialize only certain model properties to JSON. For example, you might want a JSON dictionary which only contains values known to have been modified in the model.

At present, serializeToJSONDictionary uses self.model.dictionaryValue which uses all property keys to generate the JSON dictionary. An additional argument would have to be added this method to take an array of property names. The existing method could wrap this and provide all keys, as this behaviour is what is required most of the time.

@jspahrsummers
Copy link
Member

Are you saying that you'd like to conditionalize the properties per instance? You can already omit certain keys with the implementation of +JSONKeyPathsByPropertyKey.

@dcaunt
Copy link
Member Author

dcaunt commented Nov 15, 2013

I'd like to conditionalize the building of the JSON dictionary per serialization rather than per instance, so the same model could be used in multiple situations. I might create a model object with JSON with all properties present, but in building JSON to send to some remote server, only specific properties are serialized for sending the other way.

Sorry if I'm not being clear. My use case is that I have a user model with various attributes that come from a remote server. My app allows editing of some of these values and I'd like to be able to build a JSON dictionary where the non-editable keys & values are omitted.

I can see that this could be achieved by using a subclass of the fully-fledged model but this feels messy and only works for the specific set of properties defined in +JSONKeyPathsByPropertyKey. In addition, those omitted properties wouldn't be present for reading.

@jspahrsummers
Copy link
Member

TBH, this sounds like something that'd be easier to implement manually. Mantle isn't meant to handle every possible case, just the simplest (very boilerplate-heavy) ones.

We're always happy to extend it, but the additional utility has to overwhelmingly justify the increased complexity, and I'm not convinced that this use case is super common. :\

@joshaber
Copy link
Contributor

Could you accomplish what you want by subclassing MTLJSONAdapter?

@robb
Copy link
Member

robb commented Nov 16, 2013

Could you accomplish what you want by subclassing MTLJSONAdapter?

This seems like a sensible approach to me, let us know if there is a way to facilitate a subclass' access to what gets serialized

@dcaunt
Copy link
Member Author

dcaunt commented Nov 18, 2013

Thanks for the suggestions. I looked at this in more detail and it's not easily achievable with a subclass.

MTLJSONAdapter currently uses the MTLModel's dictionaryValue to fetch properties for serialization. Various non-public utility methods in MTLJSONAdapter are used to serialize each property.

As far as I can see, MTLJSONAdapter would best support this feature by allowing developers to specify which properties are serialized in serializeToJSONDictionary. This could be an argument to this method, or more neatly imho, an NSSet of propertiesForSerialization on the adapter itself (defaulting to everything/ignored if nil).

Outside of MTLJSONAdapter it's possible to filter the dictionary produced by serializeToJSONDictionary: though this is fairly involved when matching a property to a nested JSON keypath.

@robb
Copy link
Member

robb commented Nov 18, 2013

MTLJSONAdapter maintains a cached copy of +JSONKeyPathsByPropertyKey, what if we refactored the access to those properties through a public method that you could leverage for your filtering?

@dcaunt
Copy link
Member Author

dcaunt commented Nov 18, 2013

What exactly might that look like? I'm struggling to see how it would help as we can already access + JSONKeyPathsByPropertyKey from the model class.

@robb
Copy link
Member

robb commented Nov 18, 2013

I was thinking you'd subclass MTLJSONAdapter and override e.g. - JSONKeyPathForPropertyKey: that Mantle would provide

// XYJSONAdapter.m
- (NSString *)JSONKeyPathForPropertyKey:(NSString *)propertyKey {
    if ([self.keySubset containsObject:propertyKey]) {
        return [super JSONKeyPathForPropertyKey:propertyKey]; // This checks the cached copy of the model's +JSONKeyPathsByPropertyKey
    }

    return nil; // i.e. don't serialize `propertyKey`
}

Then you could set self.keySubset to whatever you need when serializing your models

// XYAPIClient.m
adapter.keySubset = [NSSet setWithArray:@[ @"id", @"username" ]];

NSDictionary *idAndName = [adapter JSONDictionaryFromModel:user];

Would that work for your use case?

(I assumed that we'd end up with longer lived adapters as discuessed in #151 but there'd probably be a way if we decided against that, too)

@dcaunt
Copy link
Member Author

dcaunt commented Nov 18, 2013

@robb I couldn't see the wood for the trees. That would be perfect! 💗

@milancermak
Copy link

FWIW I have the same use case as @dcaunt - I need to send a partial representation of an object to an API, basically a PATCH request.

For now I've implemented a custom method - JSONDictionaryForKeys: directly on MTLJSONAdapter (since subclassing wouldn't help) to get this functionality. However I would appreciate proper support for this in Mantle.

@paulyoung
Copy link
Contributor

@robb said:

Then you could set self.keySubset to whatever you need when serializing your models

// XYAPIClient.m
adapter.keySubset = [NSSet setWithArray:@[ @"id", @"username" ]];

NSDictionary *idAndName = [adapter JSONDictionaryFromModel:user];

However it demonstrates calling +JSONDictionaryFromModel: on an instance.

It also doesn't seem possible to create an adapter instance without conversion, at which point it's too late to set the property.

@robb
Copy link
Member

robb commented Mar 1, 2014

Yeah, I was thinking along the lines of #151

Would it be possible to do something like this:

// XYAPIClient.m
+ (NSDictionary *)JSONDictionaryFromModel:(MTLModel<MTLJSONSerializing> *)model keySubset:(NSSet *)keySubset {
    MTLJSONAdapter *adapter = [[self alloc] initWithModel:model];
    adapter.keySubset = keySubset;

    return adapter.JSONDictionary;
}

?

@paulyoung
Copy link
Contributor

I'll probably also need to support PATCH in the near future so will also need a per-instance serialization option.

However, my current use case only requires me to specify which subset of properties should be serialized per class.

I created a protocol which extended MTLJSONSerialiazing, and also subclassed MTLJSONAdapter.

The protocol has the optional method +propertyKeysForJSONRepresentation which the JSON adapter subclass calls in -JSONKeyPathForPropertyKey: if its model has implemented the method.

The rest of the implementation for -JSONKeyPathForPropertyKey: is quite similar to what @robb described above except self.keySubset is replaced by the array of keys returned by +propertyKeysForJSONRepresentation.

@paulyoung
Copy link
Contributor

If @robb and @jspahrsummers think this is useful I'll happily create a pull request.

@robb
Copy link
Member

robb commented Mar 5, 2014

It seems like you're building this more-or-less anyway and I'd 💙 to discuss this further in a separete PR.

@Pitometsu
Copy link

@paulyoung did you implement it yet?

@Pitometsu
Copy link

Didn't this feature implemented already?

@Pitometsu
Copy link

Pitometsu commented Sep 15, 2016

I see there was PR

But in current version I do not able to use + (NSArray *)propertyKeysForJSONRepresentation in model object. Any alternatives?

@Pitometsu
Copy link

I implemented it in subclass like this:

#import <Mantle/Mantle.h>


@protocol SmartJSONAdapting <MTLJSONSerializing>

@optional
/// whitelist only if present
+ (NSSet<NSString *> *)propertyKeysForJSONRepresentation;
/// remove properties with `nil` value from set
+ (BOOL)withoutNil;

@end


@interface SmartJSONAdapter : MTLJSONAdapter

- (NSSet *)serializablePropertyKeys:(NSSet *)propertyKeys
                           forModel:(id<SmartJSONAdapting>)model;

@end


@implementation SmartJSONAdapter

- (NSSet *)serializablePropertyKeys:(NSSet *)propertyKeys
                           forModel:(id<SmartJSONAdapting>)model
{
    if ([[model class] respondsToSelector:@selector(withoutNil)]
     && [[model class] withoutNil]) {
        NSMutableSet *propertyKeys_ = [propertyKeys mutableCopy];

        [propertyKeys enumerateObjectsUsingBlock:^(NSString *_Nonnull propertyKey,
                                                   BOOL * _Nonnull stop) {
            if ([[NSNull null] isEqual:model.dictionaryValue[propertyKey]]) {
                [propertyKeys_ removeObject:propertyKey];
            }
        }];
        propertyKeys = [propertyKeys_ copy];
    }
    if ([[model class] respondsToSelector:@selector(propertyKeysForJSONRepresentation)]) {
        NSMutableSet *propertyKeys_ = [propertyKeys mutableCopy];
        [propertyKeys_ minusSet:[[model class] propertyKeysForJSONRepresentation]];

        propertyKeys = [propertyKeys_ copy];
    }
    return propertyKeys;
}

@end

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

Successfully merging a pull request may close this issue.

7 participants