trevi
trevi

Reputation: 711

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?

Upvotes: 1

Views: 333

Answers (1)

margusl
margusl

Reputation: 17294

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

Upvotes: 2

Related Questions