rgoogle-places-apihttr2

Sending a request with httr2: JSON body contains a nested JSON input


In the below example i am using Google Maps Places API's (new) Text Search (new) request method. See here more information about this API method.

Basically i am starting to have problems when the JSON request body starts to have nested (and nasty) elements. Below i will give examples on what works and what doesn't work:

Works: request with string objects as inputs

library(httr2)

# Test params:
textQuery = "nature retreat in Southwest Alentejo, Portugal"
maxResultCount = 10
key = API_KEY

params <- list(textQuery = textQuery,
               maxResultCount = maxResultCount)

req <-
  request("https://places.googleapis.com/v1/places:searchText") |>
  req_headers(
    `X-Goog-Api-Key` = key,
    `X-Goog-FieldMask` = "*",
    `Content-Type` = "application/json"
  ) |>
  req_body_json(Filter(Negate(is.null), params))

req |>
  req_dry_run()
# POST /v1/places: searchText HTTP/1.1
# Host: places.googleapis.com
# User-Agent: httr2/0.2.3 r-curl/5.2.1 libcurl/7.81.0
# Accept: */*
#   Accept-Encoding: deflate, gzip, br, zstd
# X-Goog-Api-Key: API_KEY
# X-Goog-FieldMask: *
#   Content-Type: application/json
# Content-Length: 47
# 
# {"textQuery":"nature retreat in Southwest Alentejo, Portugal","maxResultCount":10}

req |>
  req_perform()
# <httr2_response>
#   POST https://places.googleapis.com/v1/places:searchText
# Status: 200 OK
# Content-Type: application/json
# Body: In memory (192086 bytes)

Doesn't work: JSON object as input

Now i am trying to add a JSON input to the JSON request body. Below i am trying to reproduce this example from the Places API, which has the following curl command:

curl -X POST -d '{
  "textQuery" : "Spicy Vegetarian Food",
  "openNow": true,
  "maxResultCount": 10,
  "locationBias": {
    "circle": {
      "center": {"latitude": 37.7937, "longitude": -122.3965},
      "radius": 500.0
    }
  },
}' \
-H 'Content-Type: application/json' -H 'X-Goog-Api-Key: API_KEY' \
-H 'X-Goog-FieldMask: places.displayName,places.formattedAddress' \
'https://places.googleapis.com/v1/places:searchText'

This should be the httr2 equivalent, but it doesn't work:

# Test params:
textQuery = "Spicy Vegetarian Food"
openNow = "true"
maxResultCount = 10
latitude = 37.7937
longitude = -122.3965
radius = 500
key = API_KEY

# Creating the locationRestriction JSON parameter:
body <- list()
innerBody <- list()
innerInnerBody <- list()
innerInnerBody$latitude <- latitude
innerInnerBody$longitude <- longitude
innerBody$center <- innerInnerBody
innerBody$radius <- radius
body$circle <- innerBody
locationBias <- jsonlite::toJSON(body, auto_unbox = TRUE, pretty = TRUE)
locationBias
# {
#   "circle": {
#     "center": {
#       "latitude": 37.7937,
#       "longitude": -122.3965
#     },
#     "radius": 500
#   }
# } 

params <- list(textQuery = textQuery,
               openNow = openNow,
               maxResultCount = maxResultCount,
               locationBias = locationBias)
params
# $textQuery
# [1] "Spicy Vegetarian Food"
# 
# $openNow
# [1] "true"
# 
# $maxResultCount
# [1] 10
# 
# $locationBias
# {
#   "circle": {
#     "center": {
#       "latitude": 37.7937,
#       "longitude": -122.3965
#     },
#     "radius": 500
#   }
# }

req <-
  request("https://places.googleapis.com/v1/places:searchText") |>
  req_headers(
    `X-Goog-Api-Key` = key,
    `X-Goog-FieldMask` = "*",
    `Content-Type` = "application/json"
  ) |>
  req_body_json(Filter(Negate(is.null), params))

req |>
  req_dry_run()
# POST /v1/places: searchText HTTP/1.1
# Host: places.googleapis.com
# User-Agent: httr2/0.2.3 r-curl/5.2.1 libcurl/7.81.0
# Accept: */*
#   Accept-Encoding: deflate, gzip, br, zstd
# X-Goog-Api-Key: API_KEY
# X-Goog-FieldMask: *
#   Content-Type: application/json
# Content-Length: 228
# 
# {"textQuery":"Spicy Vegetarian Food","openNow":"true","maxResultCount":10,"locationBias":"{\n  \"circle\": {\n    \"center\": {\n      \"latitude\": 37.7937,\n      \"longitude\": -122.3965\n    },\n    \"radius\": 500\n  }\n}"}

req |>
  req_perform()
# Error in `req_perform()`:
#   ! HTTP 400 Bad Request.

I have double checked that by removing the locationBias input all works fine. Any ideas why i get the 400 error in httr2 with the JSON locationBias input?


Solution

  • According to curl example, payload is a regular JSON object; as you are passing body through jsonlite::toJSON() and then assign it to locationBias, it becomes a string. Note how it gets escaped in req_dry_run() output:

    # ... "locationBias":"{\n  \"circle\": {\n    \"center\": {\n      \"latitude\": 37.7937,\n      \"longitude\": -122.3965\n    },\n    \"radius\": 500\n  }\n}"}
    
    

    Without jsonlite::toJSON() it should be fine:

    library(httr2)
    
    # Test params:
    textQuery = "Spicy Vegetarian Food"
    openNow = "true"
    maxResultCount = 10
    latitude = 37.7937
    longitude = -122.3965
    radius = 500
    key = "API_KEY"
    
    # Creating the locationRestriction JSON parameter:
    body <- list()
    innerBody <- list()
    innerInnerBody <- list()
    innerInnerBody$latitude <- latitude
    innerInnerBody$longitude <- longitude
    innerBody$center <- innerInnerBody
    innerBody$radius <- radius
    body$circle <- innerBody
    str(body)
    #> List of 1
    #>  $ circle:List of 2
    #>   ..$ center:List of 2
    #>   .. ..$ latitude : num 37.8
    #>   .. ..$ longitude: num -122
    #>   ..$ radius: num 500
    
    params <- list(textQuery = textQuery,
                   openNow = openNow,
                   maxResultCount = maxResultCount,
                   locationBias = body)
    str(params)
    #> List of 4
    #>  $ textQuery     : chr "Spicy Vegetarian Food"
    #>  $ openNow       : chr "true"
    #>  $ maxResultCount: num 10
    #>  $ locationBias  :List of 1
    #>   ..$ circle:List of 2
    #>   .. ..$ center:List of 2
    #>   .. .. ..$ latitude : num 37.8
    #>   .. .. ..$ longitude: num -122
    #>   .. ..$ radius: num 500
    
    req <-
      request("https://places.googleapis.com/v1/places:searchText") |>
      req_headers(
        `X-Goog-Api-Key` = key,
        `X-Goog-FieldMask` = "*",
        `Content-Type` = "application/json"
      ) |>
      req_body_json(Filter(Negate(is.null), params))
    

    Payload in request looks fine now:

    req |>
      req_dry_run()
    #> POST /v1/places: searchText HTTP/1.1
    #> Host: places.googleapis.com
    #> User-Agent: httr2/1.0.0 r-curl/5.2.1 libcurl/8.3.0
    #> Accept: */*
    #> Accept-Encoding: deflate, gzip
    #> X-Goog-Api-Key: API_KEY
    #> X-Goog-FieldMask: *
    #> Content-Type: application/json
    #> Content-Length: 178
    #> 
    #> {"textQuery":"Spicy Vegetarian Food","openNow":"true","maxResultCount":10,"locationBias":{"circle":{"center":{"latitude":37.793700000000001,"longitude":-122.3965},"radius":500}}}
    

    Created on 2024-04-24 with reprex v2.1.0