NSPredicate Nested Relationships

I am trying to filter a mutable array of objects using NSPredicate, and I'm having problems accessing a level that contains a property that I would like to filter.

Make a simplified example consisting of similar user objects.

  • Grandparent
  • Parent
  • Child

I have an NSMutableArray of grandparents and I would like to find all the items of grandparents that have GrandChildren at the age of 10. Therefore, grandchildren are at two levels from the root. Among other things, the child has an age-related property.

t. Grandparents have an array property. Parents and parents have an array property. Children and children have integer age properties.

The following NSPredicate yielded no results. "SELF.parents.children.age == 10" .

I understand that since these are nested collections, this predicate is probably the wrong way, but I am fixated on how to access this level. Perhaps using a subquery or a collection statement, but I can't solve it.

One thing to keep in mind is that I obviously still want GrandParents to have several grandchildren of different ages, one of whom is 10 years old.

+6
source share
2 answers

As an alternative to Martin R's answer, you can use a block predicate instead. Something like that:

 NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary * bindings) { GrandParent *grandparent = evaluatedObject; for (Parent *parent in grandparent.parents) for (Child *child in parent.children) if (child.age == 10) return YES return NO; }]; 

Assuming GrandParent , Parent and Child are the corresponding class names for different objects.

Personally, I prefer this form because I always feel with a string predicate that I mix languages ​​in code, which, in my opinion, makes it less readable. The choice is obviously up to you, though.

Update: After re-reading the question, I now understand that the condition was more complex than I originally thought. I updated my answer to the loop over parents and children, but Martin R's answer is now much simpler. However, this is a possible solution to consider.

+12
source

The β€œobvious” solution is the predicate:

 "ANY parents.children.age == 10" 

However, the ANY operator does not work with nested to many relationships. Therefore you need SUBQUERY:

 NSArray *grandParents = your array of GrandParent objects; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SUBQUERY(parents, $p, ANY $p.children.age == 10) .@count > 0"]; NSArray *filtered = [grandParents filteredArrayUsingPredicate:predicate]; 

Notes:

  • Using SELF in a predicate is not required. filteredArrayUsingPredicate applies a predicate to each GrandParent object in the array.
  • The use of SUBQUERY in predicates seems to be poorly documented. There is one example in the link of the NSExpression class. See Also SUBQUERY Quick Explanation in an NSPredicate Expression .
  • In this case, the predicate inside SUBQUERY is applied to each Parent one GrandParent . SUBQUERY returns parents who have a child aged 10 years. So, "SUBQUERY(...) .@count > 0" evaluates to TRUE if grandparents have at least one parent who has a child aged 10 years.

ADDED: I just found out that this can really be done without SUBQUERY:

 NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ANY parents.@unionOfArrays.children.age == 10"]; 

It works and gives the desired result. (This may be less effective than SUBQUERY, but I have not tested this.)

+15
source

Source: https://habr.com/ru/post/946258/


All Articles