6 Non-rectangular data

working with raw data from online services (JSON)

This chapter gives an example for processing deeply nested lists and converting them to data frames.

6.1 Traversing

Click here to show setup code.

library(tidyverse)
library(here)

We are now working with the results from downloading geolocation data from photon.komoot.de. This is stored in the file here("data/komoot-berlin.rds") and we can read it with readRDS():

berlin <- readRDS(here("data/komoot-berlin.rds"))

berlin
## $features
## $features[[1]]
## $features[[1]]$geometry
## $features[[1]]$geometry$coordinates
## $features[[1]]$geometry$coordinates[[1]]
## [1] 13.38886
## 
## $features[[1]]$geometry$coordinates[[2]]
## [1] 52.51704
## 
## 
## $features[[1]]$geometry$type
## [1] "Point"
## 
## 
## $features[[1]]$type
## [1] "Feature"
## 
## $features[[1]]$properties
## $features[[1]]$properties$osm_id
## [1] 240109189
## 
## $features[[1]]$properties$osm_type
## [1] "N"
## 
## $features[[1]]$properties$country
## [1] "Germany"
## 
## $features[[1]]$properties$osm_key
## [1] "place"
## 
## $features[[1]]$properties$city
## [1] "Berlin"
## 
## $features[[1]]$properties$osm_value
## [1] "city"
## 
## $features[[1]]$properties$postcode
## [1] "10117"
## 
## $features[[1]]$properties$name
## [1] "Berlin"
## 
## 
## 
## 
## $type
## [1] "FeatureCollection"
str(berlin)
## List of 2
##  $ features:List of 1
##   ..$ :List of 3
##   .. ..$ geometry  :List of 2
##   .. .. ..$ coordinates:List of 2
##   .. .. .. ..$ : num 13.4
##   .. .. .. ..$ : num 52.5
##   .. .. ..$ type       : chr "Point"
##   .. ..$ type      : chr "Feature"
##   .. ..$ properties:List of 8
##   .. .. ..$ osm_id   : int 240109189
##   .. .. ..$ osm_type : chr "N"
##   .. .. ..$ country  : chr "Germany"
##   .. .. ..$ osm_key  : chr "place"
##   .. .. ..$ city     : chr "Berlin"
##   .. .. ..$ osm_value: chr "city"
##   .. .. ..$ postcode : chr "10117"
##   .. .. ..$ name     : chr "Berlin"
##  $ type    : chr "FeatureCollection"

As you can see it is a somewhat complex list structure. We know from “Indexing” that we can access it’s components in the following way:

berlin$type
## [1] "FeatureCollection"
berlin$features
## [[1]]
## [[1]]$geometry
## [[1]]$geometry$coordinates
## [[1]]$geometry$coordinates[[1]]
## [1] 13.38886
## 
## [[1]]$geometry$coordinates[[2]]
## [1] 52.51704
## 
## 
## [[1]]$geometry$type
## [1] "Point"
## 
## 
## [[1]]$type
## [1] "Feature"
## 
## [[1]]$properties
## [[1]]$properties$osm_id
## [1] 240109189
## 
## [[1]]$properties$osm_type
## [1] "N"
## 
## [[1]]$properties$country
## [1] "Germany"
## 
## [[1]]$properties$osm_key
## [1] "place"
## 
## [[1]]$properties$city
## [1] "Berlin"
## 
## [[1]]$properties$osm_value
## [1] "city"
## 
## [[1]]$properties$postcode
## [1] "10117"
## 
## [[1]]$properties$name
## [1] "Berlin"
berlin$features[[1]]
## $geometry
## $geometry$coordinates
## $geometry$coordinates[[1]]
## [1] 13.38886
## 
## $geometry$coordinates[[2]]
## [1] 52.51704
## 
## 
## $geometry$type
## [1] "Point"
## 
## 
## $type
## [1] "Feature"
## 
## $properties
## $properties$osm_id
## [1] 240109189
## 
## $properties$osm_type
## [1] "N"
## 
## $properties$country
## [1] "Germany"
## 
## $properties$osm_key
## [1] "place"
## 
## $properties$city
## [1] "Berlin"
## 
## $properties$osm_value
## [1] "city"
## 
## $properties$postcode
## [1] "10117"
## 
## $properties$name
## [1] "Berlin"

