3

I am trying to find a way to access named nested list elements by passing their names as a string (or list of strings). Something like you can do with attr(the_thing, "the_attr") but I want to do attr(the_thing, "$attr1$attr2$attr3"). Seems like this should be possible but I'm at a loss.

For example, I'm writing some code to consume responses from the Github API. There are a bunch of functions like (these are very simplified just for example's sake):

get_milestone <- function(org, repo) {
        response <- graphql_query(org = org, repo = repo)
        return(response$repository$milestone$issues)
}
get_pull_request <- function(org, repo) {
        response <- graphql_query(org = org, repo = repo)
        return(response$repository$pull_request$issues)
}
get_issues <- function(org, repo) {
        response <- graphql_query(org = org, repo = repo)
        return(response$repository$issues)
}

and you'll notice the only difference is what attributes you pull out of the response.

I'd like to make simple helper function like

get_something <- function(org, repo, attr_to_get) {
        response <- graphql_query(org = org, repo = repo)
        return(attr(response, attr_to_get))
}

and then call it each time like

get_something(org, repo, attr_to_get="$repository$milestone$issues")
get_something(org, repo, attr_to_get="$repository$pull_request$issues")
get_something(org, repo, attr_to_get="$repository$issues")

but that syntax doesn't work.

Is there any way to pass a string to specify nested attributes to extract from an object? I feel like rlang or something like that may be relevant, but I can't seem to figure it out.

My initial idea was just to do something like this

param_list <- unlist(strsplit(attr_to_get, "\\$"))
for (p in param_list) {
    if (p != "") {
       response <- response[[p]]
    }
}

But that feels very ugly and silly to me. Like, there must be a one-liner way of doing this without manually iterating. But maybe I should just do it that way? Any help is much appreciated.

For reproducibility you can replace the response <- graphql_query(org = org, repo = repo) line in my function with something like response <- list(repository = list(milestone = list(issues=c("issue1", "issue2", "issue3")))) and then attempt to extract "$repository$milestone$issues" from the response. Thanks!

5
  • 1
    first $repository$milestone$issues is not an attribute. it is just a nested list. Are you dealing with nested lists or with attributes?? Commented Jan 13, 2020 at 20:41
  • I'm dealing with nested lists. Commented Jan 13, 2020 at 20:42
  • 2
    well just use a vector instead of a string eg response[[c("repository","milestone","issues")]] Commented Jan 13, 2020 at 20:44
  • perfect. That works. Feel free to make an answer and I'll accept. Although I guess I should modify the question to be explicitly about nested lists. Commented Jan 13, 2020 at 20:48
  • 2
    The purrr:pluck function is also very helpful for extracting elements from nested lists. Commented Jan 13, 2020 at 20:52

3 Answers 3

4

you can do

 nm <- c("repository","milestone","issues")
 response[[nm]]

also we can use the purrr::pluck function

  purrr::pluck(response, !!!nm)
Sign up to request clarification or add additional context in comments.

1 Comment

Similarly, purrr::pluck(response, "repository", "milestone", "issues")
2

If you feel like using rlang here's another possible answer:

# Something generic that produces a nested list with the names you specified
graphql_query <- function(org, repo){
list(repository = list(milestone = list(issues = 'hi')))
}

get_something <- function(org, repo, ...) {
response <- graphql_query(org, repo)
attr_to_get <- paste0('response$', paste(rlang::list2(...), collapse = '$'))
rlang::eval_tidy(rlang::parse_expr(attr_to_get))
}

get_something('org', 'repo', 'repository', 'milestone', 'issues')
>>> [1] "hi"

or you can do:

att_lst <- c('repository', 'milestone', 'issues')
get_something('org', 'repo', !!!att_lst)
>>> [1] "hi"

1 Comment

I would strongly recommend against using parse_expr. Building strings of code and executing them can be messy and dangerous. Especially given that the other answers already show safe, built-in ways to accomplish this.
0

Another base R solution is to use Reduce() + [[

Reduce(`[[`,list(response,"repository","milestone","issues"))

or

Reduce(`[[`,list(response,c("repository","milestone","issues")))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.