Обработка JSON-запроса в Go

250

Итак, у меня есть следующее, которое кажется невероятным, и я подумал, что Go имеет лучше спроектированные библиотеки, чем этот, но я не могу найти пример того, как Go обрабатывает POST-запрос данных JSON. Все они представляют собой посты.

Вот пример запроса: curl -X POST -d "{\"test\": \"that\"}" http://localhost:8082/test

А вот код со встроенными журналами:

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))
}

Должен быть лучший способ, верно? Я просто ошеломлен поиском лучшей практики.

(Go также известен как Golang для поисковых систем и упоминается здесь, чтобы другие могли его найти.)

TomJ
источник
3
если вы используете curl -X POST -H 'Content-Type: application/json' -d "{\"test\": \"that\"}", то req.Form["test"]должны вернуться"that"
Vinicius
@ Виниций, есть ли доказательства этому?
Диралик

Ответы:

388

Пожалуйста, используйте json.Decoderвместо 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)
}
Джо
источник
79
Не могли бы вы объяснить, почему?
Райан Бигг
86
Для начала, похоже, что он может обрабатывать поток, а не загружать его в буфер самостоятельно. (Я другой Джо Кстати)
Джо
7
Интересно, как будет выглядеть правильная обработка ошибок в этом случае. Я не думаю, что это хорошая идея, чтобы паниковать из-за недействительного JSON.
codepushr
15
Я не думаю, что вам нужно defer req.Body.Close()Из документов: «Сервер закроет тело запроса. Обработчику ServeHTTP это не нужно». Также, чтобы ответить на @thisisnotabus, из документов: «Для запросов к серверу тело запроса всегда не равно нулю, но будет возвращать EOF немедленно, когда тела нет» golang.org/pkg/net/http/#Request
Drew LeSueur
22
Я бы предложил не использовать json.Decoder. Он предназначен для потоков объектов JSON, а не для одного объекта. Это не более эффективно для одного объекта JSON, так как он считывает весь объект в память. Недостатком является то, что если мусор включается после объекта, он не будет жаловаться. В зависимости от нескольких факторов, json.Decoderвозможно, не полностью прочитать тело и соединение не будет иметь право на повторное использование.
B
85

Вам нужно читать с req.Body. ParseFormМетод чтения от req.Bodyи затем разбор его в стандартном формате HTTP закодированной. Вам нужно прочитать тело и разобрать его в формате JSON.

Вот ваш код обновлен.

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))
}
Даниил
источник
Спасибо! Я вижу, где я сейчас иду не так. Если вы звоните req.ParseForm(), что я делал в более ранних попытках решить эту проблему, перед тем, как вы попытаетесь прочитать req.Body, он, кажется, очищает тело и unexpected end of JSON inputвыбрасывается, когда вы переходите Unmarshal(по крайней мере, в 1.0.2)
TomJ
1
@Daniel: Когда я делаю curl -X POST -d "{\" tes \ ": \" that \ "}" localhost: 8082 / test , log.Println (t.Test) возвращает пустое значение. Зачем ? Или в этом случае, если опубликовать любой другой JSON, он возвращается пустым
Somesh
Ваш запрос POST неверен. tes! = тест. Цените это 5 лет назад: /
Рамбатино
Это хороший простой пример!
15412 с
Это хороший совет, но, чтобы быть ясным, ответы, касающиеся использования json.NewDecoder(req.Body), также являются правильными.
Рич
59

Я сводил себя с ума с этой точной проблемой. Мой JSON Marshaller и Unmarshaller не заполняли мою структуру Go. Затем я нашел решение по адресу https://eager.io/blog/go-and-json :

«Как и во всех структурах Go, важно помнить, что только поля с заглавной первой буквой видны внешним программам, таким как JSON Marshaller».

После этого мои маршаллеры и унмаршаллеры сработали отлично!

Стив Стилсон
источник
Пожалуйста, включите некоторые фрагменты из ссылки. Если это устарело, примеры будут потеряны.
0:30
47

Есть две причины, по которым json.Decoderследует отдавать предпочтение, json.Unmarshal- они не рассматриваются в самом популярном ответе 2013 года:

  1. Февраль 2018 года, go 1.10представил новый метод json.Decoder.DisallowUnknownFields (), который решает проблему обнаружения нежелательного JSON-ввода
  2. req.Bodyэто уже io.Reader. Считывание всего содержимого и последующее использование json.Unmarshalненужных ресурсов, если поток был, скажем, 10-мегабайтным блоком недопустимого JSON. Синтаксический анализ тела запроса при json.Decoderего поступлении вызовет ошибку раннего разбора, если будет обнаружен недопустимый JSON. Обработка потоков ввода / вывода в реальном времени является предпочтительным способом .

Обращаясь к некоторым комментариям пользователя об обнаружении неправильного ввода пользователя:

Чтобы ввести обязательные поля и другие санитарные проверки, попробуйте:

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)

Игровая площадка

Типичный вывод:

$ 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
colm.anseo
источник
6
Спасибо за разъяснение мнений, вместо того, чтобы просто заявить, что что-то плохое
Фьолнир Дворжак
ты знаешь, с чем это не справляется? я видел, что Test может быть в json дважды, и он принимает 2-е вхождение
tooptoop4
@ tooptoop4 Чтобы написать сценарий, который никогда не произойдет, нужно написать собственный декодер, чтобы предупредить о дублирующих полях - добавив неэффективность декодеру. Ни один стандартный JSON-кодировщик никогда не будет создавать дубликаты полей.
colm.anseo
20

Я нашел следующий пример из документации действительно полезным (источник здесь ).

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)
    }
}

Ключевым моментом здесь является то, что ОП пытался декодировать

type test_struct struct {
    Test string
}

... в этом случае мы удалили бы const jsonStreamи заменили Messageструктуру на 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)
    }
}

Обновление : я также хотел бы добавить, что в этом посте представлены отличные данные об ответах с помощью JSON. Автор объясняет struct tags, о чем я не знал.

Поскольку JSON обычно выглядит не так {"Test": "test", "SomeKey": "SomeVal"}, а наоборот {"test": "test", "somekey": "some value"}, вы можете реструктурировать свою структуру следующим образом:

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

... и теперь ваш обработчик будет анализировать JSON, используя "some-key" вместо "SomeKey" (который вы будете использовать внутри).

JohnnyCoder
источник
1
type test struct {
    Test string `json:"test"`
}

func test(w http.ResponseWriter, req *http.Request) {
    var t test_struct

    body, _ := ioutil.ReadAll(req.Body)
    json.Unmarshal(body, &t)

    fmt.Println(t)
}
Анжелика Паявал
источник