With the function purrr::pluck(), there is however a more universal tool available for accessing elements of more complex lists:

berlin %>%
  pluck("type")
## [1] "FeatureCollection"
berlin[["type"]]
## [1] "FeatureCollection"
berlin %>%
  pluck("features")
## [[1]]
## [[1]]$geometry
## [[1]]$geometry$coordinates
## [[1]]$geometry$coordinates[[1]]
## [1] 13.38886
## 
## [[1]]$geometry$coordinates[[2]]
## [1] 52.51704
## 
## 
## [[1]]$geometry$type
## [1] "Point"
## 
## 
## [[1]]$type
## [1] "Feature"
## 
## [[1]]$properties
## [[1]]$properties$osm_id
## [1] 240109189
## 
## [[1]]$properties$osm_type
## [1] "N"
## 
## [[1]]$properties$country
## [1] "Germany"
## 
## [[1]]$properties$osm_key
## [1] "place"
## 
## [[1]]$properties$city
## [1] "Berlin"
## 
## [[1]]$properties$osm_value
## [1] "city"
## 
## [[1]]$properties$postcode
## [1] "10117"
## 
## [[1]]$properties$name
## [1] "Berlin"
berlin %>%
  pluck("features", 1)
## $geometry
## $geometry$coordinates
## $geometry$coordinates[[1]]
## [1] 13.38886
## 
## $geometry$coordinates[[2]]
## [1] 52.51704
## 
## 
## $geometry$type
## [1] "Point"
## 
## 
## $type
## [1] "Feature"
## 
## $properties
## $properties$osm_id
## [1] 240109189
## 
## $properties$osm_type
## [1] "N"
## 
## $properties$country
## [1] "Germany"
## 
## $properties$osm_key
## [1] "place"
## 
## $properties$city
## [1] "Berlin"
## 
## $properties$osm_value
## [1] "city"
## 
## $properties$postcode
## [1] "10117"
## 
## $properties$name
## [1] "Berlin"
berlin[["features"]][[1]]
## $geometry
## $geometry$coordinates
## $geometry$coordinates[[1]]
## [1] 13.38886
## 
## $geometry$coordinates[[2]]
## [1] 52.51704
## 
## 
## $geometry$type
## [1] "Point"
## 
## 
## $type
## [1] "Feature"
## 
## $properties
## $properties$osm_id
## [1] 240109189
## 
## $properties$osm_type
## [1] "N"
## 
## $properties$country
## [1] "Germany"
## 
## $properties$osm_key
## [1] "place"
## 
## $properties$city
## [1] "Berlin"
## 
## $properties$osm_value
## [1] "city"
## 
## $properties$postcode
## [1] "10117"
## 
## $properties$name
## [1] "Berlin"
berlin %>%
  pluck("features", 1, "geometry")
## $coordinates
## $coordinates[[1]]
## [1] 13.38886
## 
## $coordinates[[2]]
## [1] 52.51704
## 
## 
## $type
## [1] "Point"
berlin[["features"]][[1]][["geometry"]]
## $coordinates
## $coordinates[[1]]
## [1] 13.38886
## 
## $coordinates[[2]]
## [1] 52.51704
## 
## 
## $type
## [1] "Point"
berlin %>%
  pluck("features", 1, "geometry", "coordinates")
## [[1]]
## [1] 13.38886
## 
## [[2]]
## [1] 52.51704

Similarly:

berlin %>%
  pluck("features", 1, "properties", "country")
## [1] "Germany"

And as one more important characteristic of a tidyverse-function, pluck() is pipe-able:

berlin %>%
  pluck("features", 1) %>%
  pluck("properties", "country")
## [1] "Germany"

6.1.1 Exercises

  1. Introduce a variable for the first feature. Collect the coordinates, the country and the postal code.

    first_feature <-
      berlin %>% 
      ___(_____)
    
    first_feature %>% 
      ___(_____)
    
    first_feature %>% 
      ___(_____)
    
    first_feature %>% 
      ___(_____)
    ## NULL
    ## [1] "Germany"
    ## [1] "10117"

6.2 Iterating and traversing

Click here to show setup code.

library(tidyverse)
library(here)

Now we are not only working with the geolocation data for Berlin, but we are adding data for our usual suspects:

komoot <- readRDS(here("data/komoot.rds"))

komoot
## # A tibble: 4 x 6
##   name    url_name   url                            res     status content 
##   <chr>   <chr>      <chr>                          <list>  <list> <list>  
## 1 Berlin  Berlin     https://photon.komoot.de/api/… <respo<NULL> <list […
## 2 Toronto Toronto    https://photon.komoot.de/api/… <respo<NULL> <list […
## 3 Tel Av… Tel%20Aviv https://photon.komoot.de/api/… <respo<NULL> <list […
## 4 Zürich  Z%C3%BCri… https://photon.komoot.de/api/… <respo<NULL> <list [

It looks slightly different from the list berlin from section “Traversing”. That is because we have the list-of-2 stored for each city in the column content. By using pull() on content, we can produce a list containing the information for all cities:

komoot_content <-
  komoot %>%
  pull(content)

berlin <-
  komoot_content %>%
  pluck(1)

berlin %>%
  pluck("features", 1, "geometry", "coordinates")
## [[1]]
## [1] 13.38886
## 
## [[2]]
## [1] 52.51704
toronto <-
  komoot_content %>%
  pluck(2)

toronto %>%
  pluck("features", 1, "geometry", "coordinates")
## [[1]]
## [1] -79.38721
## 
## [[2]]
## [1] 43.65396

With map() we can access the same element of the respective list for each city:

komoot_content %>%
  map(~ pluck(., "features", 1, "geometry", "coordinates"))
## [[1]]
## [[1]][[1]]
## [1] 13.38886
## 
## [[1]][[2]]
## [1] 52.51704
## 
## 
## [[2]]
## [[2]][[1]]
## [1] -79.38721
## 
## [[2]][[2]]
## [1] 43.65396
## 
## 
## [[3]]
## [[3]][[1]]
## [1] 34.78053
## 
## [[3]][[2]]
## [1] 32.08048
## 
## 
## [[4]]
## [[4]][[1]]
## [1] 8.542322
## 
## [[4]][[2]]
## [1] 47.3724

With map() we can also use a shorthand notation for this, without the need to use pluck(). We can just give it a list of the arguments which we would normally use as arguments for pluck():

komoot_content %>%
  map(list("features", 1, "geometry", "coordinates"))
## [[1]]
## [[1]][[1]]
## [1] 13.38886
## 
## [[1]][[2]]
## [1] 52.51704
## 
## 
## [[2]]
## [[2]][[1]]
## [1] -79.38721
## 
## [[2]][[2]]
## [1] 43.65396
## 
## 
## [[3]]
## [[3]][[1]]
## [1] 34.78053
## 
## [[3]][[2]]
## [1] 32.08048
## 
## 
## [[4]]
## [[4]][[1]]
## [1] 8.542322
## 
## [[4]][[2]]
## [1] 47.3724

The access path can also be stored in a variable:

accessor <- list("features", 1, "geometry", "coordinates")
coordinates <-
  komoot_content %>%
  map(accessor)
coordinates
## [[1]]
## [[1]][[1]]
## [1] 13.38886
## 
## [[1]][[2]]
## [1] 52.51704
## 
## 
## [[2]]
## [[2]][[1]]
## [1] -79.38721
## 
## [[2]][[2]]
## [1] 43.65396
## 
## 
## [[3]]
## [[3]][[1]]
## [1] 34.78053
## 
## [[3]][[2]]
## [1] 32.08048
## 
## 
## [[4]]
## [[4]][[1]]
## [1] 8.542322
## 
## [[4]][[2]]
## [1] 47.3724

