iosobjective-cnsarraykey-value-codingnsorderedset

extracting properties from NSArray of NSArray of NSDictionary


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


Solution

  • 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).