Se encuentran API en todas partes hoy en día. Imagine: quiere recoger información sobre sus clientes potenciales gracias a sus emails. Bueno hay una API para hacer esto. ¿Necesita geocodar una dirección? También existe una API para hacer esto. Como desarrollador, integro regularmente nuevas API en mi sistema, en Python o en Go. Los dos métodos son bastante diferentes. Comparemolos trabajando sobre un caso “borderline”: enviar datos JSON en el body de una consulta POST.
Un ejemplo real
Recientemente utilicé la API NameAPI.org para separar un nombre entero en nombre y apellido, y conocer el género de la persona.
Su API espera datos JSON puestos en el body de una consulta POST. Ademas, el Content-Type
de la consulta tiene que ser application/json
y no multipart/form-data
. Se trata de un caso especial porque en general se envían los datos POST a través del header de la consulta, y si se quiere enviarlos en el body de la consulta (en el caso de datos JSON complejos por ejemplo) el Content-Type
común es multipart/form-data
.
Aquí esta el JSON que se quiere enviar:
{
"inputPerson" : {
"type" : "NaturalInputPerson",
"personName" : {
"nameFields" : [ {
"string" : "Petra",
"fieldType" : "GIVENNAME"
}, {
"string" : "Meyer",
"fieldType" : "SURNAME"
} ]
},
"gender" : "UNKNOWN"
}
}
Se puede hacerlo fácilmente con cURL:
curl -H "Content-Type: application/json" \
-X POST \
-d '{"inputPerson":{"type":"NaturalInputPerson","personName":{"nameFields":[{"string":"Petra Meyer","fieldType":"FULLNAME"}]}}}' \
http://rc50-api.nameapi.org/rest/v5.0/parser/personnameparser?apiKey=<API-KEY>
Y aquí esta la respuesta JSON de NameAPI.org:
{
"matches" : [ {
"parsedPerson" : {
"personType" : "NATURAL",
"personRole" : "PRIMARY",
"mailingPersonRoles" : [ "ADDRESSEE" ],
"gender" : {
"gender" : "MALE",
"confidence" : 0.9111111111111111
},
"addressingGivenName" : "Petra",
"addressingSurname" : "Meyer",
"outputPersonName" : {
"terms" : [ {
"string" : "Petra",
"termType" : "GIVENNAME"
},{
"string" : "Meyer",
"termType" : "SURNAME"
} ]
}
},
"parserDisputes" : [ ],
"likeliness" : 0.926699401733102,
"confidence" : 0.7536487758945387
}
¡Ahora veamos como hacer esto en Go y en Python!
Realización en Go
Código
/*
Fetch the NameAPI.org REST API and turn JSON response into a Go struct.
Sent data have to be JSON data encoded into request body.
Send request headers must be set to 'application/json'.
*/
package main
import (
"encoding/json"
"io/ioutil"
"log"
"net/http"
"strings"
)
// url of the NameAPI.org endpoint:
const (
url = "http://rc50-api.nameapi.org/rest/v5.0/parser/personnameparser?" +
"apiKey=<API-KEY>"
)
func main() {
// JSON string to be sent to NameAPI.org:
jsonString := `{
"inputPerson": {
"type": "NaturalInputPerson",
"personName": {
"nameFields": [
{
"string": "Petra",
"fieldType": "GIVENNAME"
}, {
"string": "Meyer",
"fieldType": "SURNAME"
}
]
},
"gender": "UNKNOWN"
}
}`
// Convert JSON string to NewReader (expected by NewRequest)
jsonBody := strings.NewReader(jsonString)
// Need to create a client in order to modify headers
// and set content-type to 'application/json':
client := &http.Client{}
req, err := http.NewRequest("POST", url, jsonBody)
if err != nil {
log.Println(err)
}
req.Header.Add("Content-Type", "application/json")
resp, err := client.Do(req)
// Proceed only if no error:
switch {
default:
// Create a struct dedicated to receiving the fetched
// JSON content:
type Level5 struct {
String string `json:"string"`
TermType string `json:"termType"`
}
type Level41 struct {
Gender string `json:"gender"`
Confidence float64 `json:"confidence"`
}
type Level42 struct {
Terms []Level5 `json:"terms"`
}
type Level3 struct {
Gender Level41 `json:"gender"`
OutputPersonName Level42 `json:"outputPersonName"`
}
type Level2 struct {
ParsedPerson Level3 `json:"parsedPerson"`
}
type RespContent struct {
Matches []Level2 `json:"matches"`
}
// Decode fetched JSON and put it into respContent:
respContentBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println(err)
}
var respContent RespContent
err = json.Unmarshal(respContentBytes, &respContent)
if err != nil {
log.Println(err)
}
log.Println(respContent)
case err != nil:
log.Println("Network error:", err)
case resp.StatusCode != 200:
log.Println("Bad HTTP status code:", err)
}
}
Explicaciones
Nos enfrentamos a 2 problemas:
- tenemos que utilizar
http.Client
,NewRequest()
,client.Do(req)
, yreq.Header.Add("Content-Type", "application/json")
par poner datos en el body y cambiar elContent-Type
. Son muchas etapas. - recibir el JSON de NameAPI en Go es difícil porque tenemos que crear un
struct
que tenga la misma estructura que el JSON.
Realización en Python
Código
"""
Fetch the NameAPI.org REST API and turn JSON response into Python dict.
Sent data have to be JSON data encoded into request body.
Send request headers must be set to 'application/json'.
"""
import requests
# url of the NameAPI.org endpoint:
url = (
"http://rc50-api.nameapi.org/rest/v5.0/parser/personnameparser?"
"apiKey=<API-KEY>"
)
# Dict of data to be sent to NameAPI.org:
payload = {
"inputPerson": {
"type": "NaturalInputPerson",
"personName": {
"nameFields": [
{
"string": "Petra",
"fieldType": "GIVENNAME"
}, {
"string": "Meyer",
"fieldType": "SURNAME"
}
]
},
"gender": "UNKNOWN"
}
}
# Proceed, only if no error:
try:
# Send request to NameAPI.org by doing the following:
# - make a POST HTTP request
# - encode the Python payload dict to JSON
# - pass the JSON to request body
# - set header's 'Content-Type' to 'application/json' instead of
# default 'multipart/form-data'
resp = requests.post(url, json=payload)
resp.raise_for_status()
# Decode JSON response into a Python dict:
resp_dict = resp.json()
print(resp_dict)
except requests.exceptions.HTTPError as e:
print("Bad HTTP status code:", e)
except requests.exceptions.RequestException as e:
print("Network error:", e)
Explicaciones
¡La biblioteca Request lo hace casi todo en una sola linea: resp = requests.post(url, json=payload)
!
Recibir el JSON retornado por NameAPI se hace en una linea también: resp_dict = resp.json()
.
Conclusión
Python es el ganador. La simplicidad de Python y su cantidad de bibliotecas disponibles nos ayudan mucho.
Aquí no hablamos del desempeño. Si el desempeño de la integracion que hace es importante para usted, Go puede ser una muy buena elección. Pero simplicidad y desempeño no están compatibles…