简体   繁体   中英

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

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

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

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!]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM