web-dev-qa-db-fra.com

Gestion des demandes de publication JSON dans Go

J'ai donc ce qui suit, qui semble incroyablement hacky, et je me suis dit que Go avait des bibliothèques mieux conçues que celle-ci, mais je ne trouve pas d'exemple où Go gère une POST demande de JSON. Les données. Ils sont tous sous forme de post.

Voici un exemple de demande: curl -X POST -d "{\"test\": \"that\"}" http://localhost:8082/test

Et voici le code, avec les journaux intégrés:

package main

import (
    "encoding/json"
    "log"
    "net/http"
)

type test_struct struct {
    Test string
}

func test(rw http.ResponseWriter, req *http.Request) {
    req.ParseForm()
    log.Println(req.Form)
    //LOG: map[{"test": "that"}:[]]
    var t test_struct
    for key, _ := range req.Form {
        log.Println(key)
        //LOG: {"test": "that"}
        err := json.Unmarshal([]byte(key), &t)
        if err != nil {
            log.Println(err.Error())
        }
    }
    log.Println(t.Test)
    //LOG: that
}

func main() {
    http.HandleFunc("/test", test)
    log.Fatal(http.ListenAndServe(":8082", nil))
}

Il doit y avoir un meilleur moyen, non? Je suis juste embarrassé pour trouver ce que la meilleure pratique pourrait être.

(Go est également connu sous le nom de Golang dans les moteurs de recherche et est mentionné ici pour que d'autres puissent le trouver.)

218
TomJ

Veuillez utiliser json.Decoder au lieu de json.Unmarshal.

func test(rw http.ResponseWriter, req *http.Request) {
    decoder := json.NewDecoder(req.Body)
    var t test_struct
    err := decoder.Decode(&t)
    if err != nil {
        panic(err)
    }
    log.Println(t.Test)
}
345
Joe

Vous devez lire req.Body. La méthode ParseForm lit à partir de req.Body puis l’analyse au format HTTP codé standard. Ce que vous voulez, c'est lire le corps et l'analyser au format JSON.

Voici votre code mis à jour.

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "io/ioutil"
)

type test_struct struct {
    Test string
}

func test(rw http.ResponseWriter, req *http.Request) {
    body, err := ioutil.ReadAll(req.Body)
    if err != nil {
        panic(err)
    }
    log.Println(string(body))
    var t test_struct
    err = json.Unmarshal(body, &t)
    if err != nil {
        panic(err)
    }
    log.Println(t.Test)
}

func main() {
    http.HandleFunc("/test", test)
    log.Fatal(http.ListenAndServe(":8082", nil))
}
77
Daniel

Je me rendais fou avec ce problème précis. JSON Marshaller et Unmarshaller ne peuplaient pas ma structure Go. Ensuite, j'ai trouvé la solution sur https://eager.io/blog/go-and-json :

"Comme pour toutes les structures de Go, il est important de se rappeler que seuls les champs avec une première lettre en majuscule sont visibles par des programmes externes tels que JSON Marshaller."

Après cela, mon Marshaller et Unmarshaller ont parfaitement fonctionné!

48
Steve Stilson

Il y a deux raisons pour lesquelles json.Decoder devrait être préféré à json.Unmarshal - qui ne sont pas abordées dans la réponse la plus populaire de 2013:

  1. Février 2018, go 1.10 a introduit une nouvelle méthode json.Decoder.DisallowUnknownFields () qui répond au souci de détecter les entrées JSON indésirables.
  2. req.Body est déjà un io.Reader. Lire tout son contenu, puis exécuter json.Unmarshal gaspille des ressources si le flux était, par exemple, un bloc de 10 Mo de JSON non valide. L'analyse du corps de la demande, avec json.Decoder _, car il se diffuse dans déclencherait une erreur d'analyse précoce si un JSON non valide était rencontré. Le traitement des flux d'E/S en temps réel est la méthode préférée .

Répondant à certains des commentaires des utilisateurs sur la détection des entrées utilisateur incorrectes:

Pour appliquer les champs obligatoires et d’autres contrôles d’assainissement, essayez:

d := json.NewDecoder(req.Body)
d.DisallowUnknownFields() // catch unwanted fields

// anonymous struct type: handy for one-time use
t := struct {
    Test *string `json:"test"` // pointer so we can test for field absence
}{}

err := d.Decode(&t)
if err != nil {
    // bad JSON or unrecognized json field
    http.Error(rw, err.Error(), http.StatusBadRequest)
    return
}

if t.Test == nil {
    http.Error(rw, "missing field 'test' from JSON object", http.StatusBadRequest)
    return
}

// optional extra check
if d.More() {
    http.Error(rw, "extraneous data after JSON object", http.StatusBadRequest)
    return
}

// got the input we expected: no more, no less
log.Println(*t.Test)

Terrain de je

Sortie typique:

$ curl -X POST -d "{}" http://localhost:8082/strict_test

expected json field 'test'

$ curl -X POST -d "{\"Test\":\"maybe?\",\"Unwanted\":\"1\"}" http://localhost:8082/strict_test

json: unknown field "Unwanted"

$ curl -X POST -d "{\"Test\":\"oops\"}g4rB4g3@#$%^&*" http://localhost:8082/strict_test

extraneous data after JSON

$ curl -X POST -d "{\"Test\":\"Works\"}" http://localhost:8082/strict_test 

log: 2019/03/07 16:03:13 Works
21
colminator

J'ai trouvé l'exemple suivant tiré de la documentation vraiment utile (source ici ).

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "log"
    "strings"
)

func main() {
    const jsonStream = `
        {"Name": "Ed", "Text": "Knock knock."}
        {"Name": "Sam", "Text": "Who's there?"}
        {"Name": "Ed", "Text": "Go fmt."}
        {"Name": "Sam", "Text": "Go fmt who?"}
        {"Name": "Ed", "Text": "Go fmt yourself!"}
    `
    type Message struct {
        Name, Text string
    }
    dec := json.NewDecoder(strings.NewReader(jsonStream))
    for {
        var m Message
        if err := dec.Decode(&m); err == io.EOF {
            break
        } else if err != nil {
            log.Fatal(err)
        }
        fmt.Printf("%s: %s\n", m.Name, m.Text)
    }
}

La clé ici étant que l'OP cherchait à décoder

type test_struct struct {
    Test string
}

... Dans ce cas, nous abandonnerions le const jsonStream et remplacerons la structure Message par le test_struct:

func test(rw http.ResponseWriter, req *http.Request) {
    dec := json.NewDecoder(req.Body)
    for {
        var t test_struct
        if err := dec.Decode(&t); err == io.EOF {
            break
        } else if err != nil {
            log.Fatal(err)
        }
        log.Printf("%s\n", t.Test)
    }
}

Update : J'ajouterais également que cet article fournit également d'excellentes données sur la façon de répondre avec JSON. L'auteur explique struct tags, que je ne connaissais pas.

Comme JSON ne ressemble normalement pas à {"Test": "test", "SomeKey": "SomeVal"}, mais plutôt à {"test": "test", "somekey": "some value"}, vous pouvez restructurer votre structure de la manière suivante:

type test_struct struct {
    Test string `json:"test"`
    SomeKey string `json:"some-key"`
}

... et maintenant votre gestionnaire analysera JSON en utilisant "une clé" par opposition à "SomeKey" (que vous utiliserez en interne).

17
JohnnyCoder