6.2.1 Exercises

  1. Augment komoot with a columns containing information on the first feature only.

    ## # A tibble: 4 x 7
     ##   name   url_name  url                 res     status content first_feature
     ##   <chr>  <chr>     <chr>               <list>  <list> <list>  <list>       
     ## 1 Berlin Berlin    https://photon.kom… <respo<NULL> <list <list [3]>   
     ## 2 Toron… Toronto   https://photon.kom… <respo<NULL> <list <list [3]>   
     ## 3 Tel A… Tel%20Av… https://photon.kom… <respo<NULL> <list <list [3]>   
     ## 4 Zürich Z%C3%BCr… https://photon.kom… <respo<NULL> <list <list [3]>
     
  2. Augment komoot_first with columns containing information on coordinates, place and postal code. Use accessors and appropriate types for the columns.

    acc_coordinates <- _____
    acc_country <- _____
    komoot_first %>% 
      mutate(
        coordinates = ___(___, acc_coordinates),
        country = _____,
        postcode = map_chr(_____, ~ pluck(___, .default = NA))
      )
    ## # A tibble: 4 x 10
     ##   name  url_name url   res   status content first_feature coordinates
     ##   <chr> <chr>    <chr> <lis> <list> <list>  <list>        <list>     
     ## 1 Berl… Berlin   http… <res<NULL> <list <list [3]>    <NULL>     
     ## 2 Toro… Toronto  http… <res<NULL> <list <list [3]>    <NULL>     
     ## 3 Tel … Tel%20A… http… <res<NULL> <list <list [3]>    <NULL>     
     ## 4 Züri… Z%C3%BC… http… <res<NULL> <list <list [3]>    <NULL>     
     ## # … with 2 more variables: country <chr>, postcode <chr>
     

6.3 Plucking multiple locations

Click here to show setup code.

library(tidyverse)
library(here)

komoot <- readRDS(here("data/komoot.rds"))
komoot_content <-
  komoot %>%
  pull(content)

What if we want to access two different pieces of information of each main list point at once?

We are again starting in the setup with the list komoot_content from “Iterating and traversing”.

Let’s define the two locations of the city-lists we would like to access:

accessor_coords <- list("features", 1, "geometry", "coordinates")

komoot_content %>%
  map(accessor_coords)
## [[1]]
## [[1]][[1]]
## [1] 13.38886
## 
## [[1]][[2]]
## [1] 52.51704
## 
## 
## [[2]]
## [[2]][[1]]
## [1] -79.38721
## 
## [[2]][[2]]
## [1] 43.65396
## 
## 
## [[3]]
## [[3]][[1]]
## [1] 34.78053
## 
## [[3]][[2]]
## [1] 32.08048
## 
## 
## [[4]]
## [[4]][[1]]
## [1] 8.542322
## 
## [[4]][[2]]
## [1] 47.3724
accessor_country <- list("features", 1, "properties", "country")
komoot_content %>%
  map(accessor_country)
## [[1]]
## [1] "Germany"
## 
## [[2]]
## [1] "Canada"
## 
## [[3]]
## [1] "Israel"
## 
## [[4]]
## [1] "Switzerland"

Combine them as a list of lists and hand it over to a map() inside a map():

accessors <- list(coords = accessor_coords, country = accessor_country)

accessors %>%
  map(~ map(komoot_content, .))
## $coords
## $coords[[1]]
## $coords[[1]][[1]]
## [1] 13.38886
## 
## $coords[[1]][[2]]
## [1] 52.51704
## 
## 
## $coords[[2]]
## $coords[[2]][[1]]
## [1] -79.38721
## 
## $coords[[2]][[2]]
## [1] 43.65396
## 
## 
## $coords[[3]]
## $coords[[3]][[1]]
## [1] 34.78053
## 
## $coords[[3]][[2]]
## [1] 32.08048
## 
## 
## $coords[[4]]
## $coords[[4]][[1]]
## [1] 8.542322
## 
## $coords[[4]][[2]]
## [1] 47.3724
## 
## 
## 
## $country
## $country[[1]]
## [1] "Germany"
## 
## $country[[2]]
## [1] "Canada"
## 
## $country[[3]]
## [1] "Israel"
## 
## $country[[4]]
## [1] "Switzerland"

6.4 Flattening

Click here to show setup code.

library(tidyverse)
library(here)

komoot <- readRDS(here("data/komoot.rds"))

komoot_content <-
  komoot %>%
  pull(content)

coordinates <-
  komoot_content %>%
  map(list("features", 1, "geometry", "coordinates"))

It can occur that we end up with lists which are unnecessarily deep and we would like to make them flatter to make it easier to handle them.

We are starting in our setup with komoot_content and coordinates from section “Iterating and traversing”.

An example for an list that seems a bit too deep is given here:

coordinates %>%
  pluck(1)
## [[1]]
## [1] 13.38886
## 
## [[2]]
## [1] 52.51704

We can chop off a layer of a list and end up with a vector with one of the functions purrr::flatten_*(). In the * we need to specify what class the output will be:

coordinates %>%
  pluck(1) %>%
  flatten_dbl()
