extracting properties from NSArray of NSArray of NSDictionary

| | August 6, 2015

This question is similar to extracting properties from NSArray of objects, but for deeper extraction.

For the sake of simplicity, the objects I am referring to in the below examples are NSStrings.

I have two cases I want to solve.

NSArray of NSArray of NSDictionary with objects

Preferably with Key-Value-coding, from the following structure, I’d like to extract all the “title” into a single enumerable list:

NSArray *list1 = @[
    @[
        @{@"title":@"A", @"description":...},
        @{@"title":@"B", @"description":...},
    ],
    @[
        @{@"title":@"C", @"description":...},
        @{@"title":@"D", @"description":...},
    ],
];

Desired result would be for example:

@[@"A", @"B", @"C", @"D"]

NSArray of NSDictionary with NSArray of objects

Preferably with Key-Value-coding, from the following structure, I’d like to extract the lists of “titles” into a single enumerable list:

NSArray *list2 = @[
    @{
        @"titles":@[
            @"A",
            @"B",
        ],
        @"descriptions":...,
    },
    @{
        @"titles":@[
            @"C",
            @"D",
        ],
        @"descriptions":...,
    },
];

Desired result would be for example:

@[@"A", @"B", @"C", @"D"]

Notes

  • My real cases involve NSOrderedSet of NSOrderedSet of objects for first list and NSOrderedSet of NSDictionary with NSOrderedSet of objects for second list, but that shouldn’t affect the answers I think.

  • I wrote I would prefer to use key-value-coding, but that is not a must. I just want to avoid, if possible, writing for (... in ...) or enumateObjectWithBlock:.

  • Again that shouldn’t matter, but objects are NSManagedObject from fetch requests. So I know I could just optimise the fetch requests directly, but I still want to know if there are nice alternatives.

2 Responses to “extracting properties from NSArray of NSArray of NSDictionary”

  1. Thank you Josh for the answer regarding NSArray. When I asked my question, I described it using NSArray because it was easier to write with the modern @[] syntax. I couldn’t imagine that the most appropriate answer for NSArray would be incompatible with my real case of NSOrderedSet.

    So, my real case is closer to that:

    NSOrderedSet *list1 = [NSOrderedSet orderedSetWithObjects:[NSOrderedSet orderedSetWithArray:@[ @{@"title":@"A"}, @{@"title":@"B"} ]], [NSOrderedSet orderedSetWithArray:@[ @{@"title":@"C"}, @{@"title":@"D"} ]], nil];
    NSOrderedSet *list2 = [NSOrderedSet orderedSetWithArray:@[ @{ @"titles":[NSOrderedSet orderedSetWithObjects: @"A", @"B", nil] }, @{ @"titles":[NSOrderedSet orderedSetWithObjects: @"C", @"D", nil] } ]];
    

    And the only solutions for NSOrderedSet I could find are sadly using a loop:

    NSMutableOrderedSet *listA = [NSMutableOrderedSet orderedSet];
    for (NSOrderedSet *set in [list1 valueForKey:@"title"]) {
        [listA unionOrderedSet:set];
    }
    
    NSMutableOrderedSet *listB = [NSMutableOrderedSet orderedSet];
    for (NSDictionary *object in list2) {
        [listB unionOrderedSet:object[@"titles"]];
    }
    

    [edit: apparently Josh found an undocumented @array operator to solve the issue. Contratz!]

  2. KVC can indeed do your bidding in this case, via a Collection Operator called @unionOfArrays. One effect of this operator is to flatten arrays, so your first example is very simple.

    [list1 valueForKeyPath:@"@unionOfArrays.title"]
    

    The second is quite similar, but you have to do it in the reverse order. First extract all the titles arrays, then flatten them.

    [list2 valueForKeyPath:@"titles.@unionOfArrays.self"]
    

    The self is necessary — although it seems redundant — because, per the doc linked above

    All the collection operators, with the exception of @count, require a key path to the right of the collection operator.

    For NSOrderedSet, it would seem that you could use its array property in the key path to convert the inner collections before operating on them, but for some reason this produces errors. I found this interesting tidbit, however, posted on GitHub by Nicolas Bouilleaud:

    // Convert each OrderedSet to an Array to mute the error.
    NSLog(@"union : %@",[data valueForKeyPath:@"@distinctUnionOfArrays.values.@array"]);
    

    and this weird @array operator works on your NSOrderedSet sample input:

    NSOrderedSet *list1 = [NSOrderedSet orderedSetWithObjects:[NSOrderedSet orderedSetWithArray:@[ @{@"title":@"A"}, @{@"title":@"B"} ]], [NSOrderedSet orderedSetWithArray:@[ @{@"title":@"C"}, @{@"title":@"D"} ]], nil];
    
    // Note also converting outer set to array first    
    NSLog(@"%@", [list1.array valueForKeyPath:@"@unionOfArrays.title.@array"]);
    
    NSOrderedSet *list2 = [NSOrderedSet orderedSetWithArray:@[ @{ @"titles":[NSOrderedSet orderedSetWithObjects: @"A", @"B", nil] }, @{ @"titles":[NSOrderedSet orderedSetWithObjects: @"C", @"D", nil] } ]];
    
    // Note also converting outer set to array first
    NSLog(@"%@", [list2.array valueForKeyPath:@"titles.@unionOfArrays.self.@array"]);
    

    but I have no idea why. I can’t figure out where this comes from or what it’s doing (why it’s at the end, in particular).

Leave a Reply