Search code examples
iosnsarraynsdictionarynspredicate

Filter using NSPredicate of Array of Dictionary within dictionary in iOS


Rewards -- Array
   Normal Reward -- Object
     Offer -- Dictionary
       Meta -- Dictionary
         rewardId -- String
   Normal Reward -- Object
     Offer -- Dictionary
       Meta -- Dictionary
         rewardId -- String

I have the above hierarchy out of which I have to match a particular rewardId for a value. How can I achieve that using NSPredicate.


Solution

  • This predicate should do the trick:

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.nameOfOfferProperty.nameOfMetaKey.nameOfRewardIdKey == %@", myRewardIdToMatch]
    

    I explicitly named the path to go according to your needs.

    With Sample code (this way you can see what name to use if needed):

    NSMutableArray *rewards = [[NSMutableArray alloc] init];
    for (NSUInteger i = 0; i < 10; i ++)
    {
        NSString *rewardId = [NSString stringWithFormat:@"rewardID %@", (i%2 == 0)?@"Target":@"NonWanted"];
        NSString *otherMetaValue = [NSString stringWithFormat:@"otherMetaValue-%ld", i];
        NSString *otherOfferValue = [NSString stringWithFormat:@"otherOfferValue-%ld", i];
        NSDictionary *anOfferDict = @{@"meta": @{@"rewardId": rewardId,
                                                 @"otherMetaKey": otherMetaValue},
                                      @"otherOfferKey": otherOfferValue};
        Reward *aReward = [[Reward alloc] initWithOfferDict:anOfferDict andIntValue:i];
        [rewards addObject:aReward];
    }
    NSLog(@"Rewards: %@", rewards);
    NSString * myRewardIdToMatch = @"rewardID Target";
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.offer.meta.rewardId == %@", myRewardIdToMatch];
    
    
    NSArray *filteredRewards = [rewards filteredArrayUsingPredicate:predicate];
    NSLog(@"FilteredRewards: %@", filteredRewards);
    

    And

    @implementation Reward
    -(id)initWithOfferDict:(NSDictionary *)dict andIntValue:(NSUInteger)intV
    {
        self = [super init];
        if (self)
        {
            _offer = dict;
            _intV = intV;
        }
        return self;
    }
    

    Overriding description can be helpful and make it clear if it worked:

    -(NSString *)description
    {
        return [NSString stringWithFormat:@"<%@ %p> with IntValue: %ld and rewardID:\n%@", [self class], self, _intV,  _offer[@"meta"][@"rewardId"]];
    
    }
    @end
    

    Output:

    $> Rewards: (
        "<Reward 0x60000022ce80> with IntValue: 0 and rewardID: rewardID Target",
        "<Reward 0x60000022ce00> with IntValue: 1 and rewardID: rewardID NonWanted",
        "<Reward 0x60000022cf80> with IntValue: 2 and rewardID: rewardID Target",
        "<Reward 0x60000022cea0> with IntValue: 3 and rewardID: rewardID NonWanted",
        "<Reward 0x60000022cf20> with IntValue: 4 and rewardID: rewardID Target",
        "<Reward 0x60000022ce40> with IntValue: 5 and rewardID: rewardID NonWanted",
        "<Reward 0x60000022cfa0> with IntValue: 6 and rewardID: rewardID Target",
        "<Reward 0x60000022cfc0> with IntValue: 7 and rewardID: rewardID NonWanted",
        "<Reward 0x60000022ce60> with IntValue: 8 and rewardID: rewardID Target",
        "<Reward 0x60000022cec0> with IntValue: 9 and rewardID: rewardID NonWanted"
    )
    $> FilteredRewards: (
        "<Reward 0x60000022ce80> with IntValue: 0 and rewardID: rewardID Target",
        "<Reward 0x60000022cf80> with IntValue: 2 and rewardID: rewardID Target",
        "<Reward 0x60000022cf20> with IntValue: 4 and rewardID: rewardID Target",
        "<Reward 0x60000022cfa0> with IntValue: 6 and rewardID: rewardID Target",
        "<Reward 0x60000022ce60> with IntValue: 8 and rewardID: rewardID Target"
    )