## [1] 13.38886 52.51704

Let’s use map() to apply this to the entire list of our cities’ coordinates:

coordinates %>%
  map(flatten_dbl)
## [[1]]
## [1] 13.38886 52.51704
## 
## [[2]]
## [1] -79.38721  43.65396
## 
## [[3]]
## [1] 34.78053 32.08048
## 
## [[4]]
## [1]  8.542322 47.372396

6.5 Transposing

Click here to show setup code.

library(tidyverse)
library(here)

komoot <- readRDS(here("data/komoot.rds"))

komoot_content <-
  komoot %>%
  pull(content)

coordinates <-
  komoot_content %>%
  map(list("features", 1, "geometry", "coordinates"))

You might know the mathematical concept of transposition from your linear algebra courses. A similar concept is available in R when we are dealing with lists.

We are starting in our setup with komoot_content and coordinates from section “Iterating and traversing”.

Let’s apply purrr::transpose() to our list coordinates:

coordinates %>%
  transpose()
## [[1]]
## [[1]][[1]]
## [1] 13.38886
## 
## [[1]][[2]]
## [1] -79.38721
## 
## [[1]][[3]]
## [1] 34.78053
## 
## [[1]][[4]]
## [1] 8.542322
## 
## 
## [[2]]
## [[2]][[1]]
## [1] 52.51704
## 
## [[2]][[2]]
## [1] 43.65396
## 
## [[2]][[3]]
## [1] 32.08048
## 
## [[2]][[4]]
## [1] 47.3724

What was originally a list with 4 elements of which each one was a list of 2 elements has become a list of 2 elements of which each one is a list of 4 elements. With flatten_dbl() we can simplify the structure, so that we end up with a list of 2, where each element consists of a vector of 4. The first vector contains the longitude and the second the latitude of our cities:

coordinates_transposed <-
  coordinates %>%
  transpose() %>%
  map(~ flatten_dbl(.))
coordinates_transposed
## [[1]]
## [1]  13.388860 -79.387207  34.780527   8.542322
## 
## [[2]]
## [1] 52.51704 43.65396 32.08048 47.37240

6.5.1 Exercises

  1. Explain what happens if you transpose a tibble:

    komoot %>%
      transpose()

6.6 Rectangling

Click here to show setup code.

library(tidyverse)
library(here)

komoot <- readRDS(here("data/komoot.rds"))

komoot_content <-
  komoot %>%
  pull(content)

coordinates_transposed <-
  komoot_content %>%
  map(list("features", 1, "geometry", "coordinates")) %>%
  transpose() %>%
  map(~ flatten_dbl(.))

Most of us R-users feel most at ease when dealing in R with data frames on which we can use a plethora of well-known (by us) functions with non-startling behaviour. What if we don’t get our data in such a form?

We are starting in our setup with the list coordinates_transposed from section “Transposing”.

A tibble is internally a list of named vectors of equal length. In two easy steps we can therefore make a tibble out of the unnamed list coordinates_transposed:

coordinates_transposed %>%
  rlang::set_names(c("lon", "lat"))
## $lon
## [1]  13.388860 -79.387207  34.780527   8.542322
## 
## $lat
## [1] 52.51704 43.65396 32.08048 47.37240
coordinates_transposed %>%
  rlang::set_names(c("lon", "lat")) %>%
  as_tibble()
## # A tibble: 4 x 2
##      lon   lat
##    <dbl> <dbl>
## 1  13.4   52.5
## 2 -79.4   43.7
## 3  34.8   32.1
## 4   8.54  47.4

If you want to keep the names open for now, but still get a tibble, you can set as_tibble()’s argument .name_repair = "universal":

coordinates_transposed %>%
  as_tibble(.name_repair = "universal")
## New names:
## * `` -> ...1
## * `` -> ...2
## # A tibble: 4 x 2
##     ...1  ...2
##    <dbl> <dbl>
## 1  13.4   52.5
## 2 -79.4   43.7
## 3  34.8   32.1
## 4   8.54  47.4
coordinates_transposed %>%
  as_tibble(.name_repair = "universal") %>%
  rename(lon = ...1, lat = ...2)
## New names:
## * `` -> ...1
## * `` -> ...2
## # A tibble: 4 x 2
##      lon   lat
##    <dbl> <dbl>
## 1  13.4   52.5
## 2 -79.4   43.7
## 3  34.8   32.1
## 4   8.54  47.4

6.7 Accessing APIs

Click here to show setup code.

library(tidyverse)
library(here)

When dealing with web-APIs, query results come frequently in the JSON (JavaScript Object Notation) format. How to deal with this in R? A way to “talk” with APIs is provided by the package {httr}. The “GET”-query is executed by using httr::GET() with the URL, containing the query specifics, as an argument:

req <- httr::GET("https://photon.komoot.de/api/?q=Paradeplatz&limit=3")

If you are using this command in a script, you need to wait until the query is finished processing:

httr::stop_for_status(req)

The result of the query can be accessed via httr::content():

content <- httr::content(req)
content
## $features
## $features[[1]]
## $features[[1]]$geometry
## $features[[1]]$geometry$coordinates
## $features[[1]]$geometry$coordinates[[1]]
## [1] 8.538948
## 
## $features[[1]]$geometry$coordinates[[2]]
## [1] 47.36981
## 
## 
## $features[[1]]$geometry$type
## [1] "Point"
## 
## 
## $features[[1]]$type
## [1] "Feature"
## 
## $features[[1]]$properties
## $features[[1]]$properties$osm_id
## [1] 905841
## 
## $features[[1]]$properties$osm_type
## [1] "R"
## 
## $features[[1]]$properties$extent
## $features[[1]]$properties$extent[[1]]
## [1] 8.538163
## 
## $features[[1]]$properties$extent[[2]]
## [1] 47.37027
## 
## $features[[1]]$properties$extent[[3]]
## [1] 8.539516
## 
## $features[[1]]$properties$extent[[4]]
## [1] 47.36935
## 
## 
## $features[[1]]$properties$country
## [1] "Switzerland"
## 
## $features[[1]]$properties$osm_key
## [1] "highway"
## 
## $features[[1]]$properties$city
## [1] "Zurich"
## 
## $features[[1]]$properties$osm_value
## [1] "pedestrian"
## 
## $features[[1]]$properties$postcode
## [1] "8001"
## 
## $features[[1]]$properties$name
## [1] "Paradeplatz"
## 
## $features[[1]]$properties$state
## [1] "Zurich"
## 
## 
## 
## $features[[2]]
## $features[[2]]$geometry
## $features[[2]]$geometry$coordinates
## $features[[2]]$geometry$coordinates[[1]]
## [1] 7.108249
## 
## $features[[2]]$geometry$coordinates[[2]]
## [1] 50.88602
## 
## 
## $features[[2]]$geometry$type
## [1] "Point"
## 
## 
## $features[[2]]$type
## [1] "Feature"
## 
## $features[[2]]$properties
## $features[[2]]$properties$osm_id
## [1] 389550464
## 
## $features[[2]]$properties$osm_type
## [1] "N"
## 
## $features[[2]]$properties$country
## [1] "Germany"
## 
## $features[[2]]$properties$osm_key
## [1] "place"
## 
## $features[[2]]$properties$city
## [1] "Cologne"
## 
## $features[[2]]$properties$osm_value
## [1] "locality"
## 
## $features[[2]]$properties$postcode
## [1] "51147"
## 
## $features[[2]]$properties$name
## [1] "Paradeplatz"
## 
## $features[[2]]$properties$state
## [1] "North Rhine-Westphalia"
## 
## 
## 
## $features[[3]]
## $features[[3]]$geometry
## $features[[3]]$geometry$coordinates
## $features[[3]]$geometry$coordinates[[1]]
## [1] 8.684258
## 
## $features[[3]]$geometry$coordinates[[2]]
## [1] 49.38674
## 
## 
## $features[[3]]$geometry$type
## [1] "Point"
## 
## 
## $features[[3]]$type
## [1] "Feature"
## 
## $features[[3]]$properties
## $features[[3]]$properties$osm_id
## [1] 391678888
## 
## $features[[3]]$properties$osm_type
## [1] "W"
## 
## $features[[3]]$properties$extent
## $features[[3]]$properties$extent[[1]]
## [1] 8.683635
## 
## $features[[3]]$properties$extent[[2]]
## [1] 49.38719
## 
## $features[[3]]$properties$extent[[3]]
## [1] 8.68488
## 
## $features[[3]]$properties$extent[[4]]
## [1] 49.3863
## 
## 
## $features[[3]]$properties$country
## [1] "Germany"
## 
## $features[[3]]$properties$osm_key
## [1] "place"
## 
## $features[[3]]$properties$city
## [1] "Heidelberg"
## 
## $features[[3]]$properties$osm_value
## [1] "locality"
## 
## $features[[3]]$properties$postcode
## [1] "69120"
## 
## $features[[3]]$properties$name
## [1] "Paradeplatz"
## 
## $features[[3]]$properties$state
## [1] "Baden-Württemberg"
## 
## 
## 
## 
## $type
## [1] "FeatureCollection"

As you can see, the result, as it is displayed in R, is already a nested list at this point. And we know how to deal with these objects.

The object did originally come as a JSON object though, which you can see if you look at the literal result of the query:

text_content <- httr::content(req, as = "text")
cat(text_content)
## {"features":[{"geometry":{"coordinates":[8.538948327037028,47.369806999999994],"type":"Point"},"type":"Feature","properties":{"osm_id":905841,"osm_type":"R","extent":[8.5381631,47.3702704,8.5395156,47.3693475],"country":"Switzerland","osm_key":"highway","city":"Zurich","osm_value":"pedestrian","postcode":"8001","name":"Paradeplatz","state":"Zurich"}},{"geometry":{"coordinates":[7.1082488,50.8860177],"type":"Point"},"type":"Feature","properties":{"osm_id":389550464,"osm_type":"N","country":"Germany","osm_key":"place","city":"Cologne","osm_value":"locality","postcode":"51147","name":"Paradeplatz","state":"North Rhine-Westphalia"}},{"geometry":{"coordinates":[8.684257597277371,49.3867415],"type":"Point"},"type":"Feature","properties":{"osm_id":391678888,"osm_type":"W","extent":[8.6836355,49.3871856,8.6848797,49.3862973],"country":"Germany","osm_key":"place","city":"Heidelberg","osm_value":"locality","postcode":"69120","name":"Paradeplatz","state":"Baden-Württemberg"}}],"type":"FeatureCollection"}

The package {jsonlite} has the function jsonlite::prettify() to offer, in order to display a one-line JSON-structure in a more clearly laid-out manner:

cat(jsonlite::prettify(text_content))
## {
##     "features": [
##         {
##             "geometry": {
##                 "coordinates": [
##                     8.538948327037028,
##                     47.369806999999994
##                 ],
##                 "type": "Point"
##             },
##             "type": "Feature",
##             "properties": {
##                 "osm_id": 905841,
##                 "osm_type": "R",
##                 "extent": [
##                     8.5381631,
##                     47.3702704,
##                     8.5395156,
##                     47.3693475
##                 ],
##                 "country": "Switzerland",
##                 "osm_key": "highway",
##                 "city": "Zurich",
##                 "osm_value": "pedestrian",
##                 "postcode": "8001",
##                 "name": "Paradeplatz",
##                 "state": "Zurich"
##             }
##         },
##         {
##             "geometry": {
##                 "coordinates": [
##                     7.1082488,
##                     50.8860177
##                 ],
##                 "type": "Point"
##             },
##             "type": "Feature",
##             "properties": {
##                 "osm_id": 389550464,
##                 "osm_type": "N",
##                 "country": "Germany",
##                 "osm_key": "place",
##                 "city": "Cologne",
##                 "osm_value": "locality",
##                 "postcode": "51147",
##                 "name": "Paradeplatz",
##                 "state": "North Rhine-Westphalia"
##             }
##         },
##         {
##             "geometry": {
##                 "coordinates": [
##                     8.684257597277371,
##                     49.3867415
##                 ],
##                 "type": "Point"
##             },
##             "type": "Feature",
##             "properties": {
##                 "osm_id": 391678888,
##                 "osm_type": "W",
##                 "extent": [
##                     8.6836355,
##                     49.3871856,
##                     8.6848797,
##                     49.3862973
##                 ],
##                 "country": "Germany",
##                 "osm_key": "place",
##                 "city": "Heidelberg",
##                 "osm_value": "locality",
##                 "postcode": "69120",
##                 "name": "Paradeplatz",
##                 "state": "Baden-Württemberg"
##             }
##         }
##     ],
##     "type": "FeatureCollection"